Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-04-19 12:08:20 +02:00
commit ece18fcd54
14 changed files with 367 additions and 12 deletions

View File

@ -5,12 +5,20 @@
"use strict";
const {AddonWatcher} = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
const EXTENSION_DIR = "chrome://mochitests/content/extensions/mozscreenshots/browser/";
let TestRunner;
function setup() {
function* setup() {
requestLongerTimeout(20);
info("installing extension temporarily");
let chromeURL = Services.io.newURI(EXTENSION_DIR, null, null);
let dir = chromeRegistry.convertChromeURL(chromeURL).QueryInterface(Ci.nsIFileURL).file;
yield AddonManager.installTemporaryAddon(dir);
info("Checking for mozscreenshots extension");
return new Promise((resolve) => {
AddonManager.getAddonByID("mozscreenshots@mozilla.org", function(aAddon) {

View File

@ -0,0 +1,12 @@
# 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/.
TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
GENERATED_DIRS = $(TEST_EXTENSIONS_DIR)
XPI_PKGNAME = mozscreenshots@mozilla.org
include $(topsrcdir)/config/rules.mk
libs::
(cd $(DIST)/xpi-stage && tar $(TAR_CREATE_FLAGS) - $(XPI_NAME)) | (cd $(TEST_EXTENSIONS_DIR) && tar -xf -)

View File

@ -9,7 +9,7 @@
<Description about="urn:mozilla:install-manifest">
<em:id>mozscreenshots@mozilla.org</em:id>
<em:version>0.3.2</em:version>
#expand <em:version>__MOZILLA_VERSION_U__</em:version>
<em:bootstrap>true</em:bootstrap>
<!-- for running custom screenshot binaries -->

View File

@ -15,7 +15,3 @@ FINAL_TARGET_PP_FILES += [
'bootstrap.js',
'install.rdf',
]
TEST_HARNESS_FILES.testing.mochitest.extensions += [
'mozscreenshots-signed.xpi',
]

View File

@ -616,7 +616,7 @@ EventManager.prototype = {
for (let callback of this.callbacks) {
Promise.resolve(callback).then(callback => {
if (this.context.unloaded) {
dump(`${this.name} event fired after context unloaded.`);
dump(`${this.name} event fired after context unloaded.\n`);
} else if (this.callbacks.has(callback)) {
this.context.runSafe(callback, ...args);
}
@ -634,7 +634,7 @@ EventManager.prototype = {
if (this.callbacks.size) {
this.unregister();
}
this.callbacks = null;
this.callbacks = Object.freeze([]);
},
api() {
@ -661,7 +661,7 @@ SingletonEventManager.prototype = {
addListener(callback, ...args) {
let wrappedCallback = (...args) => {
if (this.context.unloaded) {
dump(`${this.name} event fired after context unloaded.`);
dump(`${this.name} event fired after context unloaded.\n`);
} else if (this.unregister.has(callback)) {
return callback(...args);
}

View File

@ -0,0 +1,129 @@
"use strict";
const global = this;
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
BaseContext,
EventManager,
SingletonEventManager,
} = ExtensionUtils;
class StubContext extends BaseContext {
constructor() {
super();
this.sandbox = new Cu.Sandbox(global);
}
get cloneScope() {
return this. sandbox;
}
get extension() {
return {id: "test@web.extension"};
}
}
add_task(function* test_post_unload_promises() {
let context = new StubContext();
let fail = result => {
ok(false, `Unexpected callback: ${result}`);
};
// Make sure promises resolve normally prior to unload.
let promises = [
context.wrapPromise(Promise.resolve()),
context.wrapPromise(Promise.reject({message: ""})).catch(() => {}),
];
yield Promise.all(promises);
// Make sure promises that resolve after unload do not trigger
// resolution handlers.
context.wrapPromise(Promise.resolve("resolved"))
.then(fail);
context.wrapPromise(Promise.reject({message: "rejected"}))
.then(fail, fail);
context.unload();
// The `setTimeout` ensures that we return to the event loop after
// promise resolution, which means we're guaranteed to return after
// any micro-tasks that get enqueued by the resolution handlers above.
yield new Promise(resolve => setTimeout(resolve, 0));
});
add_task(function* test_post_unload_listeners() {
let context = new StubContext();
let fireEvent;
let onEvent = new EventManager(context, "onEvent", fire => {
fireEvent = fire;
return () => {};
});
let fireSingleton;
let onSingleton = new SingletonEventManager(context, "onSingleton", callback => {
fireSingleton = () => {
Promise.resolve().then(callback);
};
return () => {};
});
let fail = event => {
ok(false, `Unexpected event: ${event}`);
};
// Check that event listeners aren't called after they've been removed.
onEvent.addListener(fail);
onSingleton.addListener(fail);
let promises = [
new Promise(resolve => onEvent.addListener(resolve)),
new Promise(resolve => onSingleton.addListener(resolve)),
];
fireEvent("onEvent");
fireSingleton("onSingleton");
// Both `fireEvent` calls are dispatched asynchronously, so they won't
// have fired by this point. The `fail` listeners that we remove now
// should not be called, even though the events have already been
// enqueued.
onEvent.removeListener(fail);
onSingleton.removeListener(fail);
// Wait for the remaining listeners to be called, which should always
// happen after the `fail` listeners would normally be called.
yield Promise.all(promises);
// Check that event listeners aren't called after the context has
// unloaded.
onEvent.addListener(fail);
onSingleton.addListener(fail);
// The EventManager `fire` callback always dispatches events
// asynchronously, so we need to test that any pending event callbacks
// aren't fired after the context unloads. We also need to test that
// any `fire` calls that happen *after* the context is unloaded also
// do not trigger callbacks.
fireEvent("onEvent");
Promise.resolve("onEvent").then(fireEvent);
fireSingleton("onSingleton");
Promise.resolve("onSingleton").then(fireSingleton);
context.unload();
// The `setTimeout` ensures that we return to the event loop after
// promise resolution, which means we're guaranteed to return after
// any micro-tasks that get enqueued by the resolution handlers above.
yield new Promise(resolve => setTimeout(resolve, 0));
});

View File

@ -6,5 +6,6 @@ skip-if = toolkit == 'gonk'
[test_locale_data.js]
[test_locale_converter.js]
[test_ext_contexts.js]
[test_ext_schemas.js]
[test_getAPILevelForWindow.js]

View File

@ -0,0 +1,62 @@
/* 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/. */
this.EXPORTED_SYMBOLS = ["CanonicalJSON"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "jsesc",
"resource://gre/modules/third_party/jsesc/jsesc.js");
this.CanonicalJSON = {
/**
* Return the canonical JSON form of the passed source, sorting all the object
* keys recursively. Note that this method will cause an infinite loop if
* cycles exist in the source (bug 1265357).
*
* @param source
* The elements to be serialized.
*
* The output will have all unicode chars escaped with the unicode codepoint
* as lowercase hexadecimal.
*
* @usage
* CanonicalJSON.stringify(listOfRecords);
**/
stringify: function stringify(source) {
if (Array.isArray(source)) {
const jsonArray = source.map(x => typeof x === "undefined" ? null : x);
return `[${jsonArray.map(stringify).join(",")}]`;
}
if (typeof source === "number") {
if (source === 0) {
return (Object.is(source, -0)) ? "-0" : "0";
}
}
// Leverage jsesc library, mainly for unicode escaping.
const toJSON = (input) => jsesc(input, {lowercaseHex: true, json: true});
if (typeof source !== "object" || source === null) {
return toJSON(source);
}
// Dealing with objects, ordering keys.
const sortedKeys = Object.keys(source).sort();
const lastIndex = sortedKeys.length - 1;
return sortedKeys.reduce((serial, key, index) => {
const value = source[key];
// JSON.stringify drops keys with an undefined value.
if (typeof value === "undefined") {
return serial;
}
const jsonValue = value && value.toJSON ? value.toJSON() : value;
const suffix = index !== lastIndex ? "," : "";
const escapedKey = toJSON(key);
return serial + `${escapedKey}:${stringify(jsonValue)}${suffix}`;
}, "{") + "}";
},
};

View File

@ -27,6 +27,7 @@ EXTRA_JS_MODULES += [
'Battery.jsm',
'BinarySearch.jsm',
'BrowserUtils.jsm',
'CanonicalJSON.jsm',
'CertUtils.jsm',
'CharsetMenu.jsm',
'ClientID.jsm',

View File

@ -0,0 +1,146 @@
const { CanonicalJSON } = Components.utils.import("resource://gre/modules/CanonicalJSON.jsm");
function stringRepresentation(obj) {
const clone = JSON.parse(JSON.stringify(obj));
return JSON.stringify(clone);
}
add_task(function* test_canonicalJSON_should_preserve_array_order() {
const input = ['one', 'two', 'three'];
// No sorting should be done on arrays.
do_check_eq(CanonicalJSON.stringify(input), '["one","two","three"]');
});
add_task(function* test_canonicalJSON_orders_object_keys() {
const input = [{
b: ['two', 'three'],
a: ['zero', 'one']
}];
do_check_eq(
CanonicalJSON.stringify(input),
'[{"a":["zero","one"],"b":["two","three"]}]'
);
});
add_task(function* test_canonicalJSON_orders_nested_object_keys() {
const input = [{
b: {d: 'd', c: 'c'},
a: {b: 'b', a: 'a'}
}];
do_check_eq(
CanonicalJSON.stringify(input),
'[{"a":{"a":"a","b":"b"},"b":{"c":"c","d":"d"}}]'
);
});
add_task(function* test_canonicalJSON_escapes_unicode_values() {
do_check_eq(
CanonicalJSON.stringify([{key: '✓'}]),
'[{"key":"\\u2713"}]'
);
// Unicode codepoints should be output in lowercase.
do_check_eq(
CanonicalJSON.stringify([{key: 'é'}]),
'[{"key":"\\u00e9"}]'
);
});
add_task(function* test_canonicalJSON_escapes_unicode_object_keys() {
do_check_eq(
CanonicalJSON.stringify([{'é': 'check'}]),
'[{"\\u00e9":"check"}]'
);
});
add_task(function* test_canonicalJSON_does_not_alter_input() {
const records = [
{'foo': 'bar', 'last_modified': '12345', 'id': '1'},
{'bar': 'baz', 'last_modified': '45678', 'id': '2'}
];
const serializedJSON = JSON.stringify(records);
CanonicalJSON.stringify(records);
do_check_eq(JSON.stringify(records), serializedJSON);
});
add_task(function* test_canonicalJSON_preserves_data() {
const records = [
{'foo': 'bar', 'last_modified': '12345', 'id': '1'},
{'bar': 'baz', 'last_modified': '45678', 'id': '2'},
]
const serialized = CanonicalJSON.stringify(records);
const expected = '[{"foo":"bar","id":"1","last_modified":"12345"},' +
'{"bar":"baz","id":"2","last_modified":"45678"}]';
do_check_eq(CanonicalJSON.stringify(records), expected);
});
add_task(function* test_canonicalJSON_does_not_add_space_separators() {
const records = [
{'foo': 'bar', 'last_modified': '12345', 'id': '1'},
{'bar': 'baz', 'last_modified': '45678', 'id': '2'},
]
const serialized = CanonicalJSON.stringify(records);
do_check_false(serialized.includes(" "));
});
add_task(function* test_canonicalJSON_serializes_empty_object() {
do_check_eq(CanonicalJSON.stringify({}), "{}");
});
add_task(function* test_canonicalJSON_serializes_empty_array() {
do_check_eq(CanonicalJSON.stringify([]), "[]");
});
add_task(function* test_canonicalJSON_serializes_NaN() {
do_check_eq(CanonicalJSON.stringify(NaN), "null");
});
add_task(function* test_canonicalJSON_serializes_inf() {
// This isn't part of the JSON standard.
do_check_eq(CanonicalJSON.stringify(Infinity), "null");
});
add_task(function* test_canonicalJSON_serializes_empty_string() {
do_check_eq(CanonicalJSON.stringify(""), '""');
});
add_task(function* test_canonicalJSON_escapes_backslashes() {
do_check_eq(CanonicalJSON.stringify("This\\and this"), '"This\\\\and this"');
});
add_task(function* test_canonicalJSON_handles_signed_zeros() {
// do_check_eq doesn't support comparison of -0 and 0 properly.
do_check_true(CanonicalJSON.stringify(-0) === '-0');
do_check_true(CanonicalJSON.stringify(0) === '0');
});
add_task(function* test_canonicalJSON_with_deeply_nested_dicts() {
const records = [{
'a': {
'b': 'b',
'a': 'a',
'c': {
'b': 'b',
'a': 'a',
'c': ['b', 'a', 'c'],
'd': {'b': 'b', 'a': 'a'},
'id': '1',
'e': 1,
'f': [2, 3, 1],
'g': {2: 2, 3: 3, 1: {
'b': 'b', 'a': 'a', 'c': 'c'}}}},
'id': '1'}]
const expected =
'[{"a":{"a":"a","b":"b","c":{"a":"a","b":"b","c":["b","a","c"],' +
'"d":{"a":"a","b":"b"},"e":1,"f":[2,3,1],"g":{' +
'"1":{"a":"a","b":"b","c":"c"},"2":2,"3":3},"id":"1"}},"id":"1"}]';
do_check_eq(CanonicalJSON.stringify(records), expected);
});
function run_test() {
run_next_test();
}

View File

@ -11,6 +11,7 @@ support-files =
[test_BinarySearch.js]
skip-if = toolkit == 'android'
[test_CanonicalJSON.js]
[test_client_id.js]
skip-if = toolkit == 'android'
[test_DeferredTask.js]

View File

@ -1293,7 +1293,7 @@
[this.mAddon.name], 1
);
this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableUpdatable.link");
this._errorLink.href = this.mAddon.blocklistURL;
this._errorLink.href = Services.urlFormatter.formatURLPref("plugins.update.url");
this._errorLink.hidden = false;
} else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
this.setAttribute("notification", "error");

View File

@ -88,7 +88,6 @@ add_task(function*() {
blocklistState: Ci.nsIBlocklistService.STATE_OUTDATED,
}, {
id: "addon8@tests.mozilla.org",
blocklistURL: "http://example.com/addon8@tests.mozilla.org",
name: "Test add-on 8",
blocklistState: Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE,
}, {
@ -395,7 +394,7 @@ add_task(function*() {
is(get_node(addon, "error").textContent, "Test add-on 8 is known to be vulnerable and should be updated.", "Error message should be correct");
is_element_visible(get_node(addon, "error-link"), "Error link should be visible");
is(get_node(addon, "error-link").value, "Update Now", "Error link text should be correct");
is(get_node(addon, "error-link").href, "http://example.com/addon8@tests.mozilla.org", "Error link should be correct");
is(get_node(addon, "error-link").href, gPluginURL, "Error link should be correct");
is_element_hidden(get_node(addon, "pending"), "Pending message should be hidden");
info("Addon 9");