mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
ece18fcd54
@ -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) {
|
||||
|
@ -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 -)
|
@ -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 -->
|
||||
|
@ -15,7 +15,3 @@ FINAL_TARGET_PP_FILES += [
|
||||
'bootstrap.js',
|
||||
'install.rdf',
|
||||
]
|
||||
|
||||
TEST_HARNESS_FILES.testing.mochitest.extensions += [
|
||||
'mozscreenshots-signed.xpi',
|
||||
]
|
||||
|
Binary file not shown.
@ -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);
|
||||
}
|
||||
|
129
toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
Normal file
129
toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
Normal 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));
|
||||
});
|
@ -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]
|
62
toolkit/modules/CanonicalJSON.jsm
Normal file
62
toolkit/modules/CanonicalJSON.jsm
Normal 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}`;
|
||||
}, "{") + "}";
|
||||
},
|
||||
};
|
@ -27,6 +27,7 @@ EXTRA_JS_MODULES += [
|
||||
'Battery.jsm',
|
||||
'BinarySearch.jsm',
|
||||
'BrowserUtils.jsm',
|
||||
'CanonicalJSON.jsm',
|
||||
'CertUtils.jsm',
|
||||
'CharsetMenu.jsm',
|
||||
'ClientID.jsm',
|
||||
|
146
toolkit/modules/tests/xpcshell/test_CanonicalJSON.js
Normal file
146
toolkit/modules/tests/xpcshell/test_CanonicalJSON.js
Normal 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();
|
||||
}
|
@ -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]
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user