Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-11-06 14:02:51 +01:00
commit 75419df577
344 changed files with 7188 additions and 3215 deletions

View File

@ -37,10 +37,10 @@ endif
ifndef MOZ_PROFILE_USE
# We need to explicitly put backend.RecursiveMakeBackend here
# otherwise the rule in rules.mk doesn't run early enough.
libs binaries export tools:: CLOBBER $(configure_dir)/configure config.status backend.RecursiveMakeBackend
$(TIERS) binaries:: CLOBBER $(configure_dir)/configure config.status backend.RecursiveMakeBackend
ifndef JS_STANDALONE
ifndef LIBXUL_SDK
libs binaries export tools:: $(topsrcdir)/js/src/configure js/src/config.status
$(TIERS) binaries:: $(topsrcdir)/js/src/configure js/src/config.status
endif
endif
endif
@ -174,7 +174,7 @@ endif
endif
default all::
$(call BUILDSTATUS,TIERS export $(if $(COMPILE_ENVIRONMENT),compile )libs tools $(if $(MOZ_AUTOMATION),$(MOZ_AUTOMATION_TIERS)))
$(call BUILDSTATUS,TIERS $(TIERS) $(if $(MOZ_AUTOMATION),$(MOZ_AUTOMATION_TIERS)))
include $(topsrcdir)/config/rules.mk

View File

@ -181,13 +181,18 @@ already_AddRefed<nsIURI>
ImageAccessible::GetLongDescURI() const
{
if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::longdesc)) {
nsGenericHTMLElement* element =
nsGenericHTMLElement::FromContent(mContent);
if (element) {
nsCOMPtr<nsIURI> uri;
element->GetURIAttr(nsGkAtoms::longdesc, nullptr, getter_AddRefs(uri));
return uri.forget();
// To check if longdesc contains an invalid url.
nsAutoString longdesc;
mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::longdesc, longdesc);
if (longdesc.FindChar(' ') != -1 || longdesc.FindChar('\t') != -1 ||
longdesc.FindChar('\r') != -1 || longdesc.FindChar('\n') != -1) {
return nullptr;
}
nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
nsCOMPtr<nsIURI> uri;
nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), longdesc,
mContent->OwnerDoc(), baseURI);
return uri.forget();
}
DocAccessible* document = Document();

View File

@ -93,13 +93,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429659
var attributes = {"src": aSRC};
testAttrs(acc, attributes, true);
if (aActionCount) {
is(acc.actionCount, aActionCount,
"Wrong number of actions for " + aID + "!");
for (index = 0; index < aActionNames.length; index++)
var actionCount = aActionCount || 0;
is(acc.actionCount, actionCount,
"Wrong number of actions for " + aID + "!");
if (actionCount) {
for (index = 0; index < aActionNames.length; index++) {
is(acc.getActionName(index), aActionNames[index],
"Wrong action name for " + aID + ", index " + index +"!");
}
}
}
@ -109,13 +110,18 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429659
testThis("nonLinkedImage", "moz.png", 89, 38);
// Test linked image
testThis("linkedImage", "moz.png", 89, 38);
var actionNamesArray = new Array("jump");
testThis("linkedImage", "moz.png", 89, 38, 1,
actionNamesArray);
// Image with long desc
var actionNamesArray = new Array("showlongdesc");
testThis("longdesc", "moz.png", 89, 38, 1,
actionNamesArray);
// Image with invalid url in long desc
testThis("invalidLongdesc", "moz.png", 89, 38, 0);
// Image with click and long desc
actionNamesArray = null;
actionNamesArray = new Array("click", "showlongdesc");
@ -134,6 +140,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429659
testThis("longdesc2", "moz.png",
89, 38, 1, actionNamesArray);
// Image described by HTML:a@href with whitespaces
actionNamesArray = null;
actionNamesArray = new Array("showlongdesc");
testThis("longdesc3", "moz.png",
89, 38, 1, actionNamesArray);
SimpleTest.finish();
}
@ -162,6 +174,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429659
<br>Image with longdesc:<br>
<img id="longdesc" src="moz.png" longdesc="longdesc_src.html"
alt="Image of Mozilla logo"/>
<br>Image with invalid url in longdesc:<br>
<img id="invalidLongdesc" src="moz.png" longdesc="longdesc src.html"
alt="Image of Mozilla logo"/>
<br>Image with click and longdesc:<br>
<img id="clickAndLongdesc" src="moz.png" longdesc="longdesc_src.html"
alt="Another image of Mozilla logo" onclick="alert('Clicked!');"/>
@ -171,6 +186,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429659
alt="Second Image of Mozilla logo"/>
<a id="describing_link" href="longdesc_src.html">link to description of image</a>
<br>Image described by a link to be treated as longdesc with whitespaces<br>
<img id="longdesc3" src="moz.png" aria-describedby="describing_link2"
alt="Second Image of Mozilla logo"/>
<a id="describing_link2" href="longdesc src.html">link to description of image</a>
<br>Image with click:<br>
<img id="click" src="moz.png"
alt="A third image of Mozilla logo" onclick="alert('Clicked, too!');"/>

View File

@ -172,6 +172,7 @@ EXTRA_JS_MODULES.commonjs.diffpatcher.test += [
EXTRA_JS_MODULES.commonjs.framescript += [
'source/lib/framescript/FrameScriptManager.jsm',
'source/lib/framescript/LoaderHelper.jsm',
'source/lib/framescript/tab-events.js',
]
@ -240,6 +241,8 @@ EXTRA_JS_MODULES.commonjs.sdk.content += [
'source/lib/sdk/content/sandbox.js',
'source/lib/sdk/content/thumbnail.js',
'source/lib/sdk/content/utils.js',
'source/lib/sdk/content/worker-child.js',
'source/lib/sdk/content/worker-parent.js',
'source/lib/sdk/content/worker.js',
]
@ -429,7 +432,6 @@ EXTRA_JS_MODULES.commonjs.sdk.util += [
'source/lib/sdk/util/contract.js',
'source/lib/sdk/util/deprecate.js',
'source/lib/sdk/util/dispatcher.js',
'source/lib/sdk/util/iteration.js',
'source/lib/sdk/util/list.js',
'source/lib/sdk/util/match-pattern.js',
'source/lib/sdk/util/object.js',
@ -453,4 +455,5 @@ EXTRA_JS_MODULES.commonjs.sdk.zip += [
EXTRA_JS_MODULES.commonjs.toolkit += [
'source/lib/toolkit/loader.js',
'source/lib/toolkit/require.js',
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
/* 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 { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Loader } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {});
const cpmm = Cc['@mozilla.org/childprocessmessagemanager;1'].getService(Ci.nsISyncMessageSender);
// one Loader instance per addon (per @loader/options to be precise)
let addons = new Map();
cpmm.addMessageListener('sdk/loader/unload', ({ data: options }) => {
let key = JSON.stringify(options);
let addon = addons.get(key);
if (addon)
addon.loader.unload();
addons.delete(key);
})
// create a Loader instance from @loader/options
function loader(options) {
let key = JSON.stringify(options);
let addon = addons.get(key) || {};
if (!addon.loader) {
addon.loader = Loader.Loader(options);
addon.require = Loader.Require(addon.loader, { id: 'LoaderHelper' });
addons.set(key, addon);
}
return addon;
}
const EXPORTED_SYMBOLS = ['loader'];

View File

@ -3,8 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const observerSvc = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const observerSvc = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
// map observer topics to tab event names
const EVENTS = {
@ -32,3 +32,20 @@ addEventListener('pageshow', ({ target, type, persisted }) => {
if (target === content.document)
sendAsyncMessage('sdk/tab/event', { type, persisted });
}, true);
// workers for windows in this tab
let keepAlive = new Map();
addMessageListener('sdk/worker/create', ({ data: { options, addon }}) => {
options.manager = this;
let { loader } = Cu.import(addon.paths[''] + 'framescript/LoaderHelper.jsm', {});
let { WorkerChild } = loader(addon).require('sdk/content/worker-child');
sendAsyncMessage('sdk/worker/attach', { id: options.id });
keepAlive.set(options.id, new WorkerChild(options));
})
addMessageListener('sdk/worker/event', ({ data: { id, args: [event]}}) => {
if (event === 'detach')
keepAlive.delete(id);
})

View File

@ -29,14 +29,15 @@ function isUTF8(charset) {
exports.decode = function (data, charset) {
if (isUTF8(charset))
return decodeURIComponent(escape(atob(data)))
return decodeURIComponent(escape(atob(data)))
return atob(data);
return atob(data);
}
exports.encode = function (data, charset) {
if (isUTF8(charset))
return btoa(unescape(encodeURIComponent(data)))
return btoa(data);
data = String.fromCharCode(...[(c.charCodeAt(0) & 0xff) for (c of data)]);
return btoa(data);
}

View File

@ -8,7 +8,8 @@ module.metadata = {
"stability": "stable",
"engines": {
// TODO Fennec Support 789757
"Firefox": "*"
"Firefox": "*",
"SeaMonkey": "*"
}
};

View File

@ -280,36 +280,6 @@ Object.freeze({
Object.defineProperty(exports, "self", {
value: self
});
exports.on = function deprecatedOn() {
console.error("DEPRECATED: The global `on()` function in content " +
"scripts is deprecated in favor of the `self.on()` " +
"function, which works the same. Replace calls to `on()` " +
"with calls to `self.on()`" +
"For more info on `self.on`, see " +
"<https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts/using_postMessage>.");
return self.on.apply(null, arguments);
};
// Deprecated use of `onMessage` from globals
let onMessage = null;
Object.defineProperty(exports, "onMessage", {
get: function () onMessage,
set: function (v) {
if (onMessage)
self.removeListener("message", onMessage);
console.error("DEPRECATED: The global `onMessage` function in content" +
"scripts is deprecated in favor of the `self.on()` " +
"function. Replace `onMessage = function (data){}` " +
"definitions with calls to `self.on('message', " +
"function (data){})`. " +
"For more info on `self.on`, see " +
"<https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts/using_postMessage>.");
onMessage = v;
if (typeof onMessage == "function")
self.on("message", onMessage);
}
});
},
injectOptions: function (exports, options) {

View File

@ -44,3 +44,8 @@ exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow;
exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) {
return getThumbnailCanvasForWindow(window).toDataURL()
};
// default 80x45 blank when not available
exports.BLANK = 'data:image/png;base64,' +
'iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAAJElEQVRoge3BAQ'+
'EAAACCIP+vbkhAAQAAAAAAAAAAAAAAAADXBjhtAAGQ0AF/AAAAAElFTkSuQmCC';

View File

@ -0,0 +1,88 @@
/* 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 { merge } = require('../util/object');
const { Class } = require('../core/heritage');
const { EventTarget } = require('../event/target');
const { getInnerId, getByInnerId } = require('../window/utils');
const { instanceOf, isObject } = require('../lang/type');
const { on: observe } = require('../system/events');
const { WorkerSandbox } = require('./sandbox');
const { Ci } = require('chrome');
const EVENTS = {
'chrome-page-shown': 'pageshow',
'content-page-shown': 'pageshow',
'chrome-page-hidden': 'pagehide',
'content-page-hidden': 'pagehide',
'inner-window-destroyed': 'detach',
}
const WorkerChild = Class({
implements: [EventTarget],
initialize(options) {
merge(this, options);
this.port = EventTarget();
this.port.on('*', this.send.bind(this, 'event'));
this.on('*', this.send.bind(this));
this.observe = this.observe.bind(this);
for (let topic in EVENTS)
observe(topic, this.observe);
this.receive = this.receive.bind(this);
this.manager.addMessageListener('sdk/worker/message', this.receive);
this.sandbox = WorkerSandbox(this, getByInnerId(this.window));
},
// messages
receive({ data: { id, args }}) {
if (id !== this.id)
return;
this.sandbox.emit(...args);
if (args[0] === 'detach')
this.destroy(args[1]);
},
send(...args) {
args = JSON.parse(JSON.stringify(args, exceptions));
if (this.manager.content)
this.manager.sendAsyncMessage('sdk/worker/event', { id: this.id, args });
},
// notifications
observe({ type, subject }) {
if (!this.sandbox)
return;
if (subject.defaultView && getInnerId(subject.defaultView) === this.window) {
this.sandbox.emitSync(EVENTS[type]);
this.send(EVENTS[type]);
}
if (type === 'inner-window-destroyed' &&
subject.QueryInterface(Ci.nsISupportsPRUint64).data === this.window) {
this.destroy();
}
},
// detach/destroy: unload and release the sandbox
destroy(reason) {
if (!this.sandbox)
return;
if (this.manager.content)
this.manager.removeMessageListener('sdk/worker/message', this.receive);
this.sandbox.destroy(reason);
this.sandbox = null;
this.send('detach');
}
})
exports.WorkerChild = WorkerChild;
// Error instances JSON poorly
function exceptions(key, value) {
if (!isObject(value) || !instanceOf(value, Error))
return value;
let _errorType = value.constructor.name;
let { message, fileName, lineNumber, stack, name } = value;
return { _errorType, message, fileName, lineNumber, stack, name };
}

View File

@ -0,0 +1,184 @@
/* 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 { emit } = require('../event/core');
const { omit } = require('../util/object');
const { Class } = require('../core/heritage');
const { method } = require('../lang/functional');
const { getInnerId } = require('../window/utils');
const { EventTarget } = require('../event/target');
const { when, ensure } = require('../system/unload');
const { getTabForWindow } = require('../tabs/helpers');
const { getTabForContentWindow, getBrowserForTab } = require('../tabs/utils');
const { isPrivate } = require('../private-browsing/utils');
const { getFrameElement } = require('../window/utils');
const { attach, detach, destroy } = require('./utils');
const { on: observe } = require('../system/events');
const { uuid } = require('../util/uuid');
const { Ci, Cc } = require('chrome');
const ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].
getService(Ci.nsIMessageBroadcaster);
// null-out cycles in .modules to make @loader/options JSONable
const ADDON = omit(require('@loader/options'), ['modules', 'globals']);
const workers = new WeakMap();
let modelFor = (worker) => workers.get(worker);
const ERR_DESTROYED = "Couldn't find the worker to receive this message. " +
"The script may not be initialized yet, or may already have been unloaded.";
const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
"until it is visible again.";
// a handle for communication between content script and addon code
const Worker = Class({
implements: [EventTarget],
initialize(options = {}) {
let model = {
inited: false,
earlyEvents: [], // fired before worker was inited
frozen: true, // document is in BFcache, let it go
options,
};
workers.set(this, model);
ensure(this, 'destroy');
this.on('detach', this.detach);
EventTarget.prototype.initialize.call(this, options);
this.receive = this.receive.bind(this);
model.observe = ({ subject }) => {
let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (model.window && getInnerId(model.window) === id)
this.detach();
}
observe('inner-window-destroyed', model.observe);
this.port = EventTarget();
this.port.emit = this.send.bind(this, 'event');
this.postMessage = this.send.bind(this, 'message');
if ('window' in options)
attach(this, options.window);
},
// messages
receive({ data: { id, args }}) {
let model = modelFor(this);
if (id !== model.id || !model.childWorker)
return;
if (args[0] === 'event')
emit(this.port, ...args.slice(1))
else
emit(this, ...args);
},
send(...args) {
let model = modelFor(this);
if (!model.inited) {
model.earlyEvents.push(args);
return;
}
if (!model.childWorker && args[0] !== 'detach')
throw new Error(ERR_DESTROYED);
if (model.frozen && args[0] !== 'detach')
throw new Error(ERR_FROZEN);
try {
model.manager.sendAsyncMessage('sdk/worker/message', { id: model.id, args });
} catch (e) {
//
}
},
// properties
get url() {
let { window } = modelFor(this);
return window && window.document.location.href;
},
get contentURL() {
let { window } = modelFor(this);
return window && window.document.URL;
},
get tab() {
let { window } = modelFor(this);
return window && getTabForWindow(window);
},
toString: () => '[object Worker]',
// methods
attach: method(attach),
detach: method(detach),
destroy: method(destroy),
})
exports.Worker = Worker;
attach.define(Worker, function(worker, window) {
let model = modelFor(worker);
model.window = window;
model.options.window = getInnerId(window);
model.id = model.options.id = String(uuid());
let tab = getTabForContentWindow(window);
if (tab) {
model.manager = getBrowserForTab(tab).messageManager;
} else {
model.manager = getFrameElement(window.top).frameLoader.messageManager;
}
model.manager.addMessageListener('sdk/worker/event', worker.receive);
model.manager.addMessageListener('sdk/worker/attach', attach);
model.manager.sendAsyncMessage('sdk/worker/create', {
options: model.options,
addon: ADDON
});
function attach({ data }) {
if (data.id !== model.id)
return;
model.manager.removeMessageListener('sdk/worker/attach', attach);
model.childWorker = true;
worker.on('pageshow', () => model.frozen = false);
worker.on('pagehide', () => model.frozen = true);
model.inited = true;
model.frozen = false;
model.earlyEvents.forEach(args => worker.send(...args));
emit(worker, 'attach', window);
}
})
// unload and release the child worker, release window reference
detach.define(Worker, function(worker, reason) {
let model = modelFor(worker);
worker.send('detach', reason);
if (!model.childWorker)
return;
model.childWorker = null;
model.earlyEvents = [];
model.window = null;
emit(worker, 'detach');
model.manager.removeMessageListener('sdk/worker/event', this.receive);
})
isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
// unlod worker, release references
destroy.define(Worker, function(worker, reason) {
detach(worker, reason);
modelFor(worker).inited = true;
})
// unload Loaders used for creating WorkerChild instances in each process
when(() => ppmm.broadcastAsyncMessage('sdk/loader/unload', { data: ADDON }));

View File

@ -7,7 +7,8 @@ module.metadata = {
"stability": "stable",
"engines": {
// TODO Fennec support Bug 788334
"Firefox": "*"
"Firefox": "*",
"SeaMonkey": "*"
}
};

View File

@ -8,7 +8,8 @@ module.metadata = {
};
var getPrototypeOf = Object.getPrototypeOf;
var getNames = Object.getOwnPropertyNames;
var getNames = x => [...Object.getOwnPropertyNames(x),
...Object.getOwnPropertySymbols(x)];
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var create = Object.create;
var freeze = Object.freeze;

View File

@ -58,9 +58,9 @@ let promised = (function() {
promise.then(console.log) // => [ 1, 2, 3 ]
**/
return function promised() {
return function promised(...args) {
// create array of [ f, this, args... ]
return concat.apply([ f, this ], arguments).
return [f, this, ...args].
// reduce it via `promisedConcat` to get promised array of fulfillments
reduce(promisedConcat, resolve([], prototype)).
// finally map that to promise of `f.apply(this, args...)`

View File

@ -8,6 +8,10 @@ module.metadata = {
"stability": "deprecated"
};
const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x),
...Object.getOwnPropertySymbols(x)];
// `var` is being used in the module in order to make it reusable in
// environments in which `let` and `const` is not yet supported.
@ -46,7 +50,7 @@ function createAliasProperty(object, name) {
descriptor.get = property.get.bind(object);
if ("set" in property && property.set)
descriptor.set = property.set.bind(object);
// If original property was a value property.
if ("value" in property) {
// If original property is a method using it's `object` bounded copy.
@ -104,8 +108,8 @@ exports.Cortex = function Cortex(object, names, prototype) {
// properties of the original `object` that are contained in `names` array.
// If `names` array is not provided then all the properties that don't
// start with `"_"` are aliased.
Object.getOwnPropertyNames(object).forEach(function (name) {
if ((!names && "_" !== name.charAt(0)) || (names && ~names.indexOf(name)))
getOwnIdentifiers(object).forEach(function (name) {
if ((!names && "_" !== name.toString().charAt(0)) || (names && ~names.indexOf(name)))
defineAlias(object, cortex, name);
});
return cortex;

View File

@ -8,7 +8,6 @@ module.metadata = {
};
const { Trait } = require('../deprecated/traits');
const { iteratorSymbol } = require('../util/iteration');
/**
* @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/util_list
@ -116,7 +115,7 @@ const listOptions = {
yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
},
};
listOptions[iteratorSymbol] = function* iterator() {
listOptions[Symbol.iterator] = function* iterator() {
let array = this._keyValueMap.slice(0);
for (let element of array)

View File

@ -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 = {
@ -17,9 +16,10 @@ const {
required,
} = require('./traits/core');
const defineProperties = Object.defineProperties,
freeze = Object.freeze,
create = Object.create;
const { getOwnPropertyIdentifiers } = require('../util/object');
const defineProperties = Object.defineProperties;
const freeze = Object.freeze;
const create = Object.create;
/**
* Work around bug 608959 by defining the _create function here instead of
@ -31,7 +31,7 @@ const defineProperties = Object.defineProperties,
*/
function _create(proto, trait) {
let properties = {},
keys = Object.getOwnPropertyNames(trait);
keys = getOwnPropertyIdentifiers(trait);
for (let key of keys) {
let descriptor = trait[key];
if (descriptor.required &&
@ -72,9 +72,9 @@ function TraitDescriptor(object)
function Public(instance, trait) {
let result = {},
keys = Object.getOwnPropertyNames(trait);
keys = getOwnPropertyIdentifiers(trait);
for (let key of keys) {
if ('_' === key.charAt(0) && '__iterator__' !== key )
if (typeof key === 'string' && '_' === key.charAt(0) && '__iterator__' !== key )
continue;
let property = trait[key],
descriptor = {
@ -184,4 +184,3 @@ const Trait = Composition({
});
TraitProto = Trait.prototype;
exports.Trait = Trait;

View File

@ -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 = {
@ -10,11 +9,10 @@ module.metadata = {
// Design inspired by: http://www.traitsjs.org/
// shortcuts
const getOwnPropertyNames = Object.getOwnPropertyNames,
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
hasOwn = Object.prototype.hasOwnProperty,
_create = Object.create;
const { getOwnPropertyIdentifiers } = require('../../util/object');
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const hasOwn = Object.prototype.hasOwnProperty;
const _create = Object.create;
function doPropertiesMatch(object1, object2, name) {
// If `object1` has property with the given `name`
@ -114,12 +112,12 @@ function Conflict(name) {
*/
function trait(properties) {
let result = {},
keys = getOwnPropertyNames(properties);
for (let key of keys) {
let descriptor = getOwnPropertyDescriptor(properties, key);
result[key] = (required === descriptor.value) ? Required(key) : descriptor;
}
return result;
keys = getOwnPropertyIdentifiers(properties);
for (let key of keys) {
let descriptor = getOwnPropertyDescriptor(properties, key);
result[key] = (required === descriptor.value) ? Required(key) : descriptor;
}
return result;
}
exports.Trait = exports.trait = trait;
@ -140,7 +138,7 @@ function compose(trait1, trait2) {
let traits = Array.slice(arguments, 0),
result = {};
for (let trait of traits) {
let keys = getOwnPropertyNames(trait);
let keys = getOwnPropertyIdentifiers(trait);
for (let key of keys) {
let descriptor = trait[key];
// if property already exists and it's not a requirement
@ -174,7 +172,7 @@ function exclude(keys, trait) {
let exclusions = Map(keys),
result = {};
keys = getOwnPropertyNames(trait);
keys = getOwnPropertyIdentifiers(trait);
for (let key of keys) {
if (!hasOwn.call(exclusions, key) || trait[key].required)
@ -210,7 +208,7 @@ function override() {
let traits = Array.slice(arguments, 0),
result = {};
for (let trait of traits) {
let keys = getOwnPropertyNames(trait);
let keys = getOwnPropertyIdentifiers(trait);
for (let key of keys) {
let descriptor = trait[key];
if (!hasOwn.call(result, key) || result[key].required)
@ -236,7 +234,7 @@ exports.override = override;
*/
function rename(map, trait) {
let result = {},
keys = getOwnPropertyNames(trait);
keys = getOwnPropertyIdentifiers(trait);
for (let key of keys) {
// must be renamed & it's not requirement
if (hasOwn.call(map, key) && !trait[key].required) {
@ -281,7 +279,7 @@ function rename(map, trait) {
function resolve(resolutions, trait) {
let renames = {},
exclusions = [],
keys = getOwnPropertyNames(resolutions);
keys = getOwnPropertyIdentifiers(resolutions);
for (let key of keys) { // pre-process renamed and excluded properties
if (resolutions[key]) // old name -> new name
renames[key] = resolutions[key];
@ -306,7 +304,7 @@ exports.resolve = resolve;
*/
function create(proto, trait) {
let properties = {},
keys = getOwnPropertyNames(trait);
keys = getOwnPropertyIdentifiers(trait);
for (let key of keys) {
let descriptor = trait[key];
if (descriptor.required && !hasOwn.call(proto, key))
@ -319,4 +317,3 @@ function create(proto, trait) {
return _create(proto, properties);
}
exports.create = create;

View File

@ -26,7 +26,8 @@ const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}
var ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
const TEST_REGEX = /(([^\/]+\/)(?:lib\/)?)?(tests?\/test-[^\.\/]+)\.js$/;
const CFX_TEST_REGEX = /(([^\/]+\/)(?:lib\/)?)?(tests?\/test-[^\.\/]+)\.js$/;
const JPM_TEST_REGEX = /^()(tests?\/test-[^\.\/]+)\.js$/;
const { mapcat, map, filter, fromEnumerator } = require("sdk/util/sequence");
@ -51,6 +52,8 @@ const removeDups = (array) => array.reduce((result, value) => {
}, []);
const getSuites = function getSuites({ id, filter }) {
const TEST_REGEX = isNative ? JPM_TEST_REGEX : CFX_TEST_REGEX;
return getAddon(id).then(addon => {
let fileURI = addon.getResourceURI("tests/");
let isPacked = fileURI.scheme == "jar";
@ -77,9 +80,13 @@ const getSuites = function getSuites({ id, filter }) {
suites = removeDups(suites.sort());
return suites;
})
} else {
let tests = getTestEntries(file);
[...tests].forEach(addEntry);
}
else {
let tests = [...getTestEntries(file)];
let rootURI = addon.getResourceURI("/");
tests.forEach((entry) => {
addEntry(entry.replace(rootURI.spec, ""));
});
}
// sort and remove dups
@ -102,7 +109,8 @@ const makeFilters = function makeFilters(options) {
if (colonPos === -1) {
filterFileRegex = new RegExp(options.filter);
filterNameRegex = { test: () => true }
} else {
}
else {
filterFileRegex = new RegExp(options.filter.substr(0, colonPos));
filterNameRegex = new RegExp(options.filter.substr(colonPos + 1));
}

View File

@ -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 = {
@ -80,14 +79,16 @@ exports.once = once;
* Arguments that will be passed to listeners.
*/
function emit (target, type, ...args) {
let all = observers(target, '*').length;
let state = observers(target, type);
let listeners = state.slice();
let count = listeners.length;
let index = 0;
// If error event and there are no handlers then print error message
// into a console.
if (count === 0 && type === 'error') console.exception(args[0]);
// If error event and there are no handlers (explicit or catch-all)
// then print error message to the console.
if (count === 0 && type === 'error' && all === 0)
console.exception(args[0]);
while (index < count) {
try {
let listener = listeners[index];

View File

@ -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 = {
@ -68,9 +67,8 @@ const EventTarget = Class({
off(this, type, listener);
return this;
},
off: function(type, listener) {
off(this, type, listener);
return this;
}
// but we can wrap `off` here, as the semantics are the same
off: chainable(method(off))
});
exports.EventTarget = EventTarget;

View File

@ -7,15 +7,14 @@ module.metadata = {
"stability": "unstable"
};
const { Ci, Cu } = require("chrome");
const { Ci } = require("chrome");
const events = require("../system/events");
const core = require("./core");
const { loadSheet, removeSheet } = require("../stylesheet/utils");
const assetsURI = require('../self').data.url();
const { Services } = Cu.import("resource://gre/modules/Services.jsm");
const hideContentStyle = "data:text/css,:root {visibility: hidden !important;}";
const hideSheetUri = Services.io.newURI(hideContentStyle, null, null);
const hideSheetUri = "data:text/css,:root {visibility: hidden !important;}";
// Taken from Gaia:
// https://github.com/andreasgal/gaia/blob/04fde2640a7f40314643016a5a6c98bf3755f5fd/webapi.js#L1470
@ -46,11 +45,8 @@ function onDocumentReady2Translate(event) {
try {
// Finally display document when we finished replacing all text content
if (document.defaultView) {
let winUtils = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
winUtils.removeSheet(hideSheetUri, winUtils.USER_SHEET);
}
if (document.defaultView)
removeSheet(document.defaultView, hideSheetUri, 'user');
}
catch(e) {
console.exception(e);
@ -76,9 +72,7 @@ function onContentWindow(event) {
try {
// First hide content of the document in order to have content blinking
// between untranslated and translated states
let winUtils = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
winUtils.loadSheet(hideSheetUri, winUtils.USER_SHEET);
loadSheet(document.defaultView, hideSheetUri, 'user');
}
catch(e) {
console.exception(e);

View File

@ -14,11 +14,10 @@ const { getAttachEventType, WorkerHost } = require('./content/utils');
const { Class } = require('./core/heritage');
const { Disposable } = require('./core/disposable');
const { WeakReference } = require('./core/reference');
const { Worker } = require('./content/worker');
const { Worker } = require('./content/worker-parent');
const { EventTarget } = require('./event/target');
const { on, emit, once, setListeners } = require('./event/core');
const { on: domOn, removeListener: domOff } = require('./dom/events');
const { pipe } = require('./event/utils');
const { isRegExp, isUndefined } = require('./lang/type');
const { merge } = require('./util/object');
const { windowIterator } = require('./deprecated/window-utils');
@ -114,7 +113,6 @@ const PageMod = Class({
modContract.properties(modelFor),
EventTarget,
Disposable,
WeakReference
],
extends: WorkerHost(workerFor),
setup: function PageMod(options) {
@ -213,11 +211,15 @@ function createWorker (mod, window) {
onError: (e) => emit(mod, 'error', e)
});
workers.set(mod, worker);
pipe(worker, mod);
emit(mod, 'attach', worker);
once(worker, 'detach', function detach() {
worker.destroy();
});
worker.on('*', (event, ...args) => {
// worker's "attach" event passes a window as the argument
// page-mod's "attach" event needs a worker
if (event === 'attach')
emit(mod, event, worker)
else
emit(mod, event, ...args);
})
once(worker, 'detach', () => worker.destroy());
}
function onContent (mod, window) {

View File

@ -3,12 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// The panel module currently supports only Firefox.
// The panel module currently supports only Firefox and SeaMonkey.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
module.metadata = {
"stability": "stable",
"engines": {
"Firefox": "*"
"Firefox": "*",
"SeaMonkey": "*"
}
};

View File

@ -7,7 +7,8 @@
module.metadata = {
"stability": "stable",
"engines": {
"Firefox": "*"
"Firefox": "*",
"SeaMonkey": "*"
}
};
@ -22,8 +23,7 @@ const { Ci, Cc } = require("chrome"),
{ getTabs, getTabContentWindow, getTabForContentWindow,
getAllTabContentWindows } = require('./tabs/utils'),
winUtils = require("./window/utils"),
events = require("./system/events"),
{ iteratorSymbol, forInIterator } = require("./util/iteration");
events = require("./system/events");
// The selection types
const HTML = 0x01,
@ -116,9 +116,12 @@ function* forOfIterator() {
}
const selectionIteratorOptions = {
__iterator__: forInIterator
__iterator__: function() {
for (let item of this)
yield item;
}
}
selectionIteratorOptions[iteratorSymbol] = forOfIterator;
selectionIteratorOptions[Symbol.iterator] = forOfIterator;
const selectionIterator = obscure(selectionIteratorOptions);
/**

View File

@ -30,13 +30,18 @@ const isPacked = rootURI && rootURI.indexOf("jar:") === 0;
const uri = (path="") =>
path.contains(":") ? path : addonDataURI + path.replace(/^\.\//, "");
let { preferencesBranch } = options;
if (/[^\w{@}.-]/.test(preferencesBranch)) {
preferencesBranch = id;
console.warn("Ignoring preferences-branch (not a valid branch name)");
}
// Some XPCOM APIs require valid URIs as an argument for certain operations
// (see `nsILoginManager` for example). This property represents add-on
// associated unique URI string that can be used for that.
exports.uri = 'addon:' + id;
exports.id = id;
exports.preferencesBranch = options.preferencesBranch || id;
exports.preferencesBranch = preferencesBranch || id;
exports.name = name;
exports.loadReason = loadReason;
exports.version = version;

View File

@ -1,17 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cc, Ci } = require("chrome");
const io = Cc['@mozilla.org/network/io-service;1'].
getService(Ci.nsIIOService);
const { Ci } = require("chrome");
const SHEET_TYPE = {
"agent": "AGENT_SHEET",
@ -36,12 +32,12 @@ function loadSheet(window, url, type) {
type = SHEET_TYPE[type];
if (!(url instanceof Ci.nsIURI))
url = io.newURI(url, null, null);
if (url instanceof Ci.nsIURI)
url = url.spec;
let winUtils = getDOMWindowUtils(window);
try {
winUtils.loadSheet(url, winUtils[type]);
winUtils.loadSheetUsingURIString(url, winUtils[type]);
}
catch (e) {};
};
@ -57,13 +53,13 @@ function removeSheet(window, url, type) {
type = SHEET_TYPE[type];
if (!(url instanceof Ci.nsIURI))
url = io.newURI(url, null, null);
if (url instanceof Ci.nsIURI)
url = url.spec;
let winUtils = getDOMWindowUtils(window);
try {
winUtils.removeSheet(url, winUtils[type]);
winUtils.removeSheetUsingURIString(url, winUtils[type]);
}
catch (e) {};
};

View File

@ -14,6 +14,7 @@ const { emit } = require('../event/core');
const { isPrivate } = require('../private-browsing/utils');
const { isWindowPrivate } = require('../window/utils');
const { when: unload } = require('../system/unload');
const { BLANK } = require('../content/thumbnail');
const { viewFor } = require('../view/core');
const { EVENTS } = require('./events');
@ -94,7 +95,7 @@ const Tab = Class({
console.error(ERR_FENNEC_MSG);
// return 80x45 blank default
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAAJElEQVRoge3BAQEAAACCIP+vbkhAAQAAAAAAAAAAAAAAAADXBjhtAAGQ0AF/AAAAAElFTkSuQmCC';
return BLANK;
},
/**

View File

@ -9,7 +9,7 @@ const { defer } = require("../lang/functional");
const { has } = require("../util/array");
const { each } = require("../util/object");
const { EVENTS } = require("./events");
const { getThumbnailURIForWindow } = require("../content/thumbnail");
const { getThumbnailURIForWindow, BLANK } = require("../content/thumbnail");
const { getFaviconURIForLocation } = require("../io/data");
const { activateTab, getOwnerWindow, getBrowserForTab, getTabTitle,
setTabTitle, getTabContentDocument, getTabURL, setTabURL,
@ -199,8 +199,15 @@ const TabTrait = Trait.compose(EventEmitter, {
* Thumbnail data URI of the page currently loaded in this tab.
* @type {String}
*/
getThumbnail: function getThumbnail()
this._tab ? getThumbnailURIForWindow(this._contentWindow) : undefined,
getThumbnail() {
if (!this._tab)
return undefined;
if (this._tab.getAttribute('remote')) {
console.error('This method is not supported with E10S');
return BLANK;
}
return getThumbnailURIForWindow(this._contentWindow);
},
/**
* Whether or not tab is pinned (Is an app-tab).
* @type {Boolean}

View File

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const ContentWorker = require('../content/worker').Worker;
const ContentWorker = require('../content/worker-parent').Worker;
function Worker(options, window) {
options.window = window;

View File

@ -108,4 +108,5 @@ on(actionButtonStateEvents, 'data', ({target, window, state}) => {
view.setIcon(id, window, state.icon);
view.setLabel(id, window, state.label);
view.setDisabled(id, window, state.disabled);
view.setBadge(id, window, state.badge, state.badgeColor);
});

View File

@ -6,14 +6,13 @@
const { contract } = require('../../util/contract');
const { isLocalURL } = require('../../url');
const { isNil, isObject, isString } = require('../../lang/type');
const { required, either, string, boolean, object } = require('../../deprecated/api-utils');
const { required, either, string, boolean, object, number } = require('../../deprecated/api-utils');
const { merge } = require('../../util/object');
const { freeze } = Object;
function isIconSet(icons) {
return Object.keys(icons).
every(size => String(size >>> 0) === size && isLocalURL(icons[size]))
}
const isIconSet = (icons) =>
Object.keys(icons).
every(size => String(size >>> 0) === size && isLocalURL(icons[size]));
let iconSet = {
is: either(object, string),
@ -36,10 +35,22 @@ let label = {
msg: 'The option "label" must be a non empty string'
}
let badge = {
is: either(string, number),
msg: 'The option "badge" must be a string or a number'
}
let badgeColor = {
is: string,
msg: 'The option "badgeColor" must be a string'
}
let stateContract = contract({
label: label,
icon: iconSet,
disabled: boolean
disabled: boolean,
badge: badge,
badgeColor: badgeColor
});
exports.stateContract = stateContract;

View File

@ -100,6 +100,7 @@ on(toggleButtonStateEvents, 'data', ({target, window, state}) => {
view.setLabel(id, window, state.label);
view.setDisabled(id, window, state.disabled);
view.setChecked(id, window, state.checked);
view.setBadge(id, window, state.badge, state.badgeColor);
});
on(clickEvents, 'data', ({target: id, window, checked }) => {

View File

@ -15,7 +15,7 @@ const { on, off, emit } = require('../../event/core');
const { data } = require('sdk/self');
const { isObject } = require('../../lang/type');
const { isObject, isNil } = require('../../lang/type');
const { getMostRecentBrowserWindow } = require('../../window/utils');
const { ignoreWindow } = require('../../private-browsing/utils');
@ -114,7 +114,7 @@ function nodeFor(id, window=getMostRecentBrowserWindow()) {
exports.nodeFor = nodeFor;
function create(options) {
let { id, label, icon, type } = options;
let { id, label, icon, type, badge } = options;
if (views.has(id))
throw new Error('The ID "' + id + '" seems already used.');
@ -137,7 +137,7 @@ function create(options) {
node.style.display = 'none';
node.setAttribute('id', this.id);
node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional');
node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional badged-button');
node.setAttribute('type', type);
node.setAttribute('label', label);
node.setAttribute('tooltiptext', label);
@ -213,6 +213,27 @@ function setChecked(id, window, checked) {
}
exports.setChecked = setChecked;
function setBadge(id, window, badge, color) {
let node = nodeFor(id, window);
if (node) {
// `Array.from` is needed to handle unicode symbol properly:
// '𝐀𝐁'.length is 4 where Array.from('𝐀𝐁').length is 2
let text = isNil(badge)
? ''
: Array.from(String(badge)).slice(0, 4).join('');
node.setAttribute('badge', text);
let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
'class', 'toolbarbutton-badge');
if (badgeNode)
badgeNode.style.backgroundColor = isNil(color) ? '' : color;
}
}
exports.setBadge = setBadge;
function click(id) {
let node = nodeFor(id);

View File

@ -1,24 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.metadata = {
"stability": "experimental"
};
// This is known as @@iterator in the ES6 spec. In builds that have ES6
// Symbols, use Symbol.iterator; otherwise use the legacy method name,
// "@@iterator".
const JS_HAS_SYMBOLS = typeof Symbol === "function";
exports.iteratorSymbol = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
// An adaptor that, given an object that is iterable with for-of, is
// suitable for being bound to __iterator__ in order to make the object
// iterable in the same way via for-in.
function forInIterator() {
for (let item of this)
yield item;
}
exports.forInIterator = forInIterator;

View File

@ -9,7 +9,6 @@ module.metadata = {
const { Class } = require('../core/heritage');
const listNS = require('../core/namespace').ns();
const { iteratorSymbol } = require('../util/iteration');
const listOptions = {
/**
@ -48,8 +47,8 @@ const listOptions = {
yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
},
};
listOptions[iteratorSymbol] = function iterator() {
return listNS(this).keyValueMap.slice(0)[iteratorSymbol]();
listOptions[Symbol.iterator] = function iterator() {
return listNS(this).keyValueMap.slice(0)[Symbol.iterator]();
};
const List = Class(listOptions);
exports.List = List;

View File

@ -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 = {
@ -36,7 +35,7 @@ function merge(source) {
// converted to `true` where `null` and `undefined` becames `false`. Therefore
// the `filter` method will keep only objects that are defined and not null.
Array.slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
Object.getOwnPropertyNames(properties).forEach(function(name) {
getOwnPropertyIdentifiers(properties).forEach(function(name) {
descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
});
});
@ -85,8 +84,19 @@ function omit(source, ...values) {
let copy = {};
let keys = flatten(values);
for (let prop in source)
if (!~keys.indexOf(prop))
if (!~keys.indexOf(prop))
copy[prop] = source[prop];
return copy;
}
exports.omit = omit;
// get object's own property Symbols and/or Names, including nonEnumerables by default
function getOwnPropertyIdentifiers(object, options = { names: true, symbols: true, nonEnumerables: true }) {
const symbols = !options.symbols ? [] :
Object.getOwnPropertySymbols(object);
const names = !options.names ? [] :
options.nonEnumerables ? Object.getOwnPropertyNames(object) :
Object.keys(object);
return [...names, ...symbols];
}
exports.getOwnPropertyIdentifiers = getOwnPropertyIdentifiers;

View File

@ -22,13 +22,12 @@ module.metadata = {
// - `_` used for argument(s) or variable(s) who's values are ignored.
const { complement, flip, identity } = require("../lang/functional");
const { iteratorSymbol } = require("../util/iteration");
const { isArray, isArguments, isMap, isSet,
isString, isBoolean, isNumber } = require("../lang/type");
const Sequence = function Sequence(iterator) {
if (iterator.isGenerator && iterator.isGenerator())
this[iteratorSymbol] = iterator;
this[Symbol.iterator] = iterator;
else
throw TypeError("Expected generator argument");
};
@ -216,7 +215,7 @@ const map = (f, ...sequences) => seq(function* () {
let index = 0;
while (index < count) {
inputs[index] = sequences[index][iteratorSymbol]();
inputs[index] = sequences[index][Symbol.iterator]();
index = index + 1;
}

View File

@ -59,8 +59,11 @@ const WindowLoader = Trait.compose({
if (window !== _window) {
if (_window) {
_window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
_window.removeEventListener(ON_LOAD, this.__loadListener, false);
if (this.__unloadListener)
_window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
if (this.__loadListener)
_window.removeEventListener(ON_LOAD, this.__loadListener, false);
}
if (window) {
@ -123,4 +126,3 @@ const WindowLoader = Trait.compose({
__unloadListener: null
});
exports.WindowLoader = WindowLoader;

View File

@ -45,12 +45,13 @@ const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules
// Define some shortcuts.
const bind = Function.call.bind(Function.bind);
const getOwnPropertyNames = Object.getOwnPropertyNames;
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const define = Object.defineProperties;
const prototypeOf = Object.getPrototypeOf;
const create = Object.create;
const keys = Object.keys;
const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x),
...Object.getOwnPropertySymbols(x)];
const NODE_MODULES = ["assert", "buffer_ieee754", "buffer", "child_process", "cluster", "console", "constants", "crypto", "_debugger", "dgram", "dns", "domain", "events", "freelist", "fs", "http", "https", "_linklist", "module", "net", "os", "path", "punycode", "querystring", "readline", "repl", "stream", "string_decoder", "sys", "timers", "tls", "tty", "url", "util", "vm", "zlib"];
@ -84,7 +85,7 @@ function freeze(object) {
// Returns map of given `object`-s own property descriptors.
const descriptor = iced(function descriptor(object) {
let value = {};
getOwnPropertyNames(object).forEach(function(name) {
getOwnIdentifiers(object).forEach(function(name) {
value[name] = getOwnPropertyDescriptor(object, name)
});
return value;
@ -121,7 +122,7 @@ function iced(f) {
const override = iced(function override(target, source) {
let properties = descriptor(target)
let extension = descriptor(source || {})
getOwnPropertyNames(extension).forEach(function(name) {
getOwnIdentifiers(extension).forEach(function(name) {
properties[name] = extension[name];
});
return define({}, properties);
@ -294,7 +295,7 @@ const load = iced(function load(loader, module) {
// the scope object for this particular module
sandbox = new loader.sharedGlobalSandbox.Object();
// Inject all expected globals in the scope object
getOwnPropertyNames(globals).forEach(function(name) {
getOwnIdentifiers(globals).forEach(function(name) {
descriptors[name] = getOwnPropertyDescriptor(globals, name)
});
define(sandbox, descriptors);
@ -521,7 +522,7 @@ const resolveURI = iced(function resolveURI(id, mapping) {
let count = mapping.length, index = 0;
// Do not resolve if already a resource URI
if (isResourceURI(id)) return normalizeExt(id);
if (isAbsoluteURI(id)) return normalizeExt(id);
while (index < count) {
let [ path, uri ] = mapping[index ++];
@ -844,7 +845,9 @@ exports.Loader = Loader;
let isJSONURI = uri => uri.substr(-5) === '.json';
let isJSMURI = uri => uri.substr(-4) === '.jsm';
let isJSURI = uri => uri.substr(-3) === '.js';
let isResourceURI = uri => uri.substr(0, 11) === 'resource://';
let isAbsoluteURI = uri => uri.indexOf("resource://") >= 0 ||
uri.indexOf("chrome://") >= 0 ||
uri.indexOf("file://") >= 0
let isRelative = id => id[0] === '.'
const generateMap = iced(function generateMap(options, callback) {
@ -928,7 +931,7 @@ function findAllModuleIncludes (uri, options, results, callback) {
// Given a resource URI or source, return an array of strings passed into
// the require statements from the source
function findModuleIncludes (uri, callback) {
let src = isResourceURI(uri) ? readURI(uri) : uri;
let src = isAbsoluteURI(uri) ? readURI(uri) : uri;
let modules = [];
walk(src, function (node) {
@ -979,4 +982,3 @@ function isRequire (node) {
}
});

View File

@ -0,0 +1,54 @@
const make = (exports, rootURI, components) => {
const { Loader: { Loader, Require, Module, main } } =
components.utils.import(rootURI + "toolkit/loader.js", {});
const loader = Loader({
id: "toolkit/require",
rootURI: rootURI,
isNative: true,
paths: {
"": rootURI,
"devtools/": "resource://gre/modules/devtools/"
}
});
// Below we define `require` & `require.resolve` that resolve passed
// module id relative to the caller URI. This is not perfect but good
// enough for common case & there is always an option to pass absolute
// id when that
// but presumably well enough to cover
const require = id => {
const requirerURI = components.stack.caller.filename;
const requirer = Module(requirerURI, requirerURI);
return Require(loader, requirer)(id);
};
require.resolve = id => {
const requirerURI = components.stack.caller.filename;
const requirer = Module(requirerURI, requirerURI);
return Require(loader, requirer).resolve(id);
};
exports.require = require;
}
// If loaded in the context of commonjs module, reload as JSM into an
// exports object.
if (typeof(require) === "function" && typeof(module) === "object") {
require("chrome").Cu.import(module.uri, module.exports);
}
// If loaded in the context of JSM make a loader & require and define
// new symbols as exported ones.
else if (typeof(__URI__) === "string" && this["Components"]) {
const builtin = Object.keys(this);
const uri = __URI__.replace("toolkit/require.js", "");
make(this, uri, this["Components"]);
this.EXPORTED_SYMBOLS = Object.
keys(this).
filter($ => builtin.indexOf($) < 0);
}
else {
throw Error("Loading require.js in this environment isn't supported")
}

View File

@ -401,12 +401,7 @@ def generate_build_for_target(pkg_cfg, target, deps,
build['preferencesBranch'] = jid
if 'preferences-branch' in target_cfg:
# check it's a non-empty, valid branch name
preferencesBranch = target_cfg['preferences-branch']
if re.match('^[\w{@}-]+$', preferencesBranch):
build['preferencesBranch'] = preferencesBranch
elif not is_running_tests:
print >>sys.stderr, "IGNORING preferences-branch (not a valid branch name)"
build['preferencesBranch'] = target_cfg['preferences-branch']
return build

View File

@ -34,6 +34,9 @@ DEFAULT_COMMON_PREFS = {
# Allow installing extensions dropped into the profile folder
'extensions.autoDisableScopes' : 10,
# shut up some warnings on `about:` page
'app.releaseNotesURL': 'http://localhost/app-dummy/',
'app.vendorURL': 'http://localhost/app-dummy/'
}
DEFAULT_NO_CONNECTIONS_PREFS = {

View File

@ -17,11 +17,6 @@ exports.testCurlyID = function(assert) {
assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), simple.prefs.test14, 'simple test14 also 15');
}
exports.testInvalidPreferencesBranch = function(assert) {
assert.notEqual(preferencesBranch, 'invalid^branch*name', 'invalid preferences-branch value ignored');
assert.equal(preferencesBranch, '{34a1eae1-c20a-464f-9b0e-000000000000}', 'preferences-branch is {34a1eae1-c20a-464f-9b0e-000000000000}');
}
// from `/test/test-self.js`, adapted to `sdk/test/assert` API
exports.testSelfID = function*(assert) {
assert.equal(typeof(id), 'string', 'self.id is a string');

View File

@ -8,7 +8,5 @@
"type": "integer",
"title": "test13",
"value": 26
}],
"preferences-branch": "invalid^branch*name"
}]
}

View File

@ -19,11 +19,6 @@ exports.testStandardID = function(assert) {
assert.equal(service.get('extensions.standard-id@jetpack.test14'), simple.prefs.test14, 'simple test14 also 15');
}
exports.testInvalidPreferencesBranch = function(assert) {
assert.notEqual(preferencesBranch, 'invalid^branch*name', 'invalid preferences-branch value ignored');
assert.equal(preferencesBranch, 'standard-id@jetpack', 'preferences-branch is standard-id@jetpack');
}
// from `/test/test-self.js`, adapted to `sdk/test/assert` API
exports.testSelfID = function*(assert) {
assert.equal(typeof(id), 'string', 'self.id is a string');

View File

@ -8,7 +8,5 @@
"type": "integer",
"title": "test13",
"value": 26
}],
"preferences-branch": "invalid^branch*name"
}]
}

View File

@ -0,0 +1 @@
div { border-style: dashed; }

View File

@ -0,0 +1,5 @@
/* 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/. */
self.postMessage("msg from contentScriptFile");

View File

@ -0,0 +1,13 @@
<!-- 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/. -->
<html>
<head>
<meta charset="UTF-8">
<title>foo</title>
</head>
<body>
<p>bar</p>
</body>
</html>

View File

@ -37,6 +37,7 @@ support-files =
[test-content-script.js]
[test-content-symbiont.js]
[test-content-worker.js]
[test-content-worker-parent.js]
[test-context-menu.js]
[test-cortex.js]
[test-cuddlefish.js]

View File

@ -1,45 +1,31 @@
/* 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 {Cc,Ci} = require("chrome");
const timer = require("sdk/timers");
const xulApp = require("sdk/system/xul-app");
const { Cc, Ci } = require("chrome");
const { setTimeout } = require("sdk/timers");
const { Loader } = require("sdk/test/loader");
const { openTab, getBrowserForTab, closeTab } = require("sdk/tabs/utils");
const self = require("sdk/self");
const { merge } = require("sdk/util/object");
const httpd = require("./lib/httpd");
/**
* A helper function that creates a PageMod, then opens the specified URL
* and checks the effect of the page mod on 'onload' event via testCallback.
*/
const PORT = 8099;
const PATH = '/test-contentScriptWhen.html';
// an evil function enables the creation of tests
// that depend on delicate event timing. do not use.
exports.testPageMod = function testPageMod(assert, done, testURL, pageModOptions,
testCallback, timeout) {
if (!xulApp.versionInRange(xulApp.platformVersion, "1.9.3a3", "*") &&
!xulApp.versionInRange(xulApp.platformVersion, "1.9.2.7", "1.9.2.*")) {
assert.pass("Note: not testing PageMod, as it doesn't work on this platform version");
return null;
}
var wm = Cc['@mozilla.org/appshell/window-mediator;1']
.getService(Ci.nsIWindowMediator);
var browserWindow = wm.getMostRecentWindow("navigator:browser");
if (!browserWindow) {
assert.pass("page-mod tests: could not find the browser window, so " +
"will not run. Use -a firefox to run the pagemod tests.")
return null;
}
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, require("./fixtures"))
})
}
});
let options = merge({}, require('@loader/options'),
{ prefixURI: require('./fixtures').url() });
let loader = Loader(module, null, options);
let pageMod = loader.require("sdk/page-mod");
var pageMods = [new pageMod.PageMod(opts) for each(opts in pageModOptions)];
@ -55,7 +41,7 @@ exports.testPageMod = function testPageMod(assert, done, testURL, pageModOptions
// load event. So page-mod actions may not be already done.
// If we delay even more contentScriptWhen:'end', we may want to modify
// this code again.
timer.setTimeout(testCallback, 0,
setTimeout(testCallback, timeout,
b.contentWindow.wrappedJSObject,
function () {
pageMods.forEach(function(mod) mod.destroy());
@ -76,7 +62,8 @@ exports.testPageMod = function testPageMod(assert, done, testURL, pageModOptions
* based on the value of document.readyState at the time contentScript is attached
*/
exports.handleReadyState = function(url, contentScriptWhen, callbacks) {
const { PageMod } = Loader(module).require('sdk/page-mod');
const loader = Loader(module);
const { PageMod } = loader.require('sdk/page-mod');
let pagemod = PageMod({
include: url,
@ -86,13 +73,39 @@ exports.handleReadyState = function(url, contentScriptWhen, callbacks) {
onAttach: worker => {
let { tab } = worker;
worker.on('message', readyState => {
pagemod.destroy();
// generate event name from `readyState`, e.g. `"loading"` becomes `onLoading`.
let type = 'on' + readyState[0].toUpperCase() + readyState.substr(1);
if (type in callbacks)
callbacks[type](tab);
pagemod.destroy();
loader.unload();
})
}
});
}
// serves a slow page which takes 1.5 seconds to load,
// 0.5 seconds in each readyState: uninitialized, loading, interactive.
exports.contentScriptWhenServer = function() {
const URL = 'http://localhost:' + PORT + PATH;
const HTML = `/* polyglot js
<script src="${URL}"></script>
delay both the "DOMContentLoaded"
<script async src="${URL}"></script>
and "load" events */`;
let srv = httpd.startServerAsync(PORT);
srv.registerPathHandler(PATH, (_, response) => {
response.processAsync();
response.setHeader('Content-Type', 'text/html', false);
setTimeout(_ => response.finish(), 500);
response.write(HTML);
})
srv.URL = URL;
return srv;
}

View File

@ -9,7 +9,8 @@ const base64 = require("sdk/base64");
const text = "Awesome!";
const b64text = "QXdlc29tZSE=";
const utf8text = "✓ à la mode";
const utf8text = "\u2713 à la mode";
const badutf8text = "\u0013 à la mode";
const b64utf8text = "4pyTIMOgIGxhIG1vZGU=";
exports["test base64.encode"] = function (assert) {
@ -66,8 +67,8 @@ exports["test base64.decode with wrong charset"] = function (assert) {
exports["test encode/decode Unicode without utf-8 as charset"] = function (assert) {
assert.notEqual(base64.decode(base64.encode(utf8text)), utf8text,
"Unicode strings needs 'utf-8' charset"
assert.equal(base64.decode(base64.encode(utf8text)), badutf8text,
"Unicode strings needs 'utf-8' charset or will be mangled"
);
}

View File

@ -557,7 +557,8 @@ exports["test Collections 2"] = createProxyTest(html, function (helper) {
for(let i in body.childNodes) {
count++;
}
assert(count == 6, "body.childNodes is iterable");
assert(count >= 3, "body.childNodes is iterable");
done();
}
);

View File

@ -0,0 +1,982 @@
/* 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";
// Skipping due to window creation being unsupported in Fennec
module.metadata = {
engines: {
'Firefox': '*'
}
};
const { Cc, Ci } = require("chrome");
const { on } = require("sdk/event/core");
const { setTimeout } = require("sdk/timers");
const { LoaderWithHookedConsole } = require("sdk/test/loader");
const { Worker } = require("sdk/content/worker-parent");
const { close } = require("sdk/window/helpers");
const { set: setPref } = require("sdk/preferences/service");
const { isArray } = require("sdk/lang/type");
const { URL } = require('sdk/url');
const fixtures = require("./fixtures");
const system = require("sdk/system/events");
const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo";
const WINDOW_SCRIPT_URL = "data:text/html;charset=utf-8," +
"<script>window.addEventListener('message', function (e) {" +
" if (e.data === 'from -> content-script')" +
" window.postMessage('from -> window', '*');" +
"});</script>";
function makeWindow() {
let content =
"<?xml version=\"1.0\"?>" +
"<window " +
"xmlns=\"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\">" +
"<script>var documentValue=true;</script>" +
"</window>";
var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," +
encodeURIComponent(content);
var features = ["chrome", "width=10", "height=10"];
return Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIWindowWatcher).
openWindow(null, url, null, features.join(","), null);
}
// Listen for only first one occurence of DOM event
function listenOnce(node, eventName, callback) {
node.addEventListener(eventName, function onevent(event) {
node.removeEventListener(eventName, onevent, true);
callback(node);
}, true);
}
// Load a given url in a given browser and fires the callback when it is loaded
function loadAndWait(browser, url, callback) {
listenOnce(browser, "load", callback);
// We have to wait before calling `loadURI` otherwise, if we call
// `loadAndWait` during browser load event, the history will be broken
setTimeout(function () {
browser.loadURI(url);
}, 0);
}
// Returns a test function that will automatically open a new chrome window
// with a <browser> element loaded on a given content URL
// The callback receive 3 arguments:
// - test: reference to the jetpack test object
// - browser: a reference to the <browser> xul node
// - done: a callback to call when test is over
function WorkerTest(url, callback) {
return function testFunction(assert, done) {
let chromeWindow = makeWindow();
chromeWindow.addEventListener("load", function onload() {
chromeWindow.removeEventListener("load", onload, true);
let browser = chromeWindow.document.createElement("browser");
browser.setAttribute("type", "content");
chromeWindow.document.documentElement.appendChild(browser);
// Wait for about:blank load event ...
listenOnce(browser, "load", function onAboutBlankLoad() {
// ... before loading the expected doc and waiting for its load event
loadAndWait(browser, url, function onDocumentLoaded() {
callback(assert, browser, function onTestDone() {
close(chromeWindow).then(done);
});
});
});
}, true);
};
}
exports["test:sample"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
assert.notEqual(browser.contentWindow.location.href, "about:blank",
"window is now on the right document");
let window = browser.contentWindow
let worker = Worker({
window: window,
contentScript: "new " + function WorkerScope() {
// window is accessible
let myLocation = window.location.toString();
self.on("message", function(data) {
if (data == "hi!")
self.postMessage("bye!");
});
},
contentScriptWhen: "ready",
onMessage: function(msg) {
assert.equal("bye!", msg);
assert.equal(worker.url, window.location.href,
"worker.url still works");
done();
}
});
assert.equal(worker.url, window.location.href,
"worker.url works");
assert.equal(worker.contentURL, window.location.href,
"worker.contentURL works");
worker.postMessage("hi!");
}
);
exports["test:emit"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
// Validate self.on and self.emit
self.port.on("addon-to-content", function (data) {
self.port.emit("content-to-addon", data);
});
// Check for global pollution
//if (typeof on != "undefined")
// self.postMessage("`on` is in globals");
if (typeof once != "undefined")
self.postMessage("`once` is in globals");
if (typeof emit != "undefined")
self.postMessage("`emit` is in globals");
},
onMessage: function(msg) {
assert.fail("Got an unexpected message : "+msg);
}
});
// Validate worker.port
worker.port.on("content-to-addon", function (data) {
assert.equal(data, "event data");
done();
});
worker.port.emit("addon-to-content", "event data");
}
);
exports["test:emit hack message"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
// Validate self.port
self.port.on("message", function (data) {
self.port.emit("message", data);
});
// We should not receive message on self, but only on self.port
self.on("message", function (data) {
self.postMessage("message", data);
});
},
onError: function(e) {
assert.fail("Got exception: "+e);
}
});
worker.port.on("message", function (data) {
assert.equal(data, "event data");
done();
});
worker.on("message", function (data) {
assert.fail("Got an unexpected message : "+msg);
});
worker.port.emit("message", "event data");
}
);
exports["test:n-arguments emit"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let repeat = 0;
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
// Validate self.on and self.emit
self.port.on("addon-to-content", function (a1, a2, a3) {
self.port.emit("content-to-addon", a1, a2, a3);
});
}
});
// Validate worker.port
worker.port.on("content-to-addon", function (arg1, arg2, arg3) {
if (!repeat++) {
this.emit("addon-to-content", "first argument", "second", "third");
} else {
assert.equal(arg1, "first argument");
assert.equal(arg2, "second");
assert.equal(arg3, "third");
done();
}
});
worker.port.emit("addon-to-content", "first argument", "second", "third");
}
);
exports["test:post-json-values-only"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
self.on("message", function (message) {
self.postMessage([ message.fun === undefined,
typeof message.w,
message.w && "port" in message.w,
message.w._url,
Array.isArray(message.array),
JSON.stringify(message.array)]);
});
}
});
// Validate worker.onMessage
let array = [1, 2, 3];
worker.on("message", function (message) {
assert.ok(message[0], "function becomes undefined");
assert.equal(message[1], "object", "object stays object");
assert.ok(message[2], "object's attributes are enumerable");
assert.equal(message[3], DEFAULT_CONTENT_URL,
"jsonable attributes are accessible");
// See bug 714891, Arrays may be broken over compartements:
assert.ok(message[4], "Array keeps being an array");
assert.equal(message[5], JSON.stringify(array),
"Array is correctly serialized");
done();
});
// Add a new url property sa the Class function used by
// Worker doesn't set enumerables to true for non-functions
worker._url = DEFAULT_CONTENT_URL;
worker.postMessage({ fun: function () {}, w: worker, array: array });
}
);
exports["test:emit-json-values-only"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
// Validate self.on and self.emit
self.port.on("addon-to-content", function (fun, w, obj, array) {
self.port.emit("content-to-addon", [
fun === null,
typeof w,
"port" in w,
w._url,
"fun" in obj,
Object.keys(obj.dom).length,
Array.isArray(array),
JSON.stringify(array)
]);
});
}
});
// Validate worker.port
let array = [1, 2, 3];
worker.port.on("content-to-addon", function (result) {
assert.ok(result[0], "functions become null");
assert.equal(result[1], "object", "objects stay objects");
assert.ok(result[2], "object's attributes are enumerable");
assert.equal(result[3], DEFAULT_CONTENT_URL,
"json attribute is accessible");
assert.ok(!result[4], "function as object attribute is removed");
assert.equal(result[5], 0, "DOM nodes are converted into empty object");
// See bug 714891, Arrays may be broken over compartments:
assert.ok(result[6], "Array keeps being an array");
assert.equal(result[7], JSON.stringify(array),
"Array is correctly serialized");
done();
});
let obj = {
fun: function () {},
dom: browser.contentWindow.document.createElement("div")
};
// Add a new url property sa the Class function used by
// Worker doesn't set enumerables to true for non-functions
worker._url = DEFAULT_CONTENT_URL;
worker.port.emit("addon-to-content", function () {}, worker, obj, array);
}
);
exports["test:content is wrapped"] = WorkerTest(
"data:text/html;charset=utf-8,<script>var documentValue=true;</script>",
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
self.postMessage(!window.documentValue);
},
contentScriptWhen: "ready",
onMessage: function(msg) {
assert.ok(msg,
"content script has a wrapped access to content document");
done();
}
});
}
);
// ContentWorker is not for chrome
/*
exports["test:chrome is unwrapped"] = function(assert, done) {
let window = makeWindow();
listenOnce(window, "load", function onload() {
let worker = Worker({
window: window,
contentScript: "new " + function WorkerScope() {
self.postMessage(window.documentValue);
},
contentScriptWhen: "ready",
onMessage: function(msg) {
assert.ok(msg,
"content script has an unwrapped access to chrome document");
close(window).then(done);
}
});
});
}
*/
exports["test:nothing is leaked to content script"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
self.postMessage([
"ContentWorker" in window,
"UNWRAP_ACCESS_KEY" in window,
"getProxyForObject" in window
]);
},
contentScriptWhen: "ready",
onMessage: function(list) {
assert.ok(!list[0], "worker API contrustor isn't leaked");
assert.ok(!list[1], "Proxy API stuff isn't leaked 1/2");
assert.ok(!list[2], "Proxy API stuff isn't leaked 2/2");
done();
}
});
}
);
exports["test:ensure console.xxx works in cs"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
const EXPECTED = ["time", "log", "info", "warn", "error", "error", "timeEnd"];
let calls = [];
let levels = [];
system.on('console-api-log-event', onMessage);
function onMessage({ subject }) {
calls.push(subject.wrappedJSObject.arguments[0]);
levels.push(subject.wrappedJSObject.level);
}
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
console.time("time");
console.log("log");
console.info("info");
console.warn("warn");
console.error("error");
console.debug("debug");
console.exception("error");
console.timeEnd("timeEnd");
self.postMessage();
},
onMessage: function() {
system.off('console-api-log-event', onMessage);
assert.equal(JSON.stringify(calls),
JSON.stringify(EXPECTED),
"console methods have been called successfully, in expected order");
assert.equal(JSON.stringify(levels),
JSON.stringify(EXPECTED),
"console messages have correct log levels, in expected order");
done();
}
});
}
);
exports["test:setTimeout works with string argument"] = WorkerTest(
"data:text/html;charset=utf-8,<script>var docVal=5;</script>",
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function ContentScriptScope() {
// must use "window.scVal" instead of "var csVal"
// since we are inside ContentScriptScope function.
// i'm NOT putting code-in-string inside code-in-string </YO DAWG>
window.csVal = 13;
setTimeout("self.postMessage([" +
"csVal, " +
"window.docVal, " +
"'ContentWorker' in window, " +
"'UNWRAP_ACCESS_KEY' in window, " +
"'getProxyForObject' in window, " +
"])", 1);
},
contentScriptWhen: "ready",
onMessage: function([csVal, docVal, chrome1, chrome2, chrome3]) {
// test timer code is executed in the correct context
assert.equal(csVal, 13, "accessing content-script values");
assert.notEqual(docVal, 5, "can't access document values (directly)");
assert.ok(!chrome1 && !chrome2 && !chrome3, "nothing is leaked from chrome");
done();
}
});
}
);
exports["test:setInterval works with string argument"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let count = 0;
let worker = Worker({
window: browser.contentWindow,
contentScript: "setInterval('self.postMessage(1)', 50)",
contentScriptWhen: "ready",
onMessage: function(one) {
count++;
assert.equal(one, 1, "got " + count + " message(s) from setInterval");
if (count >= 3) done();
}
});
}
);
exports["test:setInterval async Errors passed to .onError"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let count = 0;
let worker = Worker({
window: browser.contentWindow,
contentScript: "setInterval(() => { throw Error('ubik') }, 50)",
contentScriptWhen: "ready",
onError: function(err) {
count++;
assert.equal(err.message, "ubik",
"error (corectly) propagated " + count + " time(s)");
if (count >= 3) done();
}
});
}
);
exports["test:setTimeout throws array, passed to .onError"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "setTimeout(function() { throw ['array', 42] }, 1)",
contentScriptWhen: "ready",
onError: function(arr) {
assert.ok(isArray(arr),
"the type of thrown/propagated object is array");
assert.ok(arr.length==2,
"the propagated thrown array is the right length");
assert.equal(arr[1], 42,
"element inside the thrown array correctly propagated");
done();
}
});
}
);
exports["test:setTimeout string arg with SyntaxError to .onError"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "setTimeout('syntax 123 error', 1)",
contentScriptWhen: "ready",
onError: function(err) {
assert.equal(err.name, "SyntaxError",
"received SyntaxError thrown from bad code in string argument to setTimeout");
assert.ok('fileName' in err,
"propagated SyntaxError contains a fileName property");
assert.ok('stack' in err,
"propagated SyntaxError contains a stack property");
assert.equal(err.message, "missing ; before statement",
"propagated SyntaxError has the correct (helpful) message");
assert.equal(err.lineNumber, 1,
"propagated SyntaxError was thrown on the right lineNumber");
done();
}
});
}
);
exports["test:setTimeout can't be cancelled by content"] = WorkerTest(
"data:text/html;charset=utf-8,<script>var documentValue=true;</script>",
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
let id = setTimeout(function () {
self.postMessage("timeout");
}, 100);
unsafeWindow.eval("clearTimeout("+id+");");
},
contentScriptWhen: "ready",
onMessage: function(msg) {
assert.ok(msg,
"content didn't managed to cancel our setTimeout");
done();
}
});
}
);
exports["test:clearTimeout"] = WorkerTest(
"data:text/html;charset=utf-8,clear timeout",
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
let id1 = setTimeout(function() {
self.postMessage("failed");
}, 10);
let id2 = setTimeout(function() {
self.postMessage("done");
}, 100);
clearTimeout(id1);
},
contentScriptWhen: "ready",
onMessage: function(msg) {
if (msg === "failed") {
assert.fail("failed to cancel timer");
} else {
assert.pass("timer cancelled");
done();
}
}
});
}
);
exports["test:clearInterval"] = WorkerTest(
"data:text/html;charset=utf-8,clear timeout",
function(assert, browser, done) {
let called = 0;
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
let id = setInterval(function() {
self.postMessage("intreval")
clearInterval(id)
setTimeout(function() {
self.postMessage("done")
}, 100)
}, 10);
},
contentScriptWhen: "ready",
onMessage: function(msg) {
if (msg === "intreval") {
called = called + 1;
if (called > 1) assert.fail("failed to cancel timer");
} else {
assert.pass("interval cancelled");
done();
}
}
});
}
)
exports["test:setTimeout are unregistered on content unload"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let originalWindow = browser.contentWindow;
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
document.title = "ok";
let i = 0;
setInterval(function () {
document.title = i++;
}, 10);
},
contentScriptWhen: "ready"
});
// Change location so that content script is destroyed,
// and all setTimeout/setInterval should be unregistered.
// Wait some cycles in order to execute some intervals.
setTimeout(function () {
// Bug 689621: Wait for the new document load so that we are sure that
// previous document cancelled its intervals
let url2 = "data:text/html;charset=utf-8,<title>final</title>";
loadAndWait(browser, url2, function onload() {
let titleAfterLoad = originalWindow.document.title;
// Wait additional cycles to verify that intervals are really cancelled
setTimeout(function () {
assert.equal(browser.contentDocument.title, "final",
"New document has not been modified");
assert.equal(originalWindow.document.title, titleAfterLoad,
"Nor previous one");
done();
}, 100);
});
}, 100);
}
);
exports['test:check window attribute in iframes'] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
// Create a first iframe and wait for its loading
let contentWin = browser.contentWindow;
let contentDoc = contentWin.document;
let iframe = contentDoc.createElement("iframe");
contentDoc.body.appendChild(iframe);
listenOnce(iframe, "load", function onload() {
// Create a second iframe inside the first one and wait for its loading
let iframeDoc = iframe.contentWindow.document;
let subIframe = iframeDoc.createElement("iframe");
iframeDoc.body.appendChild(subIframe);
listenOnce(subIframe, "load", function onload() {
subIframe.removeEventListener("load", onload, true);
// And finally create a worker against this second iframe
let worker = Worker({
window: subIframe.contentWindow,
contentScript: 'new ' + function WorkerScope() {
self.postMessage([
window.top !== window,
frameElement,
window.parent !== window,
top.location.href,
parent.location.href,
]);
},
onMessage: function(msg) {
assert.ok(msg[0], "window.top != window");
assert.ok(msg[1], "window.frameElement is defined");
assert.ok(msg[2], "window.parent != window");
assert.equal(msg[3], contentWin.location.href,
"top.location refers to the toplevel content doc");
assert.equal(msg[4], iframe.contentWindow.location.href,
"parent.location refers to the first iframe doc");
done();
}
});
});
subIframe.setAttribute("src", "data:text/html;charset=utf-8,bar");
});
iframe.setAttribute("src", "data:text/html;charset=utf-8,foo");
}
);
exports['test:check window attribute in toplevel documents'] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: 'new ' + function WorkerScope() {
self.postMessage([
window.top === window,
frameElement,
window.parent === window
]);
},
onMessage: function(msg) {
assert.ok(msg[0], "window.top == window");
assert.ok(!msg[1], "window.frameElement is null");
assert.ok(msg[2], "window.parent == window");
done();
}
});
}
);
exports["test:check worker API with page history"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let url2 = "data:text/html;charset=utf-8,bar";
loadAndWait(browser, url2, function () {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
// Just before the content script is disable, we register a timeout
// that will be disable until the page gets visible again
self.on("pagehide", function () {
setTimeout(function () {
self.postMessage("timeout restored");
}, 0);
});
},
contentScriptWhen: "start"
});
// postMessage works correctly when the page is visible
worker.postMessage("ok");
// We have to wait before going back into history,
// otherwise `goBack` won't do anything.
setTimeout(function () {
browser.goBack();
}, 0);
// Wait for the document to be hidden
browser.addEventListener("pagehide", function onpagehide() {
browser.removeEventListener("pagehide", onpagehide, false);
// Now any event sent to this worker should throw
setTimeout(_ => {
assert.throws(
function () { worker.postMessage("data"); },
/The page is currently hidden and can no longer be used/,
"postMessage should throw when the page is hidden in history"
);
assert.throws(
function () { worker.port.emit("event"); },
/The page is currently hidden and can no longer be used/,
"port.emit should throw when the page is hidden in history"
);
})
// Display the page with attached content script back in order to resume
// its timeout and receive the expected message.
// We have to delay this in order to not break the history.
// We delay for a non-zero amount of time in order to ensure that we
// do not receive the message immediatly, so that the timeout is
// actually disabled
setTimeout(function () {
worker.on("message", function (data) {
assert.ok(data, "timeout restored");
done();
});
browser.goForward();
}, 500);
}, false);
});
}
);
exports['test:conentScriptFile as URL instance'] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let url = new URL(fixtures.url("test-contentScriptFile.js"));
let worker = Worker({
window: browser.contentWindow,
contentScriptFile: url,
onMessage: function(msg) {
assert.equal(msg, "msg from contentScriptFile",
"received a wrong message from contentScriptFile");
done();
}
});
}
);
exports["test:worker events"] = WorkerTest(
DEFAULT_CONTENT_URL,
function (assert, browser, done) {
let window = browser.contentWindow;
let events = [];
let worker = Worker({
window: window,
contentScript: 'new ' + function WorkerScope() {
self.postMessage('start');
},
onAttach: win => {
events.push('attach');
assert.pass('attach event called when attached');
assert.equal(window, win, 'attach event passes in attached window');
},
onError: err => {
assert.equal(err.message, 'Custom',
'Error passed into error event');
worker.detach();
},
onMessage: msg => {
assert.pass('`onMessage` handles postMessage')
throw new Error('Custom');
},
onDetach: _ => {
assert.pass('`onDetach` called when worker detached');
done();
}
});
// `attach` event is called synchronously during instantiation,
// so we can't listen to that, TODO FIX?
// worker.on('attach', obj => console.log('attach', obj));
}
);
exports["test:onDetach in contentScript on destroy"] = WorkerTest(
"data:text/html;charset=utf-8,foo#detach",
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: 'new ' + function WorkerScope() {
self.port.on('detach', function(reason) {
window.location.hash += '!' + reason;
})
},
});
browser.contentWindow.addEventListener('hashchange', _ => {
assert.equal(browser.contentWindow.location.hash, '#detach!',
"location.href is as expected");
done();
})
worker.destroy();
}
);
exports["test:onDetach in contentScript on unload"] = WorkerTest(
"data:text/html;charset=utf-8,foo#detach",
function(assert, browser, done) {
let { loader } = LoaderWithHookedConsole(module);
let worker = loader.require("sdk/content/worker-parent").Worker({
window: browser.contentWindow,
contentScript: 'new ' + function WorkerScope() {
self.port.on('detach', function(reason) {
window.location.hash += '!' + reason;
})
},
});
browser.contentWindow.addEventListener('hashchange', _ => {
assert.equal(browser.contentWindow.location.hash, '#detach!shutdown',
"location.href is as expected");
done();
})
loader.unload('shutdown');
}
);
exports["test:console method log functions properly"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let logs = [];
system.on('console-api-log-event', onMessage);
function onMessage({ subject }) {
logs.push(clean(subject.wrappedJSObject.arguments[0]));
}
let clean = message =>
message.trim().
replace(/[\r\n]/g, " ").
replace(/ +/g, " ");
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
console.log(Function);
console.log((foo) => foo * foo);
console.log(function foo(bar) { return bar + bar });
self.postMessage();
},
onMessage: () => {
system.off('console-api-log-event', onMessage);
assert.deepEqual(logs, [
"function Function() { [native code] }",
"(foo) => foo * foo",
"function foo(bar) { \"use strict\"; return bar + bar }"
]);
done();
}
});
}
);
exports["test:global postMessage"] = WorkerTest(
WINDOW_SCRIPT_URL,
function(assert, browser, done) {
let contentScript = "window.addEventListener('message', function (e) {" +
" if (e.data === 'from -> window')" +
" self.port.emit('response', e.data, e.origin);" +
"});" +
"postMessage('from -> content-script', '*');";
let { loader } = LoaderWithHookedConsole(module);
let worker = loader.require("sdk/content/worker-parent").Worker({
window: browser.contentWindow,
contentScriptWhen: "ready",
contentScript: contentScript
});
worker.port.on("response", (data, origin) => {
assert.equal(data, "from -> window", "Communication from content-script to window completed");
done();
});
});
exports["test:destroy unbinds listeners from port"] = WorkerTest(
"data:text/html;charset=utf-8,portdestroyer",
function(assert, browser, done) {
let destroyed = false;
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
self.port.emit("destroy");
setInterval(self.port.emit, 10, "ping");
},
onDestroy: done
});
worker.port.on("ping", () => {
if (destroyed) {
assert.fail("Should not call events on port after destroy.");
}
});
worker.port.on("destroy", () => {
destroyed = true;
worker.destroy();
assert.pass("Worker destroyed, waiting for no future listeners handling events.");
setTimeout(done, 500);
});
}
);
require("test").run(exports);

View File

@ -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';
const { on, once, off, emit, count } = require('sdk/event/core');
@ -199,7 +198,7 @@ exports['test unhandled errors'] = function(assert) {
let exceptions = [];
let { loader, messages } = LoaderWithHookedConsole(module);
let { emit, on } = loader.require('sdk/event/core');
let { emit } = loader.require('sdk/event/core');
let target = {};
let boom = Error('Boom!');
@ -210,6 +209,24 @@ exports['test unhandled errors'] = function(assert) {
'unhandled exception is logged');
};
exports['test piped errors'] = function(assert) {
let exceptions = [];
let { loader, messages } = LoaderWithHookedConsole(module);
let { emit } = loader.require('sdk/event/core');
let { pipe } = loader.require('sdk/event/utils');
let target = {};
let second = {};
pipe(target, second);
emit(target, 'error', 'piped!');
assert.equal(messages.length, 1, 'error logged only once, ' +
'considered "handled" on `target` by the catch-all pipe');
assert.equal(messages[0].type, 'exception', 'The console message is exception');
assert.ok(~String(messages[0].msg).indexOf('piped!'),
'unhandled (piped) exception is logged on `second` target');
};
exports['test count'] = function(assert) {
let target = {};
@ -242,4 +259,4 @@ exports['test listen to all events'] = function(assert) {
'wildcard listener called for unbound event name');
};
require('test').run(exports);
require('sdk/test').run(exports);

View File

@ -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';
const { emit } = require('sdk/event/core');
@ -115,7 +114,6 @@ exports['test remove a listener'] = function(assert) {
})
});
target.off('message'); // must do nothing.
emit(target, 'message');
assert.deepEqual([ 1 ], actual, 'first listener called');
emit(target, 'message');
@ -124,6 +122,26 @@ exports['test remove a listener'] = function(assert) {
assert.deepEqual([ 1, 1, 2, 2, 2 ], actual, 'first listener removed');
};
exports['test .off() removes all listeners'] = function(assert) {
let target = EventTarget();
let actual = [];
target.on('message', function listener() {
actual.push(1);
target.on('message', function() {
target.removeListener('message', listener);
actual.push(2);
})
});
emit(target, 'message');
assert.deepEqual([ 1 ], actual, 'first listener called');
emit(target, 'message');
assert.deepEqual([ 1, 1, 2 ], actual, 'second listener called');
target.off();
emit(target, 'message');
assert.deepEqual([ 1, 1, 2 ], actual, 'target.off() removed all listeners');
};
exports['test error handling'] = function(assert) {
let target = EventTarget();
let error = Error('boom!');
@ -201,5 +219,4 @@ exports['test target is chainable'] = function (assert, done) {
emit(emitter, 'data', 'message');
};
require('test').run(exports);
require('sdk/test').run(exports);

View File

@ -4,17 +4,13 @@
"use strict";
const { PageMod } = require("sdk/page-mod");
const { testPageMod, handleReadyState } = require("./pagemod-test-helpers");
const { testPageMod, handleReadyState, contentScriptWhenServer } = require("./pagemod-test-helpers");
const { Loader } = require('sdk/test/loader');
const tabs = require("sdk/tabs");
const { setTimeout } = require("sdk/timers");
const { Cc, Ci, Cu } = require("chrome");
const {
open,
getFrames,
getMostRecentBrowserWindow,
getInnerId
} = require('sdk/window/utils');
const system = require("sdk/system/events");
const { open, getFrames, getMostRecentBrowserWindow, getInnerId } = require('sdk/window/utils');
const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils');
const xulApp = require("sdk/system/xul-app");
const { isPrivateBrowsingSupported } = require('sdk/self');
@ -24,7 +20,6 @@ const { isTabPBSupported, isWindowPBSupported, isGlobalPBSupported } = require('
const promise = require("sdk/core/promise");
const { pb } = require('./private-browsing/helper');
const { URL } = require("sdk/url");
const { LoaderWithHookedConsole } = require('sdk/test/loader');
const { waitUntil } = require("sdk/test/utils");
const data = require("./fixtures");
@ -64,7 +59,8 @@ exports.testPageMod1 = function(assert, done) {
"PageMod.onReady test"
);
done();
}
},
100
);
};
@ -96,7 +92,9 @@ exports.testPageMod2 = function(assert, done) {
assert.equal("AUQLUE" in win, false,
"PageMod test #2: scripts get a wrapped window");
done();
});
},
100
);
};
exports.testPageModIncludes = function(assert, done) {
@ -622,15 +620,14 @@ exports.testContentScriptWhenDefault = function(assert) {
// test timing for all 3 contentScriptWhen options (start, ready, end)
// for new pages, or tabs opened after PageMod is created
exports.testContentScriptWhenForNewTabs = function(assert, done) {
const url = "data:text/html;charset=utf-8,testContentScriptWhenForNewTabs";
let srv = contentScriptWhenServer();
let url = srv.URL + '?ForNewTabs';
let count = 0;
handleReadyState(url, 'start', {
onLoading: (tab) => {
assert.pass("PageMod is attached while document is loading");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onInteractive: () => assert.fail("onInteractive should not be called with 'start'."),
onComplete: () => assert.fail("onComplete should not be called with 'start'."),
@ -639,8 +636,7 @@ exports.testContentScriptWhenForNewTabs = function(assert, done) {
handleReadyState(url, 'ready', {
onInteractive: (tab) => {
assert.pass("PageMod is attached while document is interactive");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'ready'."),
onComplete: () => assert.fail("onComplete should not be called with 'ready'."),
@ -649,8 +645,7 @@ exports.testContentScriptWhenForNewTabs = function(assert, done) {
handleReadyState(url, 'end', {
onComplete: (tab) => {
assert.pass("PageMod is attached when document is complete");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'end'."),
onInteractive: () => assert.fail("onInteractive should not be called with 'end'."),
@ -662,18 +657,18 @@ exports.testContentScriptWhenForNewTabs = function(assert, done) {
// test timing for all 3 contentScriptWhen options (start, ready, end)
// for PageMods created right as the tab is created (in tab.onOpen)
exports.testContentScriptWhenOnTabOpen = function(assert, done) {
const url = "data:text/html;charset=utf-8,testContentScriptWhenOnTabOpen";
let srv = contentScriptWhenServer();
let url = srv.URL + '?OnTabOpen';
let count = 0;
tabs.open({
url: url,
onOpen: function(tab) {
let count = 0;
handleReadyState(url, 'start', {
onLoading: () => {
assert.pass("PageMod is attached while document is loading");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onInteractive: () => assert.fail("onInteractive should not be called with 'start'."),
onComplete: () => assert.fail("onComplete should not be called with 'start'."),
@ -682,8 +677,7 @@ exports.testContentScriptWhenOnTabOpen = function(assert, done) {
handleReadyState(url, 'ready', {
onInteractive: () => {
assert.pass("PageMod is attached while document is interactive");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'ready'."),
onComplete: () => assert.fail("onComplete should not be called with 'ready'."),
@ -692,8 +686,7 @@ exports.testContentScriptWhenOnTabOpen = function(assert, done) {
handleReadyState(url, 'end', {
onComplete: () => {
assert.pass("PageMod is attached when document is complete");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'end'."),
onInteractive: () => assert.fail("onInteractive should not be called with 'end'."),
@ -706,20 +699,18 @@ exports.testContentScriptWhenOnTabOpen = function(assert, done) {
// test timing for all 3 contentScriptWhen options (start, ready, end)
// for PageMods created while the tab is interactive (in tab.onReady)
exports.testContentScriptWhenOnTabReady = function(assert, done) {
// need a bit bigger document to get the right timing of events with e10s
let iframeURL = 'data:text/html;charset=utf-8,testContentScriptWhenOnTabReady';
let iframe = '<iframe src="' + iframeURL + '" />';
let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe);
let srv = contentScriptWhenServer();
let url = srv.URL + '?OnTabReady';
let count = 0;
tabs.open({
url: url,
onReady: function(tab) {
let count = 0;
handleReadyState(url, 'start', {
onInteractive: () => {
assert.pass("PageMod is attached while document is interactive");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'start'."),
onComplete: () => assert.fail("onComplete should not be called with 'start'."),
@ -728,8 +719,7 @@ exports.testContentScriptWhenOnTabReady = function(assert, done) {
handleReadyState(url, 'ready', {
onInteractive: () => {
assert.pass("PageMod is attached while document is interactive");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'ready'."),
onComplete: () => assert.fail("onComplete should not be called with 'ready'."),
@ -738,8 +728,7 @@ exports.testContentScriptWhenOnTabReady = function(assert, done) {
handleReadyState(url, 'end', {
onComplete: () => {
assert.pass("PageMod is attached when document is complete");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'end'."),
onInteractive: () => assert.fail("onInteractive should not be called with 'end'."),
@ -752,18 +741,18 @@ exports.testContentScriptWhenOnTabReady = function(assert, done) {
// test timing for all 3 contentScriptWhen options (start, ready, end)
// for PageMods created after a tab has completed loading (in tab.onLoad)
exports.testContentScriptWhenOnTabLoad = function(assert, done) {
const url = "data:text/html;charset=utf-8,testContentScriptWhenOnTabLoad";
let srv = contentScriptWhenServer();
let url = srv.URL + '?OnTabLoad';
let count = 0;
tabs.open({
url: url,
onLoad: function(tab) {
let count = 0;
handleReadyState(url, 'start', {
onComplete: () => {
assert.pass("PageMod is attached when document is complete");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'start'."),
onInteractive: () => assert.fail("onInteractive should not be called with 'start'."),
@ -772,8 +761,7 @@ exports.testContentScriptWhenOnTabLoad = function(assert, done) {
handleReadyState(url, 'ready', {
onComplete: () => {
assert.pass("PageMod is attached when document is complete");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'ready'."),
onInteractive: () => assert.fail("onInteractive should not be called with 'ready'."),
@ -782,8 +770,7 @@ exports.testContentScriptWhenOnTabLoad = function(assert, done) {
handleReadyState(url, 'end', {
onComplete: () => {
assert.pass("PageMod is attached when document is complete");
if (++count === 3)
tab.close(done);
checkDone(++count, tab, srv, done);
},
onLoading: () => assert.fail("onLoading should not be called with 'end'."),
onInteractive: () => assert.fail("onInteractive should not be called with 'end'."),
@ -793,6 +780,11 @@ exports.testContentScriptWhenOnTabLoad = function(assert, done) {
});
}
function checkDone(count, tab, srv, done) {
if (count === 3)
tab.close(_ => srv.stop(done));
}
exports.testTabWorkerOnMessage = function(assert, done) {
let { browserWindows } = require("sdk/windows");
let tabs = require("sdk/tabs");
@ -1063,7 +1055,7 @@ exports.testPageModCss = function(assert, done) {
assert.equal(div.clientHeight, 100,
"PageMod contentStyle worked");
assert.equal(div.offsetHeight, 120,
"PageMod contentStyleFile worked");
@ -1144,6 +1136,7 @@ exports.testPageModCssDestroy = function(assert, done) {
);
pageMod.destroy();
assert.equal(
style.width,
"200px",
@ -1151,7 +1144,6 @@ exports.testPageModCssDestroy = function(assert, done) {
);
done();
}
);
};
@ -1194,7 +1186,6 @@ exports.testPageModCssAutomaticDestroy = function(assert, done) {
};
exports.testPageModContentScriptFile = function(assert, done) {
testPageMod(assert, done, "about:license", [{
include: "about:*",
contentScriptWhen: "start",
@ -1377,7 +1368,7 @@ exports.testIFramePostMessage = function(assert, done) {
exports.testEvents = function(assert, done) {
let content = "<script>\n new " + function DocumentScope() {
window.addEventListener("ContentScriptEvent", function () {
window.receivedEvent = true;
window.document.body.setAttribute("receivedEvent", true);
}, false);
} + "\n</script>";
let url = "data:text/html;charset=utf-8," + encodeURIComponent(content);
@ -1391,11 +1382,12 @@ exports.testEvents = function(assert, done) {
}],
function(win, done) {
assert.ok(
win.receivedEvent,
win.document.body.getAttribute("receivedEvent"),
"Content script sent an event and document received it"
);
done();
}
},
100
);
};
@ -1648,14 +1640,16 @@ exports.testDetachOnUnload = function(assert, done) {
exports.testConsole = function(assert, done) {
let innerID;
const TEST_URL = 'data:text/html;charset=utf-8,console';
const { loader } = LoaderWithHookedConsole(module, onMessage);
const { PageMod } = loader.require('sdk/page-mod');
const system = require("sdk/system/events");
let seenMessage = false;
function onMessage(type, msg, msgID) {
system.on('console-api-log-event', onMessage);
function onMessage({ subject: { wrappedJSObject: msg }}) {
if (msg.arguments[0] !== "Hello from the page mod")
return;
seenMessage = true;
innerID = msgID;
innerID = msg.innerID;
}
let mod = PageMod({
@ -1671,6 +1665,9 @@ exports.testConsole = function(assert, done) {
let id = getInnerId(window);
assert.ok(seenMessage, "Should have seen the console message");
assert.equal(innerID, id, "Should have seen the right inner ID");
system.off('console-api-log-event', onMessage);
mod.destroy();
closeTab(tab);
done();
});
@ -1704,7 +1701,8 @@ exports.testSyntaxErrorInContentScript = function(assert, done) {
if (hitError)
assert.equal(hitError.name, "SyntaxError", "The error thrown should be a SyntaxError");
done();
}
},
300
);
};

View File

@ -675,7 +675,7 @@ exports["test console.log in Panel"] = function(assert, done) {
}
};
if (isWindowPBSupported) {
/*if (isWindowPBSupported) {
exports.testPanelDoesNotShowInPrivateWindowNoAnchor = function(assert, done) {
let { loader } = LoaderWithHookedConsole(module, ignorePassingDOMNodeWarning);
let { Panel } = loader.require("sdk/panel");
@ -783,7 +783,7 @@ if (isWindowPBSupported) {
then(testShowPanel.bind(null, assert, panel)).
then(done, assert.fail.bind(assert));
}
}
}*/
function testShowPanel(assert, panel) {
let { promise, resolve } = defer();

View File

@ -221,7 +221,7 @@ exports['test promised with promise args'] = function(assert, done) {
deferred.resolve(24);
};
exports['test promised error handleing'] = function(assert, done) {
exports['test promised error handling'] = function(assert, done) {
let expected = Error('boom');
let f = promised(function() {
throw expected;
@ -293,6 +293,18 @@ exports['test promised are not greedy'] = function(assert, done) {
assert.equal(runs, 0, 'promised does not run task right away');
};
exports['test promised does not flatten arrays'] = function(assert, done) {
let p = promised(function(empty, one, two, nested) {
assert.equal(empty.length, 0, "first argument is empty");
assert.deepEqual(one, ['one'], "second has one");
assert.deepEqual(two, ['two', 'more'], "third has two more");
assert.deepEqual(nested, [[]], "forth is properly nested");
done();
});
p([], ['one'], ['two', 'more'], [[]]);
};
exports['test arrays should not flatten'] = function(assert, done) {
let a = defer();
let b = defer();

View File

@ -5,7 +5,9 @@
const xulApp = require("sdk/system/xul-app");
const self = require("sdk/self");
const { Loader, main, unload } = require("toolkit/loader");
const { Loader, main, unload, override } = require("toolkit/loader");
const { PlainTextConsole } = require("sdk/console/plain-text");
const { Loader: CustomLoader } = require("sdk/test/loader");
const loaderOptions = require("@loader/options");
exports.testSelf = function(assert) {
@ -52,4 +54,26 @@ exports.testSelfHandlesLackingLoaderOptions = function (assert) {
unload(loader);
};
exports.testPreferencesBranch = function (assert) {
let options = override(loaderOptions, {
preferencesBranch: 'human-readable',
});
let loader = CustomLoader(module, { }, options);
let { preferencesBranch } = loader.require('sdk/self');
assert.equal(preferencesBranch, 'human-readable',
'preferencesBranch is human-readable');
}
exports.testInvalidPreferencesBranch = function (assert) {
let console = new PlainTextConsole(_ => void _);
let options = override(loaderOptions, {
preferencesBranch: 'invalid^branch*name',
id: 'simple@jetpack'
});
let loader = CustomLoader(module, { console }, options);
let { preferencesBranch } = loader.require('sdk/self');
assert.equal(preferencesBranch, 'simple@jetpack',
'invalid preferencesBranch value ignored');
}
require("sdk/test").run(exports);

View File

@ -0,0 +1,35 @@
"use strict";
const { Cu } = require("chrome");
const requireURI = require.resolve("toolkit/require.js");
const jsm = Cu.import(requireURI, {});
exports.testRequire = assert => {
assert.equal(typeof(jsm.require), "function",
"require is a function");
assert.equal(typeof(jsm.require.resolve), "function",
"require.resolve is a function");
assert.equal(typeof(jsm.require("method/core")), "function",
"can import modules that aren't under sdk");
assert.equal(typeof(jsm.require("sdk/base64").encode), "function",
"can import module from sdk");
};
const required = require("toolkit/require")
exports.testSameRequire = (assert) => {
assert.equal(jsm.require("method/core"),
required.require("method/core"),
"jsm and module return same instance");
assert.equal(jsm.require, required.require,
"require function is same in both contexts");
};
require("test").run(exports)

View File

@ -50,13 +50,10 @@ exports.testTabRelativePath = function(assert, done) {
const { merge } = require("sdk/util/object");
const self = require("sdk/self");
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, fixtures)
})
}
});
const options = merge({}, require('@loader/options'),
{ prefixURI: require('./fixtures').url() });
let loader = Loader(module, null, options);
let tabs = loader.require("sdk/tabs");
@ -73,6 +70,7 @@ exports.testTabRelativePath = function(assert, done) {
"Tab attach a contentScriptFile with relative path worked");
tab.close(done);
loader.unload();
}
});
}

View File

@ -6,9 +6,13 @@
const ERR_CONFLICT = 'Remaining conflicting property: ',
ERR_REQUIRED = 'Missing required property: ';
const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x),
...Object.getOwnPropertySymbols(x)];
function assertSametrait(assert, trait1, trait2) {
let names1 = Object.getOwnPropertyNames(trait1),
names2 = Object.getOwnPropertyNames(trait2);
let names1 = getOwnIdentifiers(trait1),
names2 = getOwnIdentifiers(trait2);
assert.equal(
names1.length,
@ -738,7 +742,7 @@ exports['test:create simple'] = function(assert) {
assert.equal(1, o1.b(), 'o1.b()');
assert.equal(
2,
Object.getOwnPropertyNames(o1).length,
getOwnIdentifiers(o1).length,
'Object.keys(o1).length === 2'
);
};

View File

@ -16,11 +16,17 @@ const { open, focus, close } = require('sdk/window/helpers');
const { setTimeout } = require('sdk/timers');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { partial } = require('sdk/lang/functional');
const { wait } = require('./event/helpers');
const { gc } = require('sdk/test/memory');
const openBrowserWindow = partial(open, null, {features: {toolbar: true}});
const openPrivateBrowserWindow = partial(open, null,
{features: {toolbar: true, private: true}});
const badgeNodeFor = (node) =>
node.ownerDocument.getAnonymousElementByAttribute(node,
'class', 'toolbarbutton-badge');
function getWidget(buttonId, window = getMostRecentBrowserWindow()) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { AREA_NAVBAR } = CustomizableUI;
@ -107,6 +113,16 @@ exports['test basic constructor validation'] = function(assert) {
/^The option "icon"/,
'throws on no valid icon given');
assert.throws(
() => ActionButton({ id: 'my-button', label: 'button', icon: './i.png', badge: true}),
/^The option "badge"/,
'throws on no valid badge given');
assert.throws(
() => ActionButton({ id: 'my-button', label: 'button', icon: './i.png', badgeColor: true}),
/^The option "badgeColor"/,
'throws on no valid badge given');
loader.unload();
};
@ -137,6 +153,9 @@ exports['test button added'] = function(assert) {
assert.equal(data.url(button.icon.substr(2)), node.getAttribute('image'),
'icon is set');
assert.equal("", node.getAttribute('badge'),
'badge attribute is empty');
loader.unload();
}
@ -242,7 +261,7 @@ exports['test button global state updated'] = function(assert) {
let button = ActionButton({
id: 'my-button-4',
label: 'my button',
icon: './icon.png'
icon: './icon.png',
});
// Tried to use `getWidgetIdsInArea` but seems undefined, not sure if it
@ -283,6 +302,19 @@ exports['test button global state updated'] = function(assert) {
assert.equal(node.getAttribute('disabled'), 'true',
'node disabled is updated');
button.badge = '+2';
button.badgeColor = 'blue';
assert.equal(button.badge, '+2',
'badge is updated');
assert.equal(node.getAttribute('bagde'), '',
'node badge is updated');
assert.equal(button.badgeColor, 'blue',
'badgeColor is updated');
assert.equal(badgeNodeFor(node).style.backgroundColor, 'blue',
'badge color is updated');
// TODO: test validation on update
loader.unload();
@ -312,7 +344,9 @@ exports['test button global state set and get with state method'] = function(ass
button.state(button, {
label: 'New label',
icon: './new-icon.png',
disabled: true
disabled: true,
badge: '+2',
badgeColor: 'blue'
});
assert.equal(button.label, 'New label',
@ -321,11 +355,15 @@ exports['test button global state set and get with state method'] = function(ass
'icon is updated');
assert.equal(button.disabled, true,
'disabled is updated');
assert.equal(button.badge, '+2',
'badge is updated');
assert.equal(button.badgeColor, 'blue',
'badgeColor is updated');
loader.unload();
}
exports['test button global state updated on multiple windows'] = function(assert, done) {
exports['test button global state updated on multiple windows'] = function*(assert) {
let loader = Loader(module);
let { ActionButton } = loader.require('sdk/ui');
@ -337,38 +375,48 @@ exports['test button global state updated on multiple windows'] = function(asser
let nodes = [getWidget(button.id).node];
openBrowserWindow().then(window => {
nodes.push(getWidget(button.id, window).node);
let window = yield openBrowserWindow();
button.label = 'New label';
button.icon = './new-icon.png';
button.disabled = true;
nodes.push(getWidget(button.id, window).node);
for (let node of nodes) {
assert.equal(node.getAttribute('label'), 'New label',
'node label is updated');
assert.equal(node.getAttribute('tooltiptext'), 'New label',
'node tooltip is updated');
button.label = 'New label';
button.icon = './new-icon.png';
button.disabled = true;
button.badge = '+10';
button.badgeColor = 'green';
assert.equal(button.icon, './new-icon.png',
'icon is updated');
assert.equal(node.getAttribute('image'), data.url('new-icon.png'),
'node image is updated');
for (let node of nodes) {
assert.equal(node.getAttribute('label'), 'New label',
'node label is updated');
assert.equal(node.getAttribute('tooltiptext'), 'New label',
'node tooltip is updated');
assert.equal(button.disabled, true,
'disabled is updated');
assert.equal(node.getAttribute('disabled'), 'true',
'node disabled is updated');
};
assert.equal(button.icon, './new-icon.png',
'icon is updated');
assert.equal(node.getAttribute('image'), data.url('new-icon.png'),
'node image is updated');
return window;
}).
then(close).
then(loader.unload).
then(done, assert.fail);
assert.equal(button.disabled, true,
'disabled is updated');
assert.equal(node.getAttribute('disabled'), 'true',
'node disabled is updated');
assert.equal(button.badge, '+10',
'badge is updated')
assert.equal(button.badgeColor, 'green',
'badgeColor is updated')
assert.equal(node.getAttribute('badge'), '+10',
'node badge is updated')
assert.equal(badgeNodeFor(node).style.backgroundColor, 'green',
'node badge color is updated')
};
yield close(window);
loader.unload();
};
exports['test button window state'] = function(assert, done) {
exports['test button window state'] = function*(assert) {
let loader = Loader(module);
let { ActionButton } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
@ -376,107 +424,145 @@ exports['test button window state'] = function(assert, done) {
let button = ActionButton({
id: 'my-button-6',
label: 'my button',
icon: './icon.png'
icon: './icon.png',
badge: '+1',
badgeColor: 'red'
});
let mainWindow = browserWindows.activeWindow;
let nodes = [getWidget(button.id).node];
openBrowserWindow().then(focus).then(window => {
let node;
let state;
let window = yield openBrowserWindow().then(focus);
nodes.push(getWidget(button.id, window).node);
nodes.push(getWidget(button.id, window).node);
let { activeWindow } = browserWindows;
let { activeWindow } = browserWindows;
button.state(activeWindow, {
label: 'New label',
icon: './new-icon.png',
disabled: true
});
button.state(activeWindow, {
label: 'New label',
icon: './new-icon.png',
disabled: true,
badge: '+2',
badgeColor : 'green'
});
// check the states
// check the states
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
assert.equal(button.badge, '+1',
'global badge unchanged');
assert.equal(button.badgeColor, 'red',
'global badgeColor unchanged');
state = button.state(mainWindow);
let state = button.state(mainWindow);
assert.equal(state.label, 'my button',
'previous window label unchanged');
assert.equal(state.icon, './icon.png',
'previous window icon unchanged');
assert.equal(state.disabled, false,
'previous window disabled unchanged');
assert.equal(state.label, 'my button',
'previous window label unchanged');
assert.equal(state.icon, './icon.png',
'previous window icon unchanged');
assert.equal(state.disabled, false,
'previous window disabled unchanged');
assert.deepEqual(button.badge, '+1',
'previouswindow badge unchanged');
assert.deepEqual(button.badgeColor, 'red',
'previous window badgeColor unchanged');
state = button.state(activeWindow);
state = button.state(activeWindow);
assert.equal(state.label, 'New label',
'active window label updated');
assert.equal(state.icon, './new-icon.png',
'active window icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
assert.equal(state.label, 'New label',
'active window label updated');
assert.equal(state.icon, './new-icon.png',
'active window icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
assert.equal(state.badge, '+2',
'active badge updated');
assert.equal(state.badgeColor, 'green',
'active badgeColor updated');
// change the global state, only the windows without a state are affected
// change the global state, only the windows without a state are affected
button.label = 'A good label';
button.label = 'A good label';
button.badge = '+3';
assert.equal(button.label, 'A good label',
'global label updated');
assert.equal(button.state(mainWindow).label, 'A good label',
'previous window label updated');
assert.equal(button.state(activeWindow).label, 'New label',
'active window label unchanged');
assert.equal(button.label, 'A good label',
'global label updated');
assert.equal(button.state(mainWindow).label, 'A good label',
'previous window label updated');
assert.equal(button.state(activeWindow).label, 'New label',
'active window label unchanged');
assert.equal(button.state(activeWindow).badge, '+2',
'active badge unchanged');
assert.equal(button.state(activeWindow).badgeColor, 'green',
'active badgeColor unchanged');
assert.equal(button.state(mainWindow).badge, '+3',
'previous window badge updated');
assert.equal(button.state(mainWindow).badgeColor, 'red',
'previous window badgeColor unchanged');
// delete the window state will inherits the global state again
// delete the window state will inherits the global state again
button.state(activeWindow, null);
button.state(activeWindow, null);
assert.equal(button.state(activeWindow).label, 'A good label',
'active window label inherited');
state = button.state(activeWindow);
// check the nodes properties
node = nodes[0];
state = button.state(mainWindow);
assert.equal(state.label, 'A good label',
'active window label inherited');
assert.equal(state.badge, '+3',
'previous window badge inherited');
assert.equal(button.badgeColor, 'red',
'previous window badgeColor inherited');
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
// check the nodes properties
let node = nodes[0];
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
state = button.state(mainWindow);
node = nodes[1];
state = button.state(activeWindow);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
assert.equal(node.getAttribute("badge"), state.badge,
'badge is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
assert.equal(badgeNodeFor(node).style.backgroundColor, state.badgeColor,
'badge color is correct');
return window;
}).
then(close).
then(loader.unload).
then(done, assert.fail);
node = nodes[1];
state = button.state(activeWindow);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
assert.equal(node.getAttribute('badge'), state.badge,
'badge is correct');
assert.equal(badgeNodeFor(node).style.backgroundColor, state.badgeColor,
'badge color is correct');
yield close(window);
loader.unload();
};
exports['test button tab state'] = function(assert, done) {
exports['test button tab state'] = function*(assert) {
let loader = Loader(module);
let { ActionButton } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
@ -491,119 +577,146 @@ exports['test button tab state'] = function(assert, done) {
let mainTab = tabs.activeTab;
let node = getWidget(button.id).node;
tabs.open({
url: 'about:blank',
onActivate: function onActivate(tab) {
tab.removeListener('activate', onActivate);
tabs.open('about:blank');
let { activeWindow } = browserWindows;
// set window state
button.state(activeWindow, {
label: 'Window label',
icon: './window-icon.png'
});
yield wait(tabs, 'ready');
// set previous active tab state
button.state(mainTab, {
label: 'Tab label',
icon: './tab-icon.png',
});
let tab = tabs.activeTab;
let { activeWindow } = browserWindows;
// set current active tab state
button.state(tab, {
icon: './another-tab-icon.png',
disabled: true
});
// check the states
Cu.schedulePreciseGC(() => {
let state;
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
state = button.state(mainTab);
assert.equal(state.label, 'Tab label',
'previous tab label updated');
assert.equal(state.icon, './tab-icon.png',
'previous tab icon updated');
assert.equal(state.disabled, false,
'previous tab disabled unchanged');
state = button.state(tab);
assert.equal(state.label, 'Window label',
'active tab inherited from window state');
assert.equal(state.icon, './another-tab-icon.png',
'active tab icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
// change the global state
button.icon = './good-icon.png';
// delete the tab state
button.state(tab, null);
assert.equal(button.icon, './good-icon.png',
'global icon updated');
assert.equal(button.state(mainTab).icon, './tab-icon.png',
'previous tab icon unchanged');
assert.equal(button.state(tab).icon, './window-icon.png',
'tab icon inherited from window');
// delete the window state
button.state(activeWindow, null);
assert.equal(button.state(tab).icon, './good-icon.png',
'tab icon inherited from global');
// check the node properties
state = button.state(tabs.activeTab);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
tabs.once('activate', () => {
// This is made in order to avoid to check the node before it
// is updated, need a better check
setTimeout(() => {
let state = button.state(mainTab);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
tab.close(() => {
loader.unload();
done();
});
}, 500);
});
mainTab.activate();
});
}
// set window state
button.state(activeWindow, {
label: 'Window label',
icon: './window-icon.png',
badge: 'win',
badgeColor: 'blue'
});
// set previous active tab state
button.state(mainTab, {
label: 'Tab label',
icon: './tab-icon.png',
badge: 'tab',
badgeColor: 'red'
});
// set current active tab state
button.state(tab, {
icon: './another-tab-icon.png',
disabled: true,
badge: 't1',
badgeColor: 'green'
});
// check the states, be sure they won't be gc'ed
yield gc();
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
assert.equal(button.badge, undefined,
'global badge unchanged')
let state = button.state(mainTab);
assert.equal(state.label, 'Tab label',
'previous tab label updated');
assert.equal(state.icon, './tab-icon.png',
'previous tab icon updated');
assert.equal(state.disabled, false,
'previous tab disabled unchanged');
assert.equal(state.badge, 'tab',
'previous tab badge unchanged')
assert.equal(state.badgeColor, 'red',
'previous tab badgeColor unchanged')
state = button.state(tab);
assert.equal(state.label, 'Window label',
'active tab inherited from window state');
assert.equal(state.icon, './another-tab-icon.png',
'active tab icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
assert.equal(state.badge, 't1',
'active badge updated');
assert.equal(state.badgeColor, 'green',
'active badgeColor updated');
// change the global state
button.icon = './good-icon.png';
// delete the tab state
button.state(tab, null);
assert.equal(button.icon, './good-icon.png',
'global icon updated');
assert.equal(button.state(mainTab).icon, './tab-icon.png',
'previous tab icon unchanged');
assert.equal(button.state(tab).icon, './window-icon.png',
'tab icon inherited from window');
assert.equal(button.state(mainTab).badge, 'tab',
'previous tab badge is unchaged');
assert.equal(button.state(tab).badge, 'win',
'tab badge is inherited from window');
// delete the window state
button.state(activeWindow, null);
state = button.state(tab);
assert.equal(state.icon, './good-icon.png',
'tab icon inherited from global');
assert.equal(state.badge, undefined,
'tab badge inherited from global');
assert.equal(state.badgeColor, undefined,
'tab badgeColor inherited from global');
// check the node properties
yield wait();
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'node disabled is correct');
assert.equal(node.getAttribute('badge'), '',
'badge text is correct');
assert.equal(badgeNodeFor(node).style.backgroundColor, '',
'badge color is correct');
mainTab.activate();
yield wait(tabs, 'activate');
// This is made in order to avoid to check the node before it
// is updated, need a better check
yield wait();
state = button.state(mainTab);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
assert.equal(node.getAttribute('badge'), state.badge,
'badge text is correct');
assert.equal(badgeNodeFor(node).style.backgroundColor, state.badgeColor,
'badge color is correct');
tab.close(loader.unload);
loader.unload();
};
exports['test button click'] = function*(assert) {
@ -644,7 +757,6 @@ exports['test button click'] = function*(assert) {
}
exports['test button icon set'] = function(assert) {
let size;
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
let loader = Loader(module);
let { ActionButton } = loader.require('sdk/ui');
@ -675,7 +787,7 @@ exports['test button icon set'] = function(assert) {
let { node, id: widgetId } = getWidget(button.id);
let { devicePixelRatio } = node.ownerDocument.defaultView;
size = 16 * devicePixelRatio;
let size = 16 * devicePixelRatio;
assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
'the icon is set properly in navbar');
@ -697,7 +809,7 @@ exports['test button icon set'] = function(assert) {
loader.unload();
}
exports['test button icon se with only one option'] = function(assert) {
exports['test button icon set with only one option'] = function(assert) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
let loader = Loader(module);
let { ActionButton } = loader.require('sdk/ui');
@ -760,6 +872,11 @@ exports['test button state validation'] = function(assert) {
/^The option "icon"/,
'throws on remote icon given');
assert.throws(
() => button.state(button, { badge: true } ),
/^The option "badge"/,
'throws on wrong badge value given');
loader.unload();
};
@ -939,4 +1056,77 @@ exports['test button after destroy'] = function(assert) {
loader.unload();
};
exports['test button badge property'] = function(assert) {
let loader = Loader(module);
let { ActionButton } = loader.require('sdk/ui');
let button = ActionButton({
id: 'my-button-18',
label: 'my button',
icon: './icon.png',
badge: 123456
});
assert.equal(button.badge, 123456,
'badge is set');
assert.equal(button.badgeColor, undefined,
'badge color is not set');
let { node } = getWidget(button.id);
let { getComputedStyle } = node.ownerDocument.defaultView;
let badgeNode = badgeNodeFor(node);
assert.equal('1234', node.getAttribute('badge'),
'badge text is displayed up to four characters');
assert.equal(getComputedStyle(badgeNode).backgroundColor, 'rgb(217, 0, 0)',
'badge color is the default one');
button.badge = '危機';
assert.equal(button.badge, '危機',
'badge is properly set');
assert.equal('危機', node.getAttribute('badge'),
'badge text is displayed');
button.badge = '🐶🐰🐹';
assert.equal(button.badge, '🐶🐰🐹',
'badge is properly set');
assert.equal('🐶🐰🐹', node.getAttribute('badge'),
'badge text is displayed');
loader.unload();
}
exports['test button badge color'] = function(assert) {
let loader = Loader(module);
let { ActionButton } = loader.require('sdk/ui');
let button = ActionButton({
id: 'my-button-19',
label: 'my button',
icon: './icon.png',
badge: '+1',
badgeColor: 'blue'
});
assert.equal(button.badgeColor, 'blue',
'badge color is set');
let { node } = getWidget(button.id);
let { getComputedStyle } = node.ownerDocument.defaultView;
let badgeNode = badgeNodeFor(node);
assert.equal(badgeNodeFor(node).style.backgroundColor, 'blue',
'badge color is displayed properly');
assert.equal(getComputedStyle(badgeNode).backgroundColor, 'rgb(0, 0, 255)',
'badge color overrides the default one');
loader.unload();
}
require('sdk/test').run(exports);

View File

@ -16,11 +16,17 @@ const { open, focus, close } = require('sdk/window/helpers');
const { setTimeout } = require('sdk/timers');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { partial } = require('sdk/lang/functional');
const { wait } = require('./event/helpers');
const { gc } = require('sdk/test/memory');
const openBrowserWindow = partial(open, null, {features: {toolbar: true}});
const openPrivateBrowserWindow = partial(open, null,
{features: {toolbar: true, private: true}});
const badgeNodeFor = (node) =>
node.ownerDocument.getAnonymousElementByAttribute(node,
'class', 'toolbarbutton-badge');
function getWidget(buttonId, window = getMostRecentBrowserWindow()) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { AREA_NAVBAR } = CustomizableUI;
@ -107,12 +113,15 @@ exports['test basic constructor validation'] = function(assert) {
/^The option "icon"/,
'throws on no valid icon given');
// Test wrong checked
assert.throws(
() => ToggleButton({
id: 'my-button', label: 'my button', icon: './icon.png', checked: 'yes'}),
/^The option "checked"/,
'throws on no valid checked value given');
() => ToggleButton({ id: 'my-button', label: 'button', icon: './i.png', badge: true}),
/^The option "badge"/,
'throws on no valid badge given');
assert.throws(
() => ToggleButton({ id: 'my-button', label: 'button', icon: './i.png', badgeColor: true}),
/^The option "badgeColor"/,
'throws on no valid badge given');
loader.unload();
};
@ -128,9 +137,6 @@ exports['test button added'] = function(assert) {
});
// check defaults
assert.equal(button.checked, false,
'checked is set to default `false` value');
assert.equal(button.disabled, false,
'disabled is set to default `false` value');
@ -147,6 +153,9 @@ exports['test button added'] = function(assert) {
assert.equal(data.url(button.icon.substr(2)), node.getAttribute('image'),
'icon is set');
assert.equal("", node.getAttribute('badge'),
'badge attribute is empty');
loader.unload();
}
@ -252,7 +261,7 @@ exports['test button global state updated'] = function(assert) {
let button = ToggleButton({
id: 'my-button-4',
label: 'my button',
icon: './icon.png'
icon: './icon.png',
});
// Tried to use `getWidgetIdsInArea` but seems undefined, not sure if it
@ -293,13 +302,25 @@ exports['test button global state updated'] = function(assert) {
assert.equal(node.getAttribute('disabled'), 'true',
'node disabled is updated');
button.badge = '+2';
button.badgeColor = 'blue';
assert.equal(button.badge, '+2',
'badge is updated');
assert.equal(node.getAttribute('bagde'), '',
'node badge is updated');
assert.equal(button.badgeColor, 'blue',
'badgeColor is updated');
assert.equal(badgeNodeFor(node).style.backgroundColor, 'blue',
'badge color is updated');
// TODO: test validation on update
loader.unload();
}
exports['test button global state set and get with state method'] = function(assert) {
let state;
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
@ -310,7 +331,7 @@ exports['test button global state set and get with state method'] = function(ass
});
// read the button's state
state = button.state(button);
let state = button.state(button);
assert.equal(state.label, 'my button',
'label is correct');
@ -323,7 +344,9 @@ exports['test button global state set and get with state method'] = function(ass
button.state(button, {
label: 'New label',
icon: './new-icon.png',
disabled: true
disabled: true,
badge: '+2',
badgeColor: 'blue'
});
assert.equal(button.label, 'New label',
@ -332,11 +355,15 @@ exports['test button global state set and get with state method'] = function(ass
'icon is updated');
assert.equal(button.disabled, true,
'disabled is updated');
assert.equal(button.badge, '+2',
'badge is updated');
assert.equal(button.badgeColor, 'blue',
'badgeColor is updated');
loader.unload();
};
}
exports['test button global state updated on multiple windows'] = function(assert, done) {
exports['test button global state updated on multiple windows'] = function*(assert) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
@ -348,39 +375,48 @@ exports['test button global state updated on multiple windows'] = function(asser
let nodes = [getWidget(button.id).node];
openBrowserWindow().then(window => {
nodes.push(getWidget(button.id, window).node);
let window = yield openBrowserWindow();
button.label = 'New label';
button.icon = './new-icon.png';
button.disabled = true;
nodes.push(getWidget(button.id, window).node);
for (let node of nodes) {
assert.equal(node.getAttribute('label'), 'New label',
'node label is updated');
assert.equal(node.getAttribute('tooltiptext'), 'New label',
'node tooltip is updated');
button.label = 'New label';
button.icon = './new-icon.png';
button.disabled = true;
button.badge = '+10';
button.badgeColor = 'green';
assert.equal(button.icon, './new-icon.png',
'icon is updated');
assert.equal(node.getAttribute('image'), data.url('new-icon.png'),
'node image is updated');
for (let node of nodes) {
assert.equal(node.getAttribute('label'), 'New label',
'node label is updated');
assert.equal(node.getAttribute('tooltiptext'), 'New label',
'node tooltip is updated');
assert.equal(button.disabled, true,
'disabled is updated');
assert.equal(node.getAttribute('disabled'), 'true',
'node disabled is updated');
};
assert.equal(button.icon, './new-icon.png',
'icon is updated');
assert.equal(node.getAttribute('image'), data.url('new-icon.png'),
'node image is updated');
return window;
}).
then(close).
then(loader.unload).
then(done, assert.fail);
assert.equal(button.disabled, true,
'disabled is updated');
assert.equal(node.getAttribute('disabled'), 'true',
'node disabled is updated');
assert.equal(button.badge, '+10',
'badge is updated')
assert.equal(button.badgeColor, 'green',
'badgeColor is updated')
assert.equal(node.getAttribute('badge'), '+10',
'node badge is updated')
assert.equal(badgeNodeFor(node).style.backgroundColor, 'green',
'node badge color is updated')
};
yield close(window);
loader.unload();
};
exports['test button window state'] = function(assert, done) {
let state;
exports['test button window state'] = function*(assert) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
@ -388,104 +424,145 @@ exports['test button window state'] = function(assert, done) {
let button = ToggleButton({
id: 'my-button-6',
label: 'my button',
icon: './icon.png'
icon: './icon.png',
badge: '+1',
badgeColor: 'red'
});
let mainWindow = browserWindows.activeWindow;
let nodes = [getWidget(button.id).node];
openBrowserWindow().then(focus).then(window => {
nodes.push(getWidget(button.id, window).node);
let window = yield openBrowserWindow().then(focus);
let { activeWindow } = browserWindows;
nodes.push(getWidget(button.id, window).node);
button.state(activeWindow, {
label: 'New label',
icon: './new-icon.png',
disabled: true
});
let { activeWindow } = browserWindows;
// check the states
button.state(activeWindow, {
label: 'New label',
icon: './new-icon.png',
disabled: true,
badge: '+2',
badgeColor : 'green'
});
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
// check the states
state = button.state(mainWindow);
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
assert.equal(button.badge, '+1',
'global badge unchanged');
assert.equal(button.badgeColor, 'red',
'global badgeColor unchanged');
assert.equal(state.label, 'my button',
'previous window label unchanged');
assert.equal(state.icon, './icon.png',
'previous window icon unchanged');
assert.equal(state.disabled, false,
'previous window disabled unchanged');
let state = button.state(mainWindow);
state = button.state(activeWindow);
assert.equal(state.label, 'my button',
'previous window label unchanged');
assert.equal(state.icon, './icon.png',
'previous window icon unchanged');
assert.equal(state.disabled, false,
'previous window disabled unchanged');
assert.deepEqual(button.badge, '+1',
'previouswindow badge unchanged');
assert.deepEqual(button.badgeColor, 'red',
'previous window badgeColor unchanged');
assert.equal(state.label, 'New label',
'active window label updated');
assert.equal(state.icon, './new-icon.png',
'active window icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
state = button.state(activeWindow);
// change the global state, only the windows without a state are affected
assert.equal(state.label, 'New label',
'active window label updated');
assert.equal(state.icon, './new-icon.png',
'active window icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
assert.equal(state.badge, '+2',
'active badge updated');
assert.equal(state.badgeColor, 'green',
'active badgeColor updated');
button.label = 'A good label';
// change the global state, only the windows without a state are affected
assert.equal(button.label, 'A good label',
'global label updated');
assert.equal(button.state(mainWindow).label, 'A good label',
'previous window label updated');
assert.equal(button.state(activeWindow).label, 'New label',
'active window label unchanged');
button.label = 'A good label';
button.badge = '+3';
// delete the window state will inherits the global state again
assert.equal(button.label, 'A good label',
'global label updated');
assert.equal(button.state(mainWindow).label, 'A good label',
'previous window label updated');
assert.equal(button.state(activeWindow).label, 'New label',
'active window label unchanged');
assert.equal(button.state(activeWindow).badge, '+2',
'active badge unchanged');
assert.equal(button.state(activeWindow).badgeColor, 'green',
'active badgeColor unchanged');
assert.equal(button.state(mainWindow).badge, '+3',
'previous window badge updated');
assert.equal(button.state(mainWindow).badgeColor, 'red',
'previous window badgeColor unchanged');
button.state(activeWindow, null);
// delete the window state will inherits the global state again
assert.equal(button.state(activeWindow).label, 'A good label',
'active window label inherited');
button.state(activeWindow, null);
// check the nodes properties
let node = nodes[0];
state = button.state(mainWindow);
state = button.state(activeWindow);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(state.label, 'A good label',
'active window label inherited');
assert.equal(state.badge, '+3',
'previous window badge inherited');
assert.equal(button.badgeColor, 'red',
'previous window badgeColor inherited');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
// check the nodes properties
let node = nodes[0];
node = nodes[1];
state = button.state(activeWindow);
state = button.state(mainWindow);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
assert.equal(node.getAttribute("badge"), state.badge,
'badge is correct');
return window;
}).
then(close).
then(loader.unload).
then(done, assert.fail);
assert.equal(badgeNodeFor(node).style.backgroundColor, state.badgeColor,
'badge color is correct');
node = nodes[1];
state = button.state(activeWindow);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
assert.equal(node.getAttribute('badge'), state.badge,
'badge is correct');
assert.equal(badgeNodeFor(node).style.backgroundColor, state.badgeColor,
'badge color is correct');
yield close(window);
loader.unload();
};
exports['test button tab state'] = function(assert, done) {
exports['test button tab state'] = function*(assert) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
@ -500,122 +577,149 @@ exports['test button tab state'] = function(assert, done) {
let mainTab = tabs.activeTab;
let node = getWidget(button.id).node;
tabs.open({
url: 'about:blank',
onActivate: function onActivate(tab) {
tab.removeListener('activate', onActivate);
tabs.open('about:blank');
let { activeWindow } = browserWindows;
// set window state
button.state(activeWindow, {
label: 'Window label',
icon: './window-icon.png'
});
yield wait(tabs, 'ready');
// set previous active tab state
button.state(mainTab, {
label: 'Tab label',
icon: './tab-icon.png',
});
let tab = tabs.activeTab;
let { activeWindow } = browserWindows;
// set current active tab state
button.state(tab, {
icon: './another-tab-icon.png',
disabled: true
});
// check the states
Cu.schedulePreciseGC(() => {
let state;
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
state = button.state(mainTab);
assert.equal(state.label, 'Tab label',
'previous tab label updated');
assert.equal(state.icon, './tab-icon.png',
'previous tab icon updated');
assert.equal(state.disabled, false,
'previous tab disabled unchanged');
state = button.state(tab);
assert.equal(state.label, 'Window label',
'active tab inherited from window state');
assert.equal(state.icon, './another-tab-icon.png',
'active tab icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
// change the global state
button.icon = './good-icon.png';
// delete the tab state
button.state(tab, null);
assert.equal(button.icon, './good-icon.png',
'global icon updated');
assert.equal(button.state(mainTab).icon, './tab-icon.png',
'previous tab icon unchanged');
assert.equal(button.state(tab).icon, './window-icon.png',
'tab icon inherited from window');
// delete the window state
button.state(activeWindow, null);
assert.equal(button.state(tab).icon, './good-icon.png',
'tab icon inherited from global');
// check the node properties
state = button.state(tabs.activeTab);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
tabs.once('activate', () => {
// This is made in order to avoid to check the node before it
// is updated, need a better check
setTimeout(() => {
let state = button.state(mainTab);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
tab.close(() => {
loader.unload();
done();
});
}, 500);
});
mainTab.activate();
});
}
// set window state
button.state(activeWindow, {
label: 'Window label',
icon: './window-icon.png',
badge: 'win',
badgeColor: 'blue'
});
// set previous active tab state
button.state(mainTab, {
label: 'Tab label',
icon: './tab-icon.png',
badge: 'tab',
badgeColor: 'red'
});
// set current active tab state
button.state(tab, {
icon: './another-tab-icon.png',
disabled: true,
badge: 't1',
badgeColor: 'green'
});
// check the states, be sure they won't be gc'ed
yield gc();
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
assert.equal(button.badge, undefined,
'global badge unchanged')
let state = button.state(mainTab);
assert.equal(state.label, 'Tab label',
'previous tab label updated');
assert.equal(state.icon, './tab-icon.png',
'previous tab icon updated');
assert.equal(state.disabled, false,
'previous tab disabled unchanged');
assert.equal(state.badge, 'tab',
'previous tab badge unchanged')
assert.equal(state.badgeColor, 'red',
'previous tab badgeColor unchanged')
state = button.state(tab);
assert.equal(state.label, 'Window label',
'active tab inherited from window state');
assert.equal(state.icon, './another-tab-icon.png',
'active tab icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
assert.equal(state.badge, 't1',
'active badge updated');
assert.equal(state.badgeColor, 'green',
'active badgeColor updated');
// change the global state
button.icon = './good-icon.png';
// delete the tab state
button.state(tab, null);
assert.equal(button.icon, './good-icon.png',
'global icon updated');
assert.equal(button.state(mainTab).icon, './tab-icon.png',
'previous tab icon unchanged');
assert.equal(button.state(tab).icon, './window-icon.png',
'tab icon inherited from window');
assert.equal(button.state(mainTab).badge, 'tab',
'previous tab badge is unchaged');
assert.equal(button.state(tab).badge, 'win',
'tab badge is inherited from window');
// delete the window state
button.state(activeWindow, null);
state = button.state(tab);
assert.equal(state.icon, './good-icon.png',
'tab icon inherited from global');
assert.equal(state.badge, undefined,
'tab badge inherited from global');
assert.equal(state.badgeColor, undefined,
'tab badgeColor inherited from global');
// check the node properties
yield wait();
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'node disabled is correct');
assert.equal(node.getAttribute('badge'), '',
'badge text is correct');
assert.equal(badgeNodeFor(node).style.backgroundColor, '',
'badge color is correct');
mainTab.activate();
yield wait(tabs, 'activate');
// This is made in order to avoid to check the node before it
// is updated, need a better check
yield wait();
state = button.state(mainTab);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
assert.equal(node.getAttribute('badge'), state.badge,
'badge text is correct');
assert.equal(badgeNodeFor(node).style.backgroundColor, state.badgeColor,
'badge color is correct');
tab.close(loader.unload);
loader.unload();
};
exports['test button click'] = function(assert, done) {
exports['test button click'] = function*(assert) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
@ -632,24 +736,24 @@ exports['test button click'] = function(assert, done) {
let mainWindow = browserWindows.activeWindow;
let chromeWindow = getMostRecentBrowserWindow();
openBrowserWindow().then(focus).then(window => {
button.state(mainWindow, { label: 'nothing' });
button.state(mainWindow.tabs.activeTab, { label: 'foo'})
button.state(browserWindows.activeWindow, { label: 'bar' });
let window = yield openBrowserWindow().then(focus);
button.click();
button.state(mainWindow, { label: 'nothing' });
button.state(mainWindow.tabs.activeTab, { label: 'foo'})
button.state(browserWindows.activeWindow, { label: 'bar' });
focus(chromeWindow).then(() => {
button.click();
button.click();
assert.deepEqual(labels, ['bar', 'foo'],
'button click works');
yield focus(chromeWindow);
close(window).
then(loader.unload).
then(done, assert.fail);
});
}).then(null, assert.fail);
button.click();
assert.deepEqual(labels, ['bar', 'foo'],
'button click works');
yield close(window);
loader.unload();
}
exports['test button icon set'] = function(assert) {
@ -705,7 +809,7 @@ exports['test button icon set'] = function(assert) {
loader.unload();
}
exports['test button icon se with only one option'] = function(assert) {
exports['test button icon set with only one option'] = function(assert) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
@ -768,6 +872,11 @@ exports['test button state validation'] = function(assert) {
/^The option "icon"/,
'throws on remote icon given');
assert.throws(
() => button.state(button, { badge: true } ),
/^The option "badge"/,
'throws on wrong badge value given');
loader.unload();
};
@ -947,6 +1056,79 @@ exports['test button after destroy'] = function(assert) {
loader.unload();
};
exports['test button badge property'] = function(assert) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
let button = ToggleButton({
id: 'my-button-18',
label: 'my button',
icon: './icon.png',
badge: 123456
});
assert.equal(button.badge, 123456,
'badge is set');
assert.equal(button.badgeColor, undefined,
'badge color is not set');
let { node } = getWidget(button.id);
let { getComputedStyle } = node.ownerDocument.defaultView;
let badgeNode = badgeNodeFor(node);
assert.equal('1234', node.getAttribute('badge'),
'badge text is displayed up to four characters');
assert.equal(getComputedStyle(badgeNode).backgroundColor, 'rgb(217, 0, 0)',
'badge color is the default one');
button.badge = '危機';
assert.equal(button.badge, '危機',
'badge is properly set');
assert.equal('危機', node.getAttribute('badge'),
'badge text is displayed');
button.badge = '🐶🐰🐹';
assert.equal(button.badge, '🐶🐰🐹',
'badge is properly set');
assert.equal('🐶🐰🐹', node.getAttribute('badge'),
'badge text is displayed');
loader.unload();
}
exports['test button badge color'] = function(assert) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
let button = ToggleButton({
id: 'my-button-19',
label: 'my button',
icon: './icon.png',
badge: '+1',
badgeColor: 'blue'
});
assert.equal(button.badgeColor, 'blue',
'badge color is set');
let { node } = getWidget(button.id);
let { getComputedStyle } = node.ownerDocument.defaultView;
let badgeNode = badgeNodeFor(node);
assert.equal(badgeNodeFor(node).style.backgroundColor, 'blue',
'badge color is displayed properly');
assert.equal(getComputedStyle(badgeNode).backgroundColor, 'rgb(0, 0, 255)',
'badge color overrides the default one');
loader.unload();
}
// toggle button only
exports['test button checked'] = function(assert, done) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');

View File

@ -6,6 +6,9 @@
var BaseAssert = require("sdk/test/assert").Assert;
const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x),
...Object.getOwnPropertySymbols(x)];
/**
* Whether or not given property descriptors are equivalent. They are
* equivalent either if both are marked as "conflict" or "required" property
@ -52,7 +55,7 @@ function equivalentSets(source, target) {
*/
function findNonEquivalentPropertyName(source, target) {
var value = null;
Object.getOwnPropertyNames(source).some(function(key) {
getOwnIdentifiers(source).some(function(key) {
var areEquivalent = false;
if (!equivalentDescriptors(source[key], target[key])) {
value = key;
@ -67,8 +70,8 @@ var AssertDescriptor = {
equalTraits: {
value: function equivalentTraits(actual, expected, message) {
var difference;
var actualKeys = Object.getOwnPropertyNames(actual);
var expectedKeys = Object.getOwnPropertyNames(expected);
var actualKeys = getOwnIdentifiers(actual);
var expectedKeys = getOwnIdentifiers(expected);
if (equivalentSets(actualKeys, expectedKeys)) {
this.fail({

View File

@ -319,6 +319,11 @@ pref("dom.indexedDB.warningQuota", 5);
pref("media.preload.default", 1); // default to preload none
pref("media.preload.auto", 2); // preload metadata if preload=auto
pref("media.cache_size", 4096); // 4MB media cache
// Try to save battery by not resuming reading from a connection until we fall
// below 10s of buffered data.
pref("media.cache_resume_threshold", 10);
pref("media.cache_readahead_limit", 30);
#ifdef MOZ_FMP4
// Enable/Disable Gonk Decoder Module
pref("media.fragmented-mp4.gonk.enabled", false);

View File

@ -8,13 +8,11 @@ let SocialUI,
SocialMarks,
SocialShare,
SocialSidebar,
SocialStatus;
SocialStatus,
SocialActivationListener;
(function() {
XPCOMUtils.defineLazyModuleGetter(this, "SharedFrame",
"resource:///modules/SharedFrame.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame",
"resource:///modules/PanelFrame.jsm");
@ -73,8 +71,8 @@ SocialUI = {
Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
CustomizableUI.addListener(this);
SocialActivationListener.init();
// menupopups that list social providers. we only populate them when shown,
// and if it has not been done already.
@ -108,6 +106,7 @@ SocialUI = {
Services.prefs.removeObserver("social.toast-notifications.enabled", this);
CustomizableUI.removeListener(this);
SocialActivationListener.uninit();
document.getElementById("viewSidebarMenu").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
document.getElementById("social-statusarea-popup").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
@ -174,95 +173,6 @@ SocialUI = {
SocialMarks.populateToolbarPalette();
},
// This handles "ActivateSocialFeature" events fired against content documents
// in this window. If this activation happens from within Firefox, such as
// about:home or the share panel, we bypass the enable prompt. Any website
// activation, such as from the activations directory or a providers website
// will still get the prompt.
_activationEventHandler: function SocialUI_activationHandler(e, options={}) {
let targetDoc;
let node;
if (e.target instanceof HTMLDocument) {
// version 0 support
targetDoc = e.target;
node = targetDoc.documentElement
} else {
targetDoc = e.target.ownerDocument;
node = e.target;
}
if (!(targetDoc instanceof HTMLDocument))
return;
// The share panel iframe will not match "content" so it passes a bypass
// flag
if (!options.bypassContentCheck && targetDoc.defaultView != content)
return;
// If we are in PB mode, we silently do nothing (bug 829404 exists to
// do something sensible here...)
if (PrivateBrowsingUtils.isWindowPrivate(window))
return;
// If the last event was received < 1s ago, ignore this one
let now = Date.now();
if (now - Social.lastEventReceived < 1000)
return;
Social.lastEventReceived = now;
// We only want to activate if it is as a result of user input.
let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
if (!dwu.isHandlingUserInput) {
Cu.reportError("attempt to activate provider without user input from " + targetDoc.nodePrincipal.origin);
return;
}
let data = node.getAttribute("data-service");
if (data) {
try {
data = JSON.parse(data);
} catch(e) {
Cu.reportError("Social Service manifest parse error: "+e);
return;
}
}
Social.installProvider(targetDoc, data, function(manifest) {
Social.activateFromOrigin(manifest.origin, function(provider) {
if (provider.sidebarURL) {
SocialSidebar.show(provider.origin);
}
if (provider.shareURL) {
// Ensure that the share button is somewhere usable.
// SocialShare.shareButton may return null if it is in the menu-panel
// and has never been visible, so we check the widget directly. If
// there is no area for the widget we move it into the toolbar.
let widget = CustomizableUI.getWidget("social-share-button");
if (!widget.areaType) {
CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
// ensure correct state
SocialUI.onCustomizeEnd(window);
}
// make this new provider the selected provider. If the panel hasn't
// been opened, we need to make the frame first.
SocialShare._createFrame();
SocialShare.iframe.setAttribute('src', 'data:text/plain;charset=utf8,');
SocialShare.iframe.setAttribute('origin', provider.origin);
// get the right button selected
SocialShare.populateProviderMenu();
if (SocialShare.panel.state == "open") {
SocialShare.sharePage(provider.origin);
}
}
if (provider.postActivationURL) {
// if activated from an open share panel, we load the landing page in
// a background tab
gBrowser.loadOneTab(provider.postActivationURL, {inBackground: SocialShare.panel.state == "open"});
}
});
}, options);
},
showLearnMore: function() {
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
openUILinkIn(url, "tab");
@ -356,6 +266,68 @@ SocialUI = {
}
}
// message manager handlers
SocialActivationListener = {
init: function() {
messageManager.addMessageListener("Social:Activation", this);
},
uninit: function() {
messageManager.removeMessageListener("Social:Activation", this);
},
receiveMessage: function(aMessage) {
let data = aMessage.json;
let browser = aMessage.target;
data.window = window;
// if the source if the message is the share panel, we do a one-click
// installation. The source of activations is controlled by the
// social.directories preference
let options;
if (browser == SocialShare.iframe && Services.prefs.getBoolPref("social.share.activationPanelEnabled")) {
options = { bypassContentCheck: true, bypassInstallPanel: true };
}
// If we are in PB mode, we silently do nothing (bug 829404 exists to
// do something sensible here...)
if (PrivateBrowsingUtils.isWindowPrivate(window))
return;
Social.installProvider(data, function(manifest) {
Social.activateFromOrigin(manifest.origin, function(provider) {
if (provider.sidebarURL) {
SocialSidebar.show(provider.origin);
}
if (provider.shareURL) {
// Ensure that the share button is somewhere usable.
// SocialShare.shareButton may return null if it is in the menu-panel
// and has never been visible, so we check the widget directly. If
// there is no area for the widget we move it into the toolbar.
let widget = CustomizableUI.getWidget("social-share-button");
if (!widget.areaType) {
CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
// ensure correct state
SocialUI.onCustomizeEnd(window);
}
// make this new provider the selected provider. If the panel hasn't
// been opened, we need to make the frame first.
SocialShare._createFrame();
SocialShare.iframe.setAttribute('src', 'data:text/plain;charset=utf8,');
SocialShare.iframe.setAttribute('origin', provider.origin);
// get the right button selected
SocialShare.populateProviderMenu();
if (SocialShare.panel.state == "open") {
SocialShare.sharePage(provider.origin);
}
}
if (provider.postActivationURL) {
// if activated from an open share panel, we load the landing page in
// a background tab
gBrowser.loadOneTab(provider.postActivationURL, {inBackground: SocialShare.panel.state == "open"});
}
});
}, options);
}
}
SocialFlyout = {
get panel() {
return document.getElementById("social-flyout-panel");
@ -517,15 +489,8 @@ SocialShare = {
return this.panel.lastChild;
},
_activationHandler: function(event) {
if (!Services.prefs.getBoolPref("social.share.activationPanelEnabled"))
return;
SocialUI._activationEventHandler(event, { bypassContentCheck: true, bypassInstallPanel: true });
},
uninit: function () {
if (this.iframe) {
this.iframe.removeEventListener("ActivateSocialFeature", this._activationHandler, true, true);
this.iframe.remove();
}
},
@ -544,7 +509,10 @@ SocialShare = {
iframe.setAttribute("disableglobalhistory", "true");
iframe.setAttribute("flex", "1");
panel.appendChild(iframe);
this.iframe.addEventListener("ActivateSocialFeature", this._activationHandler, true, true);
iframe.addEventListener("DOMContentLoaded", function _firstload() {
iframe.removeEventListener("DOMContentLoaded", _firstload, true);
iframe.messageManager.loadFrameScript("chrome://browser/content/content.js", true);
}, true);
this.populateProviderMenu();
},
@ -671,18 +639,31 @@ SocialShare = {
// endpoints (e.g. oexchange) that do not support additional
// socialapi functionality. One tweak is that we shoot an event
// containing the open graph data.
let _dataFn;
if (!pageData || sharedURI == gBrowser.currentURI) {
pageData = OpenGraphBuilder.getData(gBrowser);
if (graphData) {
// overwrite data retreived from page with data given to us as a param
for (let p in graphData) {
pageData[p] = graphData[p];
messageManager.addMessageListener("Social:PageDataResult", _dataFn = (msg) => {
messageManager.removeMessageListener("Social:PageDataResult", _dataFn);
let pageData = msg.json;
if (graphData) {
// overwrite data retreived from page with data given to us as a param
for (let p in graphData) {
pageData[p] = graphData[p];
}
}
}
this.sharePage(providerOrigin, pageData, target);
});
gBrowser.selectedBrowser.messageManager.sendAsyncMessage("Social:GetPageData");
return;
}
// if this is a share of a selected item, get any microdata
if (!pageData.microdata && target) {
pageData.microdata = OpenGraphBuilder.getMicrodata(gBrowser, target);
messageManager.addMessageListener("Social:PageDataResult", _dataFn = (msg) => {
messageManager.removeMessageListener("Social:PageDataResult", _dataFn);
pageData.microdata = msg.data;
this.sharePage(providerOrigin, pageData, target);
});
gBrowser.selectedBrowser.messageManager.sendAsyncMessage("Social:GetMicrodata", null, target);
return;
}
this.currentShare = pageData;
@ -1267,7 +1248,6 @@ SocialStatus = {
let notificationFrameId = "social-status-" + origin;
let frame = document.getElementById(notificationFrameId);
if (frame) {
SharedFrame.forgetGroup(frame.id);
frame.parentNode.removeChild(frame);
}
},

View File

@ -3972,7 +3972,7 @@ var XULBrowserWindow = {
.chromeEventHandler;
// Ignore loads that aren't in the main tabbrowser
if (browser.localName != "browser" || browser.getTabBrowser() != gBrowser)
if (browser.localName != "browser" || !browser.getTabBrowser || browser.getTabBrowser() != gBrowser)
return true;
if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {

View File

@ -837,6 +837,66 @@ addEventListener("pageshow", function(event) {
}
});
let SocialMessenger = {
init: function() {
addMessageListener("Social:GetPageData", this);
addMessageListener("Social:GetMicrodata", this);
XPCOMUtils.defineLazyGetter(this, "og", function() {
let tmp = {};
Cu.import("resource:///modules/Social.jsm", tmp);
return tmp.OpenGraphBuilder;
});
},
receiveMessage: function(aMessage) {
switch(aMessage.name) {
case "Social:GetPageData":
sendAsyncMessage("Social:PageDataResult", this.og.getData(content.document));
break;
case "Social:GetMicrodata":
let target = aMessage.objects;
sendAsyncMessage("Social:PageDataResult", this.og.getMicrodata(content.document, target));
break;
}
}
}
SocialMessenger.init();
addEventListener("ActivateSocialFeature", function (aEvent) {
let document = content.document;
if (PrivateBrowsingUtils.isContentWindowPrivate(content)) {
Cu.reportError("cannot use social providers in private windows");
return;
}
let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
if (!dwu.isHandlingUserInput) {
Cu.reportError("attempt to activate provider without user input from " + document.nodePrincipal.origin);
return;
}
let node = aEvent.target;
let ownerDocument = node.ownerDocument;
let data = node.getAttribute("data-service");
if (data) {
try {
data = JSON.parse(data);
} catch(e) {
Cu.reportError("Social Service manifest parse error: " + e);
return;
}
} else {
Cu.reportError("Social Service manifest not available");
return;
}
sendAsyncMessage("Social:Activation", {
url: ownerDocument.location.href,
origin: ownerDocument.nodePrincipal.origin,
manifest: data
});
}, true, true);
addMessageListener("ContextMenu:SaveVideoFrameAsImage", (message) => {
let video = message.objects.target;
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");

View File

@ -148,10 +148,24 @@
panel.appendChild(this.content);
let URLTemplate = provider.markURL;
pageData = pageData || OpenGraphBuilder.getData(gBrowser);
let _dataFn;
if (!pageData) {
messageManager.addMessageListener("Social:PageDataResult", _dataFn = (msg) => {
messageManager.removeMessageListener("Social:PageDataResult", _dataFn);
this.loadPanel(msg.json, target);
});
gBrowser.selectedBrowser.messageManager.sendAsyncMessage("Social:GetPageData");
return;
}
// if this is a share of a selected item, get any microdata
if (!pageData.microdata && target) {
pageData.microdata = OpenGraphBuilder.getMicrodata(gBrowser, target);
messageManager.addMessageListener("Social:PageDataResult", _dataFn = (msg) => {
messageManager.removeMessageListener("Social:PageDataResult", _dataFn);
pageData.microdata = msg.data;
this.loadPanel(pageData, target);
});
gBrowser.selectedBrowser.messageManager.sendAsyncMessage("Social:GetMicrodata", null, target);
return;
}
this.pageData = pageData;

View File

@ -57,6 +57,8 @@ function test() {
testVal("http://localhost");
testVal("http://someotherhostwithnodots");
testVal("http://localhost/ foo bar baz");
testVal("http://localhost.localdomain/ foo bar baz", "localhost.localdomain/ foo bar baz");
Services.prefs.setBoolPref(prefname, false);

View File

@ -27,34 +27,29 @@ support-files =
unchecked.jpg
[browser_aboutHome_activation.js]
skip-if = e10s # Bug 915547 (social providers don't install)
skip-if = e10s # Bug 1053965 "cw.ensureSnippetsMapThen is not a function", also see general/browser.ini about:home comments
[browser_addons.js]
skip-if = e10s # Bug 915547 (social providers don't install)
[browser_blocklist.js]
skip-if = e10s # Bug 915547 (social providers don't install)
[browser_share.js]
skip-if = e10s # Bug 915547 (social providers don't install)
[browser_social_activation.js]
skip-if = e10s # Bug 915547 (social providers don't install)
skip-if = e10s # Bug 933103 synthesizeMouseAtCenter not e10s friendly
[browser_social_chatwindow.js]
skip-if = e10s # Bug 915547 (social providers don't install)
[browser_social_chatwindow_resize.js]
skip-if = e10s # Bug 915547 (social providers don't install)
[browser_social_chatwindowfocus.js]
skip-if = e10s # Bug 915547 (social providers don't install)
skip-if = e10s # tab crash on data url used in this test
[browser_social_contextmenu.js]
skip-if = e10s # Bug 1072669 context menu relies on target element
[browser_social_errorPage.js]
[browser_social_flyout.js]
skip-if = e10s # when we backed out bug 1047603, this test broke.
[browser_social_isVisible.js]
[browser_social_marks.js]
skip-if = e10s # Bug 915547 (social providers don't install)
[browser_social_multiprovider.js]
skip-if = e10s # Bug 1069162 - lots of orange
[browser_social_multiworker.js]
[browser_social_perwindowPB.js]
[browser_social_sidebar.js]
[browser_social_status.js]
skip-if = e10s # Bug 915547 (social providers don't install)
[browser_social_window.js]
[browser_social_workercrash.js]
#skip-if = !crashreporter

View File

@ -193,7 +193,13 @@ var tests = {
let installFrom = doc.nodePrincipal.origin;
Services.prefs.setCharPref("social.whitelist", "");
is(SocialService.getOriginActivationType(installFrom), "foreign", "testing foriegn install");
Social.installProvider(doc, manifest2, function(addonManifest) {
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest2,
window: window
}
Social.installProvider(data, function(addonManifest) {
Services.prefs.clearUserPref("social.whitelist");
SocialService.enableProvider(addonManifest.origin, function(provider) {
Social.uninstallProvider(addonManifest.origin);
@ -217,7 +223,13 @@ var tests = {
let installFrom = doc.nodePrincipal.origin;
Services.prefs.setCharPref("social.directories", installFrom);
is(SocialService.getOriginActivationType(installFrom), "directory", "testing directory install");
Social.installProvider(doc, manifest2, function(addonManifest) {
let data = {
origin: installFrom,
url: doc.location.href,
manifest: manifest2,
window: window
}
Social.installProvider(data, function(addonManifest) {
Services.prefs.clearUserPref("social.directories");
SocialService.enableProvider(addonManifest.origin, function(provider) {
Social.uninstallProvider(addonManifest.origin);
@ -241,7 +253,13 @@ var tests = {
let doc = tab.linkedBrowser.contentDocument;
let installFrom = doc.nodePrincipal.origin;
Services.prefs.setCharPref("social.whitelist", installFrom);
Social.installProvider(doc, manifest2, function(addonManifest) {
let data = {
origin: installFrom,
url: doc.location.href,
manifest: manifest2,
window: window
}
Social.installProvider(data, function(addonManifest) {
SocialService.enableProvider(addonManifest.origin, function(provider) {
is(provider.manifest.version, 1, "manifest version is 1");

View File

@ -113,7 +113,13 @@ var tests = {
try {
// expecting an exception when attempting to install a hard blocked
// provider
Social.installProvider(doc, manifest_bad, function(addonManifest) {
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest_bad,
window: window
}
Social.installProvider(data, function(addonManifest) {
gBrowser.removeTab(tab);
finishTest(false);
});

View File

@ -130,7 +130,7 @@ var tests = {
testShareDisabledOnActivation: function(next) {
// starting on about:blank page, share should be visible but disabled when
// adding provider
is(gBrowser.contentDocument.location.href, "about:blank");
is(gBrowser.currentURI.spec, "about:blank");
// initialize the button into the navbar
CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);

View File

@ -86,9 +86,6 @@ function activateIFrameProvider(domain, callback) {
}
function waitForProviderLoad(cb) {
Services.obs.addObserver(function providerSet(subject, topic, data) {
Services.obs.removeObserver(providerSet, "social:provider-enabled");
info("social:provider-enabled observer was notified");
waitForCondition(function() {
let sbrowser = document.getElementById("social-sidebar-browser");
let provider = SocialSidebar.provider;
@ -104,7 +101,6 @@ function waitForProviderLoad(cb) {
executeSoon(cb);
},
"waitForProviderLoad: provider profile was not set");
}, "social:provider-enabled", false);
}
@ -149,14 +145,15 @@ function activateOneProvider(manifest, finishActivation, aCallback) {
let panel = document.getElementById("servicesInstall-notification");
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
info("servicesInstall-notification panel opened");
ok(!panel.hidden, "servicesInstall-notification panel opened");
if (finishActivation)
panel.button.click();
else
panel.closebutton.click();
});
activateProvider(manifest.origin, function() {
PopupNotifications.panel.addEventListener("popuphidden", function _hidden() {
PopupNotifications.panel.removeEventListener("popuphidden", _hidden);
ok(panel.hidden, "servicesInstall-notification panel hidden");
if (!finishActivation) {
ok(panel.hidden, "activation panel is not showing");
executeSoon(aCallback);
@ -169,6 +166,11 @@ function activateOneProvider(manifest, finishActivation, aCallback) {
});
}
});
// the test will continue as the popup events fire...
activateProvider(manifest.origin, function() {
info("waiting on activation panel to open/close...");
});
}
let gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"];

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/. */
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
let manifest = { // used for testing install
name: "provider test1",
origin: "https://test1.example.com",
workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
markURL: "https://test1.example.com/browser/browser/base/content/test/social/social_mark.html?url=%{url}",
markedIcon: "https://test1.example.com/browser/browser/base/content/test/social/unchecked.jpg",
unmarkedIcon: "https://test1.example.com/browser/browser/base/content/test/social/checked.jpg",
iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
version: 1
};
function test() {
waitForExplicitFinish();
runSocialTestWithProvider(manifest, function (finishcb) {
runSocialTests(tests, undefined, undefined, function () {
finishcb();
});
});
}
var tests = {
testMarkMicrodata: function(next) {
// emulates context menu action using target element, calling SocialMarks.markLink
let provider = Social._getProviderFromOrigin(manifest.origin);
let port = provider.getWorkerPort();
let target, testTab;
// browser_share tests microdata on the full page, this is testing a
// specific target element.
let expecting = JSON.stringify({
"url": "https://example.com/browser/browser/base/content/test/social/microdata.html",
"microdata": {
"items": [{
"types": ["http://schema.org/UserComments"],
"properties": {
"url": ["https://example.com/browser/browser/base/content/test/social/microdata.html#c2"],
"creator": [{
"types": ["http://schema.org/Person"],
"properties": {
"name": ["Charlotte"]
}
}
],
"commentTime": ["2013-08-29"]
}
}
]
}
});
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-share-data-message":
is(JSON.stringify(e.data.result), expecting, "microdata data ok");
gBrowser.removeTab(testTab);
port.close();
next();
break;
}
}
port.postMessage({topic: "test-init"});
let url = "https://example.com/browser/browser/base/content/test/social/microdata.html"
addTab(url, function(tab) {
testTab = tab;
let doc = tab.linkedBrowser.contentDocument;
target = doc.getElementById("test-comment");
SocialMarks.markLink(manifest.origin, url, target);
});
}
}

View File

@ -43,15 +43,6 @@ function makeMarkProvider(origin) {
}
}
function openWindowAndWaitForInit(callback) {
let topic = "browser-delayed-startup-finished";
let w = OpenBrowserWindow();
Services.obs.addObserver(function providerSet(subject, topic, data) {
Services.obs.removeObserver(providerSet, topic);
executeSoon(() => callback(w));
}, topic, false);
}
function test() {
waitForExplicitFinish();
@ -105,7 +96,14 @@ var tests = {
let activationURL = manifest3.origin + "/browser/browser/base/content/test/social/social_activate.html"
addTab(activationURL, function(tab) {
let doc = tab.linkedBrowser.contentDocument;
Social.installProvider(doc, manifest3, function(addonManifest) {
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest3,
window: window
}
Social.installProvider(data, function(addonManifest) {
// enable the provider so we know the button would have appeared
SocialService.enableProvider(manifest3.origin, function(provider) {
is(provider.origin, manifest3.origin, "provider is installed");
@ -133,7 +131,14 @@ var tests = {
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
addTab(activationURL, function(tab) {
let doc = tab.linkedBrowser.contentDocument;
Social.installProvider(doc, manifest2, function(addonManifest) {
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest2,
window: window
}
Social.installProvider(data, function(addonManifest) {
SocialService.enableProvider(manifest2.origin, function(provider) {
is(provider.origin, manifest2.origin, "provider is installed");
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
@ -280,56 +285,6 @@ var tests = {
});
},
testMarkMicrodata: function(next) {
let provider = Social._getProviderFromOrigin(manifest2.origin);
let port = provider.getWorkerPort();
let target, testTab;
// browser_share tests microdata on the full page, this is testing a
// specific target element.
let expecting = JSON.stringify({
"url": "https://example.com/browser/browser/base/content/test/social/microdata.html",
"microdata": {
"items": [{
"types": ["http://schema.org/UserComments"],
"properties": {
"url": ["https://example.com/browser/browser/base/content/test/social/microdata.html#c2"],
"creator": [{
"types": ["http://schema.org/Person"],
"properties": {
"name": ["Charlotte"]
}
}
],
"commentTime": ["2013-08-29"]
}
}
]
}
});
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-share-data-message":
is(JSON.stringify(e.data.result), expecting, "microdata data ok");
gBrowser.removeTab(testTab);
port.close();
next();
break;
}
}
port.postMessage({topic: "test-init"});
let url = "https://example.com/browser/browser/base/content/test/social/microdata.html"
addTab(url, function(tab) {
testTab = tab;
let doc = tab.linkedBrowser.contentDocument;
target = doc.getElementById("test-comment");
SocialMarks.markLink(manifest2.origin, url, target);
});
},
testButtonOnDisable: function(next) {
// enable the provider now
let provider = Social._getProviderFromOrigin(manifest2.origin);
@ -379,7 +334,14 @@ var tests = {
let toolbar = document.getElementById("nav-bar");
addTab(activationURL, function(tab) {
let doc = tab.linkedBrowser.contentDocument;
Social.installProvider(doc, manifest, function(addonManifest) {
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest,
window: window
}
Social.installProvider(data, function(addonManifest) {
// enable the provider so we know the button would have appeared
SocialService.enableProvider(manifest.origin, function(provider) {
waitForCondition(function() { return CustomizableUI.getWidget(id) },

View File

@ -66,7 +66,13 @@ var tests = {
let activationURL = manifest3.origin + "/browser/browser/base/content/test/social/social_activate.html"
addTab(activationURL, function(tab) {
let doc = tab.linkedBrowser.contentDocument;
Social.installProvider(doc, manifest3, function(addonManifest) {
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest3,
window: window
}
Social.installProvider(data, function(addonManifest) {
// enable the provider so we know the button would have appeared
SocialService.enableProvider(manifest3.origin, function(provider) {
is(provider.origin, manifest3.origin, "provider is installed");
@ -93,7 +99,14 @@ var tests = {
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
addTab(activationURL, function(tab) {
let doc = tab.linkedBrowser.contentDocument;
Social.installProvider(doc, manifest2, function(addonManifest) {
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest2,
window: window
}
Social.installProvider(data, function(addonManifest) {
SocialService.enableProvider(manifest2.origin, function(provider) {
is(provider.origin, manifest2.origin, "provider is installed");
let id = SocialStatus._toolbarHelper.idFromOrigin(manifest2.origin);

View File

@ -31,16 +31,11 @@ function activate(node) {
node.dispatchEvent(event);
}
function oldActivate(node) {
var event = new CustomEvent("ActivateSocialFeature");
document.dispatchEvent(event);
}
</script>
<body>
nothing to see here
<button id="activation-old" onclick="oldActivate(this)">Activate The Demo Provider</button>
<button id="activation" onclick="activate(this)">Activate The Demo Provider</button>
</body>

View File

@ -373,6 +373,7 @@ function injectLoopAPI(targetWindow) {
* Callback parameters:
* - err null on successful registration, non-null otherwise.
*
* @param {LOOP_SESSION_TYPE} sessionType
* @param {Function} callback Will be called once registration is complete,
* or straight away if registration has already
* happened.
@ -380,10 +381,10 @@ function injectLoopAPI(targetWindow) {
ensureRegistered: {
enumerable: true,
writable: true,
value: function(callback) {
value: function(sessionType, callback) {
// We translate from a promise to a callback, as we can't pass promises from
// Promise.jsm across the priv versus unpriv boundary.
MozLoopService.promiseRegisteredWithServers().then(() => {
MozLoopService.promiseRegisteredWithServers(sessionType).then(() => {
callback(null);
}, err => {
callback(cloneValueInto(err, targetWindow));

View File

@ -106,10 +106,6 @@ function getJSONPref(aName) {
return !!value ? JSON.parse(value) : null;
}
// The current deferred for the registration process. This is set if in progress
// or the registration was successful. This is null if a registration attempt was
// unsuccessful.
let gRegisteredDeferred = null;
let gHawkClient = null;
let gLocalizedStrings = null;
let gFxAEnabled = true;
@ -132,6 +128,13 @@ let MozLoopServiceInternal = {
webSocket: undefined,
},
/**
* The current deferreds for the registration processes. This is set if in progress
* or the registration was successful. This is null if a registration attempt was
* unsuccessful.
*/
deferredRegistrations: new Map(),
get pushHandler() this.mocks.pushHandler || MozLoopPushHandler,
// The uri of the Loop server.
@ -310,35 +313,37 @@ let MozLoopServiceInternal = {
},
/**
* Starts registration of Loop with the push server, and then will register
* with the Loop server as a GUEST. It will return early if already registered.
* Get endpoints with the push server and register for notifications.
* For now we register as both a Guest and FxA user and all must succeed.
*
* @returns {Promise} a promise that is resolved with no params on completion, or
* rejected with an error code or string.
* @return {Promise} resolves with all push endpoints
* rejects if any of the push registrations failed
*/
promiseRegisteredWithServers: function() {
if (gRegisteredDeferred) {
return gRegisteredDeferred.promise;
}
promiseRegisteredWithPushServer: function() {
// Wrap push notification registration call-back in a Promise.
let registerForNotification = function(channelID, onNotification) {
function registerForNotification(channelID, onNotification) {
log.debug("registerForNotification", channelID);
return new Promise((resolve, reject) => {
let onRegistered = (error, pushUrl) => {
function onRegistered(error, pushUrl) {
log.debug("registerForNotification onRegistered:", error, pushUrl);
if (error) {
reject(Error(error));
} else {
resolve(pushUrl);
}
};
}
// If we're already registered, resolve with the existing push URL
let pushURL = MozLoopServiceInternal.pushHandler.registeredChannels[channelID];
if (pushURL) {
log.debug("Using the existing push endpoint for channelID:", channelID);
resolve(pushURL);
return;
}
MozLoopServiceInternal.pushHandler.register(channelID, onRegistered, onNotification);
});
};
gRegisteredDeferred = Promise.defer();
// We grab the promise early in case .initialize or its results sets
// it back to null on error.
let result = gRegisteredDeferred.promise;
}
let options = this.mocks.webSocket ? { mockWebSocket: this.mocks.webSocket } : {};
this.pushHandler.initialize(options);
@ -355,27 +360,42 @@ let MozLoopServiceInternal = {
let roomsRegFxA = registerForNotification(MozLoopService.channelIDs.roomsFxA,
roomsPushNotification);
Promise.all([callsRegGuest, roomsRegGuest, callsRegFxA, roomsRegFxA])
.then((pushUrls) => {
return this.registerWithLoopServer(LOOP_SESSION_TYPE.GUEST,{
calls: pushUrls[0],
rooms: pushUrls[1],
});
return Promise.all([callsRegGuest, roomsRegGuest, callsRegFxA, roomsRegFxA]);
},
/**
* Starts registration of Loop with the push server, and then will register
* with the Loop server. It will return early if already registered.
*
* @param {LOOP_SESSION_TYPE} sessionType
* @returns {Promise} a promise that is resolved with no params on completion, or
* rejected with an error code or string.
*/
promiseRegisteredWithServers: function(sessionType = LOOP_SESSION_TYPE.GUEST) {
if (this.deferredRegistrations.has(sessionType)) {
log.debug("promiseRegisteredWithServers: registration already completed or in progress:", sessionType);
return this.deferredRegistrations.get(sessionType).promise;
}
let result = null;
let deferred = Promise.defer();
log.debug("assigning to deferredRegistrations for sessionType:", sessionType);
this.deferredRegistrations.set(sessionType, deferred);
// We grab the promise early in case one of the callers below delete it from the map.
result = deferred.promise;
this.promiseRegisteredWithPushServer().then(() => {
return this.registerWithLoopServer(sessionType);
}).then(() => {
// storeSessionToken could have rejected and nulled the promise if the token was malformed.
if (!gRegisteredDeferred) {
return;
}
gRegisteredDeferred.resolve("registered to guest status");
deferred.resolve("registered to status:" + sessionType);
// No need to clear the promise here, everything was good, so we don't need
// to re-register.
}, error => {
log.error("Failed to register with Loop server: ", error);
// registerWithLoopServer may have already made this null.
if (gRegisteredDeferred) {
gRegisteredDeferred.reject(error);
}
gRegisteredDeferred = null;
log.error("Failed to register with Loop server with sessionType " + sessionType, error);
deferred.reject(error);
this.deferredRegistrations.delete(sessionType);
log.debug("Cleared deferredRegistration for sessionType:", sessionType);
});
return result;
@ -397,6 +417,7 @@ let MozLoopServiceInternal = {
* rejected with this JSON-parsed response.
*/
hawkRequest: function(sessionType, path, method, payloadObj) {
log.debug("hawkRequest: " + path, sessionType);
if (!gHawkClient) {
gHawkClient = new HawkClient(this.loopServerUri);
}
@ -488,15 +509,12 @@ let MozLoopServiceInternal = {
} else {
// XXX Bubble the precise details up to the UI somehow (bug 1013248).
log.warn("Loop server sent an invalid session token");
gRegisteredDeferred.reject("session-token-wrong-size");
gRegisteredDeferred = null;
return false;
}
}
return true;
},
/**
* Clear the loop session token so we don't use it for Hawk Requests anymore.
*
@ -512,45 +530,62 @@ let MozLoopServiceInternal = {
},
/**
* Registers with the Loop server either as a guest or a FxA user.
* Registers with the Loop server either as a guest or a FxA user. This method should only be
* called by promiseRegisteredWithServers since it prevents calling this while a registration is
* already in progress.
*
* @private
* @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
* @param {String} pushUrls The push url given by the push server.
* @param {Boolean} [retry=true] Whether to retry if authentication fails.
* @return {Promise}
*/
registerWithLoopServer: function(sessionType, pushUrls, retry = true) {
registerWithLoopServer: function(sessionType, retry = true) {
log.debug("registerWithLoopServer with sessionType:", sessionType);
let callsPushURL, roomsPushURL;
if (sessionType == LOOP_SESSION_TYPE.FXA) {
callsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA];
roomsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
} else if (sessionType == LOOP_SESSION_TYPE.GUEST) {
callsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.callsGuest];
roomsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsGuest];
}
if (!callsPushURL || !roomsPushURL) {
return Promise.reject("Invalid sessionType or missing push URLs for registerWithLoopServer: " + sessionType);
}
// create a registration payload with a backwards compatible attribute (simplePushURL)
// that will register only the calls notification.
let msg = {
simplePushURL: pushUrls.calls,
simplePushURLs: pushUrls
simplePushURL: callsPushURL,
simplePushURLs: {
calls: callsPushURL,
rooms: roomsPushURL,
},
};
return this.hawkRequest(sessionType, "/registration", "POST", msg)
.then((response) => {
// If this failed we got an invalid token. storeSessionToken rejects
// the gRegisteredDeferred promise for us, so here we just need to
// early return.
// If this failed we got an invalid token.
if (!this.storeSessionToken(sessionType, response.headers)) {
return;
return Promise.reject("session-token-wrong-size");
}
log.debug("Successfully registered with server for sessionType", sessionType);
this.clearError("registration");
return undefined;
}, (error) => {
// There's other errors than invalid auth token, but we should only do the reset
// as a last resort.
if (error.code === 401) {
// Authorization failed, invalid token, we need to try again with a new token.
if (retry) {
return this.registerWithLoopServer(sessionType, pushUrls, false);
return this.registerWithLoopServer(sessionType, false);
}
}
log.error("Failed to register with the loop server. Error: ", error);
this.setError("registration", error);
gRegisteredDeferred.reject(error);
gRegisteredDeferred = null;
throw error;
}
);
@ -567,7 +602,7 @@ let MozLoopServiceInternal = {
* Guest or FxA have been unregistered with the LoopServer.
*
* @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
* @param {String} pushURLs The push URL previously given by the push server.
* @param {String} pushURL The push URL previously given by the push server.
* This may not be necessary to unregister in the future.
* @return {Promise} resolving when the unregistration request finishes
*/
@ -970,28 +1005,27 @@ this.MozLoopService = {
});
try {
yield this.promiseRegisteredWithServers();
if (MozLoopServiceInternal.urlExpiryTimeIsInFuture()) {
yield this.promiseRegisteredWithServers(LOOP_SESSION_TYPE.GUEST);
} else {
log.debug("delayedInitialize: URL expiry time isn't in the future so not registering as a guest");
}
} catch (ex) {
log.debug("MozLoopService: Failure of initial registration", ex);
log.debug("MozLoopService: Failure of guest registration", ex);
deferredInitialization.reject(ex);
yield completedPromise;
return;
}
if (!MozLoopServiceInternal.fxAOAuthTokenData) {
log.debug("MozLoopService: Initialized without an already logged-in account");
deferredInitialization.resolve("initialized to guest status");
log.debug("delayedInitialize: Initialized without an already logged-in account");
deferredInitialization.resolve("initialized without FxA status");
yield completedPromise;
return;
}
log.debug("MozLoopService: Initializing with already logged-in account");
let pushURLs = {
calls: MozLoopServiceInternal.pushHandler.registeredChannels[this.channelIDs.callsFxA],
rooms: MozLoopServiceInternal.pushHandler.registeredChannels[this.channelIDs.roomsFxA]
};
MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURLs).then(() => {
MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
deferredInitialization.resolve("initialized to logged-in status");
}, error => {
log.debug("MozLoopService: error logging in using cached auth token");
@ -1113,8 +1147,8 @@ this.MozLoopService = {
/**
* @see MozLoopServiceInternal.promiseRegisteredWithServers
*/
promiseRegisteredWithServers: function() {
return MozLoopServiceInternal.promiseRegisteredWithServers();
promiseRegisteredWithServers: function(sessionType = LOOP_SESSION_TYPE.GUEST) {
return MozLoopServiceInternal.promiseRegisteredWithServers(sessionType);
},
/**
@ -1310,19 +1344,11 @@ this.MozLoopService = {
MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
return tokenData;
}).then(tokenData => {
return gRegisteredDeferred.promise.then(Task.async(function*() {
let callsUrl = MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA],
roomsUrl = MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
if (callsUrl && roomsUrl) {
yield MozLoopServiceInternal.registerWithLoopServer(
LOOP_SESSION_TYPE.FXA, {calls: callsUrl, rooms: roomsUrl});
} else {
throw new Error("No pushUrls for FxA registration");
}
return MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
MozLoopServiceInternal.clearError("login");
MozLoopServiceInternal.clearError("profile");
return MozLoopServiceInternal.fxAOAuthTokenData;
}));
});
}).then(tokenData => {
let client = new FxAccountsProfileClient({
serverURL: gFxAOAuthClient.parameters.profile_uri,
@ -1341,6 +1367,7 @@ this.MozLoopService = {
}).catch(error => {
MozLoopServiceInternal.fxAOAuthTokenData = null;
MozLoopServiceInternal.fxAOAuthProfile = null;
MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
throw error;
}).catch((error) => {
MozLoopServiceInternal.setError("login", error);
@ -1372,21 +1399,22 @@ this.MozLoopService = {
throw error;
} finally {
MozLoopServiceInternal.clearSessionToken(LOOP_SESSION_TYPE.FXA);
MozLoopServiceInternal.fxAOAuthTokenData = null;
MozLoopServiceInternal.fxAOAuthProfile = null;
MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
// Reset the client since the initial promiseFxAOAuthParameters() call is
// what creates a new session.
gFxAOAuthClient = null;
gFxAOAuthClientPromise = null;
// clearError calls notifyStatusChanged so should be done last when the
// state is clean.
MozLoopServiceInternal.clearError("registration");
MozLoopServiceInternal.clearError("login");
MozLoopServiceInternal.clearError("profile");
}
MozLoopServiceInternal.fxAOAuthTokenData = null;
MozLoopServiceInternal.fxAOAuthProfile = null;
// Reset the client since the initial promiseFxAOAuthParameters() call is
// what creates a new session.
gFxAOAuthClient = null;
gFxAOAuthClientPromise = null;
// clearError calls notifyStatusChanged so should be done last when the
// state is clean.
MozLoopServiceInternal.clearError("registration");
MozLoopServiceInternal.clearError("login");
MozLoopServiceInternal.clearError("profile");
}),
openFxASettings: Task.async(function() {

View File

@ -87,10 +87,11 @@ loop.Client = (function($) {
* Callback parameters:
* - err null on successful registration, non-null otherwise.
*
* @param {LOOP_SESSION_TYPE} sessionType Guest or FxA
* @param {Function} cb Callback(err)
*/
_ensureRegistered: function(cb) {
this.mozLoop.ensureRegistered(function(error) {
_ensureRegistered: function(sessionType, cb) {
this.mozLoop.ensureRegistered(sessionType, function(error) {
if (error) {
console.log("Error registering with Loop server, code: " + error);
cb(error);
@ -110,17 +111,11 @@ loop.Client = (function($) {
* -- callUrl: The url of the call
* -- expiresAt: The amount of hours until expiry of the url
*
* @param {LOOP_SESSION_TYPE} sessionType
* @param {string} nickname the nickname of the future caller
* @param {Function} cb Callback(err, callUrlData)
*/
_requestCallUrlInternal: function(nickname, cb) {
var sessionType;
if (this.mozLoop.userProfile) {
sessionType = this.mozLoop.LOOP_SESSION_TYPE.FXA;
} else {
sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST;
}
_requestCallUrlInternal: function(sessionType, nickname, cb) {
this.mozLoop.hawkRequest(sessionType, "/call-url/", "POST",
{callerId: nickname},
function (error, responseText) {
@ -159,7 +154,7 @@ loop.Client = (function($) {
* it does not make sense to display an error.
**/
deleteCallUrl: function(token, sessionType, cb) {
this._ensureRegistered(function(err) {
this._ensureRegistered(sessionType, function(err) {
if (err) {
cb(err);
return;
@ -206,13 +201,20 @@ loop.Client = (function($) {
* @param {Function} cb Callback(err, callUrlData)
*/
requestCallUrl: function(nickname, cb) {
this._ensureRegistered(function(err) {
var sessionType;
if (this.mozLoop.userProfile) {
sessionType = this.mozLoop.LOOP_SESSION_TYPE.FXA;
} else {
sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST;
}
this._ensureRegistered(sessionType, function(err) {
if (err) {
cb(err);
return;
}
this._requestCallUrlInternal(nickname, cb);
this._requestCallUrlInternal(sessionType, nickname, cb);
}.bind(this));
},

View File

@ -27,7 +27,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
*/
var HomeView = React.createClass({displayName: 'HomeView',
render: function() {
loop.standaloneMedia.multiplexGum.reset();
multiplexGum.reset();
return (
React.DOM.p(null, mozL10n.get("welcome", {clientShortname: mozL10n.get("clientShortname2")}))
);
@ -291,7 +291,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
_cancelOutgoingCall: function() {
loop.standaloneMedia.multiplexGum.reset();
multiplexGum.reset();
this.props.websocket.cancel();
},
@ -852,6 +852,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
* timeout, cancel, media-fail, user-unknown, closed)
*/
_handleCallTerminated: function(reason) {
multiplexGum.reset();
if (reason === "cancel") {
this.setState({callStatus: "start"});
return;

View File

@ -27,7 +27,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
*/
var HomeView = React.createClass({
render: function() {
loop.standaloneMedia.multiplexGum.reset();
multiplexGum.reset();
return (
<p>{mozL10n.get("welcome", {clientShortname: mozL10n.get("clientShortname2")})}</p>
);
@ -291,7 +291,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
_cancelOutgoingCall: function() {
loop.standaloneMedia.multiplexGum.reset();
multiplexGum.reset();
this.props.websocket.cancel();
},
@ -852,6 +852,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
* timeout, cancel, media-fail, user-unknown, closed)
*/
_handleCallTerminated: function(reason) {
multiplexGum.reset();
if (reason === "cancel") {
this.setState({callStatus: "start"});
return;

View File

@ -32,7 +32,7 @@ describe("loop.Client", function() {
.returns(null)
.withArgs("hawk-session-token")
.returns(fakeToken),
ensureRegistered: sinon.stub().callsArgWith(0, null),
ensureRegistered: sinon.stub().callsArgWith(1, null),
noteCallUrlExpiry: sinon.spy(),
hawkRequest: sinon.stub(),
LOOP_SESSION_TYPE: {
@ -62,7 +62,7 @@ describe("loop.Client", function() {
});
it("should send an error when registration fails", function() {
mozLoop.ensureRegistered.callsArgWith(0, "offline");
mozLoop.ensureRegistered.callsArgWith(1, "offline");
client.deleteCallUrl("fakeToken", mozLoop.LOOP_SESSION_TYPE.FXA, callback);
@ -113,7 +113,7 @@ describe("loop.Client", function() {
});
it("should send an error when registration fails", function() {
mozLoop.ensureRegistered.callsArgWith(0, "offline");
mozLoop.ensureRegistered.callsArgWith(1, "offline");
client.requestCallUrl("foo", callback);

View File

@ -44,6 +44,10 @@ describe("loop.conversation", function() {
calls: {
clearCallInProgress: sinon.stub()
},
LOOP_SESSION_TYPE: {
GUEST: 1,
FXA: 2
},
startAlerting: sinon.stub(),
stopAlerting: sinon.stub(),
ensureRegistered: sinon.stub(),

View File

@ -342,7 +342,7 @@ add_task(function* logoutWithIncorrectPushURL() {
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, {calls: pushURL});
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA);
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL");
mockPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA] = "http://www.example.com/invalid";
@ -365,7 +365,7 @@ add_task(function* logoutWithNoPushURL() {
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, {calls: pushURL});
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA);
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL");
mockPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA] = null;

View File

@ -120,7 +120,7 @@ function* resetFxA() {
global.gHawkClient = null;
global.gFxAOAuthClientPromise = null;
global.gFxAOAuthClient = null;
global.gRegisteredDeferred = null;
MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
MozLoopServiceInternal.fxAOAuthProfile = null;
MozLoopServiceInternal.fxAOAuthTokenData = null;
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);

View File

@ -231,6 +231,20 @@ describe("loop.webapp", function() {
loop.webapp.FailedConversationView);
});
it("should reset multiplexGum when a call is rejected",
function() {
var multiplexGum = new standaloneMedia._MultiplexGum();
standaloneMedia.setSingleton(multiplexGum);
sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
ocView._websocket.trigger("progress", {
state: "terminated",
reason: "reject"
});
sinon.assert.calledOnce(multiplexGum.reset);
});
it("should display an error message if the reason is not 'cancel'",
function() {
ocView._websocket.trigger("progress", {

View File

@ -229,6 +229,8 @@ add_task(function* setup_server() {
res.finish();
});
mockPushHandler.registrationPushURL = kEndPointUrl;
yield MozLoopService.promiseRegisteredWithServers();
});

View File

@ -24,6 +24,8 @@ let msgHandler = function(msg) {
add_test(function test_busy_2guest_calls() {
actionReceived = false;
mockPushHandler.registrationPushURL = kEndPointUrl;
MozLoopService.promiseRegisteredWithServers().then(() => {
let opened = 0;
let windowId;

View File

@ -31,6 +31,8 @@ add_test(function test_set_do_not_disturb() {
add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
MozLoopService.doNotDisturb = false;
mockPushHandler.registrationPushURL = kEndPointUrl;
MozLoopService.promiseRegisteredWithServers().then(() => {
let opened = false;
Chat.open = function() {

View File

@ -10,6 +10,8 @@ let openChatOrig = Chat.open;
add_test(function test_openChatWindow_on_notification() {
Services.prefs.setCharPref("loop.seenToS", "unseen");
mockPushHandler.registrationPushURL = kEndPointUrl;
MozLoopService.promiseRegisteredWithServers().then(() => {
let opened = false;
Chat.open = function() {

View File

@ -43,7 +43,7 @@ add_task(function test_initialize_with_urls_and_no_auth_token() {
});
yield MozLoopService.initialize().then((msg) => {
Assert.equal(msg, "initialized to guest status", "Initialize should register as a " +
Assert.equal(msg, "initialized without FxA status", "Initialize should register as a " +
"guest when no auth tokens but expired URLs");
}, (error) => {
Assert.ok(false, error, "should have resolved the promise that initialize returned");
@ -70,7 +70,7 @@ add_task(function test_initialize_with_invalid_fxa_token() {
Assert.ok(false, "Initializing with an invalid token should reject the promise");
},
(error) => {
Assert.equal(MozLoopServiceInternal.pushHandler.pushUrl, kEndPointUrl, "Push URL should match");
Assert.equal(MozLoopServiceInternal.pushHandler.registrationPushURL, kEndPointUrl, "Push URL should match");
Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), "",
"FXA pref should be cleared if token was invalid");
Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
@ -104,7 +104,7 @@ function run_test() {
// Note, this is just used to speed up the test.
Services.prefs.setIntPref(LOOP_INITIAL_DELAY_PREF, 0);
MozLoopServiceInternal.mocks.pushHandler = mockPushHandler;
mockPushHandler.pushUrl = kEndPointUrl;
mockPushHandler.registrationPushURL = kEndPointUrl;
do_register_cleanup(function() {
MozLoopServiceInternal.mocks.pushHandler = undefined;

View File

@ -46,6 +46,8 @@ add_test(function test_registration_invalid_token() {
function run_test() {
setupFakeLoopServer();
mockPushHandler.registrationPushURL = kEndPointUrl;
do_register_cleanup(function() {
Services.prefs.clearUserPref("loop.hawk-session-token");
});

Some files were not shown because too many files have changed in this diff Show More