merge fx-team to mozilla-inbound a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-03-17 11:32:02 +01:00
commit 3332e17023
109 changed files with 4574 additions and 2522 deletions

View File

@ -397,6 +397,7 @@ EXTRA_JS_MODULES.commonjs.sdk['private-browsing'] += [
EXTRA_JS_MODULES.commonjs.sdk.remote += [
'source/lib/sdk/remote/child.js',
'source/lib/sdk/remote/core.js',
'source/lib/sdk/remote/parent.js',
'source/lib/sdk/remote/utils.js',
]

View File

@ -21,6 +21,7 @@ const { getTabForContentWindow } = require('../tabs/utils');
const { getInnerId } = require('../window/utils');
const { PlainTextConsole } = require('../console/plain-text');
const { data } = require('../self');
const { isChildLoader } = require('../remote/core');
// WeakMap of sandboxes so we can access private values
const sandboxes = new WeakMap();
@ -47,6 +48,19 @@ const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
const JS_VERSION = '1.8';
// Tests whether this window is loaded in a tab
function isWindowInTab(window) {
if (isChildLoader) {
let { frames } = require('../remote/child');
let frame = frames.getFrameForWindow(window.top);
return frame.isTab;
}
else {
// The deprecated sync worker API still does everything in the main process
return getTabForContentWindow(window);
}
}
const WorkerSandbox = Class({
implements: [ EventTarget ],
@ -202,7 +216,7 @@ const WorkerSandbox = Class({
// Inject our `console` into target document if worker doesn't have a tab
// (e.g Panel, PageWorker, Widget).
// `worker.tab` can't be used because bug 804935.
if (!getTabForContentWindow(window)) {
if (!isWindowInTab(window)) {
let win = getUnsafeWindow(window);
// export our chrome console to content window, as described here:

View File

@ -3,6 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { isChildLoader } = require('./core');
if (!isChildLoader)
throw new Error("Cannot load sdk/remote/child in a main process loader.");
const { Ci, Cc } = require('chrome');
const runtime = require('../system/runtime');
const { Class } = require('../core/heritage');

View File

@ -0,0 +1,8 @@
/* 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 options = require("@loader/options");
exports.isChildLoader = options.childLoader;

View File

@ -3,6 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { isChildLoader } = require('./core');
if (isChildLoader)
throw new Error("Cannot load sdk/remote/parent in a child loader.");
const { Cu, Ci, Cc } = require('chrome');
const runtime = require('../system/runtime');
@ -53,6 +57,7 @@ catch (e) {
}
const loaderID = getNewLoaderID();
childOptions.loaderID = loaderID;
childOptions.childLoader = true;
const ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1'].
getService(Ci.nsIMessageBroadcaster);

View File

@ -492,8 +492,37 @@ exports["test processID"] = function*(assert) {
assert.equal(ID, processID, "Remote processes should have the same process ID");
}
}
loader.unload();
}
// Check that sdk/remote/parent and sdk/remote/child can only be loaded in the
// appropriate loaders
exports["test cannot load in wrong loader"] = function*(assert) {
let loader = new Loader(module);
let { processes } = yield waitForProcesses(loader);
try {
require('sdk/remote/child');
assert.fail("Should not have been able to load sdk/remote/child");
}
catch (e) {
assert.ok(/Cannot load sdk\/remote\/child in a main process loader/.test(e),
"Should have seen the right exception.");
}
for (let process of processes) {
processes.port.emit('sdk/test/parentload');
let [_, isChildLoader, loaded, message] = yield promiseEvent(processes.port, 'sdk/test/parentload');
assert.ok(isChildLoader, "Process should see itself in a child loader.");
assert.ok(!loaded, "Process couldn't load sdk/remote/parent.");
assert.ok(/Cannot load sdk\/remote\/parent in a child loader/.test(message),
"Should have seen the right exception.");
}
loader.unload();
};
after(exports, function*(name, assert) {
yield cleanUI();
});

View File

@ -8,6 +8,7 @@ const { loaderID } = require('@loader/options');
const { processID } = require('sdk/system/runtime');
const system = require('sdk/system/events');
const { Cu } = require('chrome');
const { isChildLoader } = require('sdk/remote/core');
function log(str) {
console.log("remote[" + loaderID + "][" + processID + "]: " + str);
@ -81,6 +82,24 @@ frames.port.on('sdk/test/sendevent', (frame) => {
frame.port.emit('sdk/test/eventsent');
});
process.port.on('sdk/test/parentload', () => {
let loaded = false;
let message = "";
try {
require('sdk/remote/parent');
loaded = true;
}
catch (e) {
message = "" + e;
}
process.port.emit('sdk/test/parentload',
isChildLoader,
loaded,
message
)
});
function listener(event) {
// Use the raw observer service here since it will be usable even if the
// loader has unloaded

View File

@ -492,8 +492,37 @@ exports["test processID"] = function*(assert) {
assert.equal(ID, processID, "Remote processes should have the same process ID");
}
}
loader.unload();
}
// Check that sdk/remote/parent and sdk/remote/child can only be loaded in the
// appropriate loaders
exports["test cannot load in wrong loader"] = function*(assert) {
let loader = new Loader(module);
let { processes } = yield waitForProcesses(loader);
try {
require('sdk/remote/child');
assert.fail("Should not have been able to load sdk/remote/child");
}
catch (e) {
assert.ok(/Cannot load sdk\/remote\/child in a main process loader/.test(e),
"Should have seen the right exception.");
}
for (let process of processes) {
processes.port.emit('sdk/test/parentload');
let [_, isChildLoader, loaded, message] = yield promiseEvent(processes.port, 'sdk/test/parentload');
assert.ok(isChildLoader, "Process should see itself in a child loader.");
assert.ok(!loaded, "Process couldn't load sdk/remote/parent.");
assert.ok(/Cannot load sdk\/remote\/parent in a child loader/.test(message),
"Should have seen the right exception.");
}
loader.unload();
};
after(exports, function*(name, assert) {
yield cleanUI();
});

View File

@ -8,6 +8,7 @@ const { loaderID } = require('@loader/options');
const { processID } = require('sdk/system/runtime');
const system = require('sdk/system/events');
const { Cu } = require('chrome');
const { isChildLoader } = require('sdk/remote/core');
function log(str) {
console.log("remote[" + loaderID + "][" + processID + "]: " + str);
@ -81,6 +82,24 @@ frames.port.on('sdk/test/sendevent', (frame) => {
frame.port.emit('sdk/test/eventsent');
});
process.port.on('sdk/test/parentload', () => {
let loaded = false;
let message = "";
try {
require('sdk/remote/parent');
loaded = true;
}
catch (e) {
message = "" + e;
}
process.port.emit('sdk/test/parentload',
isChildLoader,
loaded,
message
)
});
function listener(event) {
// Use the raw observer service here since it will be usable even if the
// loader has unloaded

View File

@ -2040,4 +2040,27 @@ exports.testUnloadWontAttach = function(assert, done) {
let tab = openTab(getMostRecentBrowserWindow(), TEST_URL);
}
// Tests that the SDK console isn't injected into documents loaded in tabs
exports.testDontInjectConsole = function(assert, done) {
const TEST_URL = 'data:text/html;charset=utf-8,consoleinject';
let loader = Loader(module);
let mod = PageMod({
include: TEST_URL,
contentScript: Isolate(function() {
// This relies on the fact that the SDK console doesn't have assert defined
self.postMessage((typeof unsafeWindow.console.assert) == "function");
}),
onMessage: isNativeConsole => {
assert.ok(isNativeConsole, "Shouldn't have injected the SDK console.");
mod.destroy();
closeTab(tab);
done();
}
});
let tab = openTab(getMostRecentBrowserWindow(), TEST_URL);
}
require('sdk/test').run(exports);

View File

@ -285,7 +285,7 @@ pref("browser.slowStartup.maxSamples", 5);
pref("browser.aboutHomeSnippets.updateUrl", "https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
pref("browser.enable_automatic_image_resizing", true);
pref("browser.casting.enabled", true);
pref("browser.casting.enabled", false);
pref("browser.chrome.site_icons", true);
pref("browser.chrome.favicons", true);
// browser.warnOnQuit == false will override all other possible prompts when quitting or restarting

View File

@ -22,7 +22,7 @@ add_task(function*() {
win = SessionStore.undoCloseWindow(0);
yield BrowserTestUtils.waitForEvent(win, "load", 10000);
yield BrowserTestUtils.waitForEvent(win.gBrowser.tabs[0], "SSTabRestored");
yield BrowserTestUtils.waitForEvent(win.gBrowser.tabs[0], "SSTabRestored", 10000);
is(win.gBrowser.tabs.length, 1, "Should have restored one tab");
is(win.gBrowser.selectedBrowser.currentURI.spec, uri, "Should have restored the right page");

View File

@ -455,6 +455,7 @@ loop.contacts = (function(_, mozL10n) {
return;
}
this.props.notifications.successL10n("import_contacts_success_message", {
num: stats.total,
total: stats.total
});
});

View File

@ -455,6 +455,7 @@ loop.contacts = (function(_, mozL10n) {
return;
}
this.props.notifications.successL10n("import_contacts_success_message", {
num: stats.total,
total: stats.total
});
});

View File

@ -277,7 +277,8 @@ describe("loop.contacts", function() {
sinon.assert.calledWithExactly(
notifications.successL10n,
"import_contacts_success_message",
{total: 42});
// Num is for the plural selection.
{num: 42, total: 42});
});
it("should notify the end user from any encountered error", function() {

View File

@ -6,7 +6,7 @@
<head>
<meta charset="utf-8">
<title>Loop desktop-local mocha tests</title>
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.0.1.css">
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.2.1.css">
</head>
<body>
<div id="mocha">
@ -29,12 +29,12 @@
<script src="../../content/shared/libs/backbone-1.1.2.js"></script>
<!-- test dependencies -->
<script src="../shared/vendor/mocha-2.0.1.js"></script>
<script src="../shared/vendor/chai-1.9.0.js"></script>
<script src="../shared/vendor/sinon-1.12.2.js"></script>
<script src="../shared/vendor/mocha-2.2.1.js"></script>
<script src="../shared/vendor/chai-2.1.0.js"></script>
<script src="../shared/vendor/sinon-1.13.0.js"></script>
<script>
/*global chai,mocha */
chai.Assertion.includeStack = true;
chai.config.includeStack = true;
mocha.setup('bdd');
</script>

View File

@ -6,7 +6,7 @@
<head>
<meta charset="utf-8">
<title>Loop shared mocha tests</title>
<link rel="stylesheet" media="all" href="vendor/mocha-2.0.1.css">
<link rel="stylesheet" media="all" href="vendor/mocha-2.2.1.css">
</head>
<body>
<div id="mocha">
@ -29,12 +29,12 @@
<script src="../../standalone/content/libs/l10n-gaia-02ca67948fe8.js"></script>
<!-- test dependencies -->
<script src="vendor/mocha-2.0.1.js"></script>
<script src="vendor/chai-1.9.0.js"></script>
<script src="vendor/sinon-1.12.2.js"></script>
<script src="vendor/mocha-2.2.1.js"></script>
<script src="vendor/chai-2.1.0.js"></script>
<script src="vendor/sinon-1.13.0.js"></script>
<script>
/*global chai, mocha */
chai.Assertion.includeStack = true;
chai.config.includeStack = true;
mocha.setup('bdd');
</script>

View File

@ -1,5 +1,5 @@
/**
* Sinon.JS 1.12.2, 2014/12/12
* Sinon.JS 1.13.0, 2015/03/05
*
* @author Christian Johansen (christian@cjohansen.no)
* @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
@ -35,7 +35,7 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], function () {
define('sinon', [], function () {
return (root.sinon = factory());
});
} else if (typeof exports === 'object') {
@ -1455,7 +1455,7 @@ var sinon = (function () {
}(typeof sinon == "object" && sinon || null));
/**
* @depend ../sinon.js
* @depend util/core.js
*/
(function (sinon) {
@ -1558,7 +1558,7 @@ var sinon = (function () {
}(typeof sinon == "object" && sinon || null));
/**
* @depend ../sinon.js
* @depend util/core.js
*/
(function (sinon) {
@ -1601,7 +1601,7 @@ var sinon = (function () {
}(typeof sinon == "object" && sinon || null));
/**
* @depend ../sinon.js
* @depend util/core.js
*/
/**
* Format functions
@ -1889,6 +1889,7 @@ var sinon = (function () {
function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./typeOf");
module.exports = makeApi(sinon);
}
@ -1904,7 +1905,7 @@ var sinon = (function () {
}(typeof sinon == "object" && sinon || null));
/**
* @depend ../sinon.js
* @depend util/core.js
*/
/**
* Format functions
@ -2028,7 +2029,11 @@ var sinon = (function () {
},
calledWith: function calledWith() {
for (var i = 0, l = arguments.length; i < l; i += 1) {
var l = arguments.length;
if (l > this.args.length) {
return false;
}
for (var i = 0; i < l; i += 1) {
if (!sinon.deepEqual(arguments[i], this.args[i])) {
return false;
}
@ -2038,7 +2043,11 @@ var sinon = (function () {
},
calledWithMatch: function calledWithMatch() {
for (var i = 0, l = arguments.length; i < l; i += 1) {
var l = arguments.length;
if (l > this.args.length) {
return false;
}
for (var i = 0; i < l; i += 1) {
var actual = this.args[i];
var expectation = arguments[i];
if (!sinon.match || !sinon.match(expectation).test(actual)) {
@ -2187,6 +2196,7 @@ var sinon = (function () {
function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./match");
require("./format");
module.exports = makeApi(sinon);
}
@ -2265,11 +2275,11 @@ var sinon = (function () {
}
var vars = "a,b,c,d,e,f,g,h,i,j,k,l";
function createProxy(func) {
function createProxy(func, proxyLength) {
// Retain the function length:
var p;
if (func.length) {
eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) +
if (proxyLength) {
eval("p = (function proxy(" + vars.substring(0, proxyLength * 2 - 1) +
") { return p.invoke(func, this, slice.call(arguments)); });");
} else {
p = function proxy() {
@ -2311,9 +2321,11 @@ var sinon = (function () {
this.fakes[i].reset();
}
}
return this;
},
create: function create(func) {
create: function create(func, spyLength) {
var name;
if (typeof func != "function") {
@ -2322,7 +2334,11 @@ var sinon = (function () {
name = sinon.functionName(func);
}
var proxy = createProxy(func);
if (!spyLength) {
spyLength = func.length;
}
var proxy = createProxy(func, spyLength);
sinon.extend(proxy, spy);
delete proxy.create;
@ -2623,6 +2639,9 @@ var sinon = (function () {
function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./call");
require("./extend");
require("./times_in_words");
require("./format");
module.exports = makeApi(sinon);
}
@ -2654,6 +2673,8 @@ var sinon = (function () {
(function (sinon) {
var slice = Array.prototype.slice;
var join = Array.prototype.join;
var useLeftMostCallback = -1;
var useRightMostCallback = -2;
var nextTick = (function () {
if (typeof process === "object" && typeof process.nextTick === "function") {
@ -2683,24 +2704,34 @@ var sinon = (function () {
function getCallback(behavior, args) {
var callArgAt = behavior.callArgAt;
if (callArgAt < 0) {
var callArgProp = behavior.callArgProp;
for (var i = 0, l = args.length; i < l; ++i) {
if (!callArgProp && typeof args[i] == "function") {
return args[i];
}
if (callArgProp && args[i] &&
typeof args[i][callArgProp] == "function") {
return args[i][callArgProp];
}
}
return null;
if (callArgAt >= 0) {
return args[callArgAt];
}
return args[callArgAt];
var argumentList;
if (callArgAt === useLeftMostCallback) {
argumentList = args;
}
if (callArgAt === useRightMostCallback) {
argumentList = slice.call(args).reverse();
}
var callArgProp = behavior.callArgProp;
for (var i = 0, l = argumentList.length; i < l; ++i) {
if (!callArgProp && typeof argumentList[i] == "function") {
return argumentList[i];
}
if (callArgProp && argumentList[i] &&
typeof argumentList[i][callArgProp] == "function") {
return argumentList[i][callArgProp];
}
}
return null;
}
function makeApi(sinon) {
@ -2860,7 +2891,17 @@ var sinon = (function () {
},
yields: function () {
this.callArgAt = -1;
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 0);
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;
return this;
},
yieldsRight: function () {
this.callArgAt = useRightMostCallback;
this.callbackArguments = slice.call(arguments, 0);
this.callbackContext = undefined;
this.callArgProp = undefined;
@ -2874,7 +2915,7 @@ var sinon = (function () {
throw new TypeError("argument context is not an object");
}
this.callArgAt = -1;
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 1);
this.callbackContext = context;
this.callArgProp = undefined;
@ -2884,7 +2925,7 @@ var sinon = (function () {
},
yieldsTo: function (prop) {
this.callArgAt = -1;
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 1);
this.callbackContext = undefined;
this.callArgProp = prop;
@ -2898,7 +2939,7 @@ var sinon = (function () {
throw new TypeError("argument context is not an object");
}
this.callArgAt = -1;
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 2);
this.callbackContext = context;
this.callArgProp = prop;
@ -2959,6 +3000,7 @@ var sinon = (function () {
function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./extend");
module.exports = makeApi(sinon);
}
@ -3000,7 +3042,11 @@ var sinon = (function () {
if (func) {
wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
} else {
wrapper = stub.create();
var stubLength = 0;
if (typeof object == "object" && typeof object[property] == "function") {
stubLength = object[property].length;
}
wrapper = stub.create(stubLength);
}
if (!object && typeof property === "undefined") {
@ -3036,14 +3082,14 @@ var sinon = (function () {
var uuid = 0;
var proto = {
create: function create() {
create: function create(stubLength) {
var functionStub = function () {
return getCurrentBehavior(functionStub).invoke(this, arguments);
};
functionStub.id = "stub#" + uuid++;
var orig = functionStub;
functionStub = sinon.spy.create(functionStub);
functionStub = sinon.spy.create(functionStub, stubLength);
functionStub.func = orig;
sinon.extend(functionStub, stub);
@ -3124,6 +3170,7 @@ var sinon = (function () {
var sinon = require("./util/core");
require("./behavior");
require("./spy");
require("./extend");
module.exports = makeApi(sinon);
}
@ -3141,7 +3188,10 @@ var sinon = (function () {
/**
* @depend times_in_words.js
* @depend util/core.js
* @depend call.js
* @depend extend.js
* @depend match.js
* @depend spy.js
* @depend stub.js
* @depend format.js
*/
@ -3569,9 +3619,14 @@ var sinon = (function () {
function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./times_in_words");
require("./call");
require("./extend");
require("./match");
require("./spy");
require("./stub");
require("./format");
module.exports = makeApi(sinon);
}
@ -3588,6 +3643,7 @@ var sinon = (function () {
/**
* @depend util/core.js
* @depend spy.js
* @depend stub.js
* @depend mock.js
*/
@ -3814,16 +3870,16 @@ if (typeof sinon == "undefined") {
var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
function loadDependencies(require, epxorts, module) {
function loadDependencies(require, epxorts, module, lolex) {
var sinon = require("./core");
makeApi(sinon, require("lolex"));
makeApi(sinon, lolex);
module.exports = sinon;
}
if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module);
loadDependencies(require, module.exports, module, require("lolex"));
} else {
makeApi(sinon);
}
@ -3873,6 +3929,7 @@ if (typeof sinon == "undefined") {
this.initEvent(type, false, false, target);
this.loaded = progressEventRaw.loaded || null;
this.total = progressEventRaw.total || null;
this.lengthComputable = !!progressEventRaw.total;
};
sinon.ProgressEvent.prototype = new sinon.Event();
@ -3940,7 +3997,7 @@ if (typeof sinon == "undefined") {
}());
/**
* @depend ../sinon.js
* @depend util/core.js
*/
/**
* Logs errors
@ -4007,6 +4064,229 @@ if (typeof sinon == "undefined") {
}
}(typeof sinon == "object" && sinon || null));
/**
* @depend core.js
* @depend ../extend.js
* @depend event.js
* @depend ../log_error.js
*/
/**
* Fake XDomainRequest object
*/
if (typeof sinon == "undefined") {
this.sinon = {};
}
// wrapper for global
(function (global) {
var xdr = { XDomainRequest: global.XDomainRequest };
xdr.GlobalXDomainRequest = global.XDomainRequest;
xdr.supportsXDR = typeof xdr.GlobalXDomainRequest != "undefined";
xdr.workingXDR = xdr.supportsXDR ? xdr.GlobalXDomainRequest : false;
function makeApi(sinon) {
sinon.xdr = xdr;
function FakeXDomainRequest() {
this.readyState = FakeXDomainRequest.UNSENT;
this.requestBody = null;
this.requestHeaders = {};
this.status = 0;
this.timeout = null;
if (typeof FakeXDomainRequest.onCreate == "function") {
FakeXDomainRequest.onCreate(this);
}
}
function verifyState(xdr) {
if (xdr.readyState !== FakeXDomainRequest.OPENED) {
throw new Error("INVALID_STATE_ERR");
}
if (xdr.sendFlag) {
throw new Error("INVALID_STATE_ERR");
}
}
function verifyRequestSent(xdr) {
if (xdr.readyState == FakeXDomainRequest.UNSENT) {
throw new Error("Request not sent");
}
if (xdr.readyState == FakeXDomainRequest.DONE) {
throw new Error("Request done");
}
}
function verifyResponseBodyType(body) {
if (typeof body != "string") {
var error = new Error("Attempted to respond to fake XDomainRequest with " +
body + ", which is not a string.");
error.name = "InvalidBodyException";
throw error;
}
}
sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, {
open: function open(method, url) {
this.method = method;
this.url = url;
this.responseText = null;
this.sendFlag = false;
this.readyStateChange(FakeXDomainRequest.OPENED);
},
readyStateChange: function readyStateChange(state) {
this.readyState = state;
var eventName = "";
switch (this.readyState) {
case FakeXDomainRequest.UNSENT:
break;
case FakeXDomainRequest.OPENED:
break;
case FakeXDomainRequest.LOADING:
if (this.sendFlag) {
//raise the progress event
eventName = "onprogress";
}
break;
case FakeXDomainRequest.DONE:
if (this.isTimeout) {
eventName = "ontimeout"
} else if (this.errorFlag || (this.status < 200 || this.status > 299)) {
eventName = "onerror";
} else {
eventName = "onload"
}
break;
}
// raising event (if defined)
if (eventName) {
if (typeof this[eventName] == "function") {
try {
this[eventName]();
} catch (e) {
sinon.logError("Fake XHR " + eventName + " handler", e);
}
}
}
},
send: function send(data) {
verifyState(this);
if (!/^(get|head)$/i.test(this.method)) {
this.requestBody = data;
}
this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
this.errorFlag = false;
this.sendFlag = true;
this.readyStateChange(FakeXDomainRequest.OPENED);
if (typeof this.onSend == "function") {
this.onSend(this);
}
},
abort: function abort() {
this.aborted = true;
this.responseText = null;
this.errorFlag = true;
if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) {
this.readyStateChange(sinon.FakeXDomainRequest.DONE);
this.sendFlag = false;
}
},
setResponseBody: function setResponseBody(body) {
verifyRequestSent(this);
verifyResponseBodyType(body);
var chunkSize = this.chunkSize || 10;
var index = 0;
this.responseText = "";
do {
this.readyStateChange(FakeXDomainRequest.LOADING);
this.responseText += body.substring(index, index + chunkSize);
index += chunkSize;
} while (index < body.length);
this.readyStateChange(FakeXDomainRequest.DONE);
},
respond: function respond(status, contentType, body) {
// content-type ignored, since XDomainRequest does not carry this
// we keep the same syntax for respond(...) as for FakeXMLHttpRequest to ease
// test integration across browsers
this.status = typeof status == "number" ? status : 200;
this.setResponseBody(body || "");
},
simulatetimeout: function simulatetimeout() {
this.status = 0;
this.isTimeout = true;
// Access to this should actually throw an error
this.responseText = undefined;
this.readyStateChange(FakeXDomainRequest.DONE);
}
});
sinon.extend(FakeXDomainRequest, {
UNSENT: 0,
OPENED: 1,
LOADING: 3,
DONE: 4
});
sinon.useFakeXDomainRequest = function useFakeXDomainRequest() {
sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) {
if (xdr.supportsXDR) {
global.XDomainRequest = xdr.GlobalXDomainRequest;
}
delete sinon.FakeXDomainRequest.restore;
if (keepOnCreate !== true) {
delete sinon.FakeXDomainRequest.onCreate;
}
};
if (xdr.supportsXDR) {
global.XDomainRequest = sinon.FakeXDomainRequest;
}
return sinon.FakeXDomainRequest;
};
sinon.FakeXDomainRequest = FakeXDomainRequest;
}
var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
function loadDependencies(require, exports, module) {
var sinon = require("./core");
require("../extend");
require("./event");
require("../log_error");
makeApi(sinon);
module.exports = sinon;
}
if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module);
} else {
makeApi(sinon);
}
})(this);
/**
* @depend core.js
* @depend ../extend.js
@ -4382,6 +4662,7 @@ if (typeof sinon == "undefined") {
this.upload.dispatchEvent(new sinon.Event("load", false, false, this));
if (supportsProgress) {
this.upload.dispatchEvent(new sinon.ProgressEvent("progress", {loaded: 100, total: 100}));
this.dispatchEvent(new sinon.ProgressEvent("progress", {loaded: 100, total: 100}));
}
break;
}
@ -4428,7 +4709,7 @@ if (typeof sinon == "undefined") {
if (this.requestHeaders[contentType]) {
var value = this.requestHeaders[contentType].split(";");
this.requestHeaders[contentType] = value[0] + ";charset=utf-8";
} else {
} else if (!(data instanceof FormData)) {
this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
}
@ -4544,6 +4825,12 @@ if (typeof sinon == "undefined") {
}
},
downloadProgress: function downloadProgress(progressEventRaw) {
if (supportsProgress) {
this.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw));
}
},
uploadError: function uploadError(error) {
if (supportsCustomEvent) {
this.upload.dispatchEvent(new sinon.CustomEvent("error", {detail: error}));
@ -4601,7 +4888,9 @@ if (typeof sinon == "undefined") {
function loadDependencies(require, exports, module) {
var sinon = require("./core");
require("../extend");
require("./event");
require("../log_error");
makeApi(sinon);
module.exports = sinon;
}
@ -4616,9 +4905,10 @@ if (typeof sinon == "undefined") {
makeApi(sinon);
}
})(typeof self !== "undefined" ? self : this);
})(typeof global !== "undefined" ? global : this);
/**
* @depend fake_xdomain_request.js
* @depend fake_xml_http_request.js
* @depend ../format.js
* @depend ../log_error.js
@ -4699,7 +4989,11 @@ if (typeof sinon == "undefined") {
sinon.fakeServer = {
create: function () {
var server = create(this);
this.xhr = sinon.useFakeXMLHttpRequest();
if (!sinon.xhr.supportsCORS) {
this.xhr = sinon.useFakeXDomainRequest();
} else {
this.xhr = sinon.useFakeXMLHttpRequest();
}
server.requests = [];
this.xhr.onCreate = function (xhrObj) {
@ -4835,7 +5129,9 @@ if (typeof sinon == "undefined") {
function loadDependencies(require, exports, module) {
var sinon = require("./core");
require("./fake_xdomain_request");
require("./fake_xml_http_request");
require("../format");
makeApi(sinon);
module.exports = sinon;
}
@ -5094,7 +5390,8 @@ if (typeof sinon == "undefined") {
function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./util/fake_server");
require("./extend");
require("./util/fake_server_with_clock");
require("./util/fake_timers");
require("./collection");
module.exports = makeApi(sinon);
@ -5113,8 +5410,6 @@ if (typeof sinon == "undefined") {
/**
* @depend util/core.js
* @depend stub.js
* @depend mock.js
* @depend sandbox.js
*/
/**
@ -5128,6 +5423,8 @@ if (typeof sinon == "undefined") {
(function (sinon) {
function makeApi(sinon) {
var slice = Array.prototype.slice;
function test(callback) {
var type = typeof callback;
@ -5139,12 +5436,12 @@ if (typeof sinon == "undefined") {
var config = sinon.getConfig(sinon.config);
config.injectInto = config.injectIntoThis && this || config.injectInto;
var sandbox = sinon.sandbox.create(config);
var args = slice.call(arguments);
var oldDone = args.length && args[args.length - 1];
var exception, result;
var doneIsWrapped = false;
var argumentsCopy = Array.prototype.slice.call(arguments);
if (argumentsCopy.length > 0 && typeof argumentsCopy[arguments.length - 1] == "function") {
var oldDone = argumentsCopy[arguments.length - 1];
argumentsCopy[arguments.length - 1] = function done(result) {
if (typeof oldDone == "function") {
args[args.length - 1] = function sinonDone(result) {
if (result) {
sandbox.restore();
throw exception;
@ -5152,19 +5449,16 @@ if (typeof sinon == "undefined") {
sandbox.verifyAndRestore();
}
oldDone(result);
}
doneIsWrapped = true;
};
}
var args = argumentsCopy.concat(sandbox.args);
try {
result = callback.apply(this, args);
result = callback.apply(this, args.concat(sandbox.args));
} catch (e) {
exception = e;
}
if (!doneIsWrapped) {
if (typeof oldDone != "function") {
if (typeof exception !== "undefined") {
sandbox.restore();
throw exception;
@ -5174,10 +5468,10 @@ if (typeof sinon == "undefined") {
}
return result;
};
}
if (callback.length) {
return function sinonAsyncSandboxedTest(callback) {
return function sinonAsyncSandboxedTest() {
return sinonSandboxedTest.apply(this, arguments);
};
}
@ -5210,9 +5504,7 @@ if (typeof sinon == "undefined") {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module);
} else if (!sinon) {
return;
} else {
} else if (sinon) {
makeApi(sinon);
}
}(typeof sinon == "object" && sinon || null));
@ -5323,7 +5615,7 @@ if (typeof sinon == "undefined") {
/**
* @depend times_in_words.js
* @depend util/core.js
* @depend stub.js
* @depend match.js
* @depend format.js
*/
/**
@ -5351,13 +5643,18 @@ if (typeof sinon == "undefined") {
assert.fail("fake is not a spy");
}
if (typeof method != "function") {
assert.fail(method + " is not a function");
if (method.proxy) {
verifyIsStub(method.proxy);
} else {
if (typeof method != "function") {
assert.fail(method + " is not a function");
}
if (typeof method.getCall != "function") {
assert.fail(method + " is not stubbed");
}
}
if (typeof method.getCall != "function") {
assert.fail(method + " is not stubbed");
}
}
}
@ -5387,7 +5684,7 @@ if (typeof sinon == "undefined") {
}
if (failed) {
failAssertion(this, fake.printf.apply(fake, [message].concat(args)));
failAssertion(this, (fake.printf || fake.proxy.printf).apply(fake, [message].concat(args)));
} else {
assert.pass(name);
}
@ -5513,6 +5810,7 @@ if (typeof sinon == "undefined") {
function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./match");
require("./format");
module.exports = makeApi(sinon);
}
@ -5528,226 +5826,5 @@ if (typeof sinon == "undefined") {
}(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global));
/**
* @depend core.js
* @depend ../extend.js
* @depend event.js
* @depend ../log_error.js
*/
/**
* Fake XDomainRequest object
*/
if (typeof sinon == "undefined") {
this.sinon = {};
}
// wrapper for global
(function (global) {
var xdr = { XDomainRequest: global.XDomainRequest };
xdr.GlobalXDomainRequest = global.XDomainRequest;
xdr.supportsXDR = typeof xdr.GlobalXDomainRequest != "undefined";
xdr.workingXDR = xdr.supportsXDR ? xdr.GlobalXDomainRequest : false;
function makeApi(sinon) {
sinon.xdr = xdr;
function FakeXDomainRequest() {
this.readyState = FakeXDomainRequest.UNSENT;
this.requestBody = null;
this.requestHeaders = {};
this.status = 0;
this.timeout = null;
if (typeof FakeXDomainRequest.onCreate == "function") {
FakeXDomainRequest.onCreate(this);
}
}
function verifyState(xdr) {
if (xdr.readyState !== FakeXDomainRequest.OPENED) {
throw new Error("INVALID_STATE_ERR");
}
if (xdr.sendFlag) {
throw new Error("INVALID_STATE_ERR");
}
}
function verifyRequestSent(xdr) {
if (xdr.readyState == FakeXDomainRequest.UNSENT) {
throw new Error("Request not sent");
}
if (xdr.readyState == FakeXDomainRequest.DONE) {
throw new Error("Request done");
}
}
function verifyResponseBodyType(body) {
if (typeof body != "string") {
var error = new Error("Attempted to respond to fake XDomainRequest with " +
body + ", which is not a string.");
error.name = "InvalidBodyException";
throw error;
}
}
sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, {
open: function open(method, url) {
this.method = method;
this.url = url;
this.responseText = null;
this.sendFlag = false;
this.readyStateChange(FakeXDomainRequest.OPENED);
},
readyStateChange: function readyStateChange(state) {
this.readyState = state;
var eventName = "";
switch (this.readyState) {
case FakeXDomainRequest.UNSENT:
break;
case FakeXDomainRequest.OPENED:
break;
case FakeXDomainRequest.LOADING:
if (this.sendFlag) {
//raise the progress event
eventName = "onprogress";
}
break;
case FakeXDomainRequest.DONE:
if (this.isTimeout) {
eventName = "ontimeout"
} else if (this.errorFlag || (this.status < 200 || this.status > 299)) {
eventName = "onerror";
} else {
eventName = "onload"
}
break;
}
// raising event (if defined)
if (eventName) {
if (typeof this[eventName] == "function") {
try {
this[eventName]();
} catch (e) {
sinon.logError("Fake XHR " + eventName + " handler", e);
}
}
}
},
send: function send(data) {
verifyState(this);
if (!/^(get|head)$/i.test(this.method)) {
this.requestBody = data;
}
this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
this.errorFlag = false;
this.sendFlag = true;
this.readyStateChange(FakeXDomainRequest.OPENED);
if (typeof this.onSend == "function") {
this.onSend(this);
}
},
abort: function abort() {
this.aborted = true;
this.responseText = null;
this.errorFlag = true;
if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) {
this.readyStateChange(sinon.FakeXDomainRequest.DONE);
this.sendFlag = false;
}
},
setResponseBody: function setResponseBody(body) {
verifyRequestSent(this);
verifyResponseBodyType(body);
var chunkSize = this.chunkSize || 10;
var index = 0;
this.responseText = "";
do {
this.readyStateChange(FakeXDomainRequest.LOADING);
this.responseText += body.substring(index, index + chunkSize);
index += chunkSize;
} while (index < body.length);
this.readyStateChange(FakeXDomainRequest.DONE);
},
respond: function respond(status, contentType, body) {
// content-type ignored, since XDomainRequest does not carry this
// we keep the same syntax for respond(...) as for FakeXMLHttpRequest to ease
// test integration across browsers
this.status = typeof status == "number" ? status : 200;
this.setResponseBody(body || "");
},
simulatetimeout: function simulatetimeout() {
this.status = 0;
this.isTimeout = true;
// Access to this should actually throw an error
this.responseText = undefined;
this.readyStateChange(FakeXDomainRequest.DONE);
}
});
sinon.extend(FakeXDomainRequest, {
UNSENT: 0,
OPENED: 1,
LOADING: 3,
DONE: 4
});
sinon.useFakeXDomainRequest = function useFakeXDomainRequest() {
sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) {
if (xdr.supportsXDR) {
global.XDomainRequest = xdr.GlobalXDomainRequest;
}
delete sinon.FakeXDomainRequest.restore;
if (keepOnCreate !== true) {
delete sinon.FakeXDomainRequest.onCreate;
}
};
if (xdr.supportsXDR) {
global.XDomainRequest = sinon.FakeXDomainRequest;
}
return sinon.FakeXDomainRequest;
};
sinon.FakeXDomainRequest = FakeXDomainRequest;
}
var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
function loadDependencies(require, exports, module) {
var sinon = require("./core");
require("./event");
makeApi(sinon);
module.exports = sinon;
}
if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module);
} else {
makeApi(sinon);
}
})(this);
return sinon;
}));

View File

@ -6,7 +6,7 @@
<head>
<meta charset="utf-8">
<title>Loop mocha tests</title>
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.0.1.css">
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.2.1.css">
</head>
<body>
<div id="mocha">
@ -29,12 +29,12 @@
<script src="../../content/shared/libs/backbone-1.1.2.js"></script>
<script src="../../standalone/content/libs/l10n-gaia-02ca67948fe8.js"></script>
<!-- test dependencies -->
<script src="../shared/vendor/mocha-2.0.1.js"></script>
<script src="../shared/vendor/chai-1.9.0.js"></script>
<script src="../shared/vendor/sinon-1.12.2.js"></script>
<script src="../shared/vendor/mocha-2.2.1.js"></script>
<script src="../shared/vendor/chai-2.1.0.js"></script>
<script src="../shared/vendor/sinon-1.13.0.js"></script>
<script src="../shared/sdk_mock.js"></script>
<script>
chai.Assertion.includeStack = true;
chai.config.includeStack = true;
mocha.setup('bdd');
</script>
<!-- App scripts -->

View File

@ -99,6 +99,11 @@ var gContentPane = {
*/
_rebuildFonts: function ()
{
var preferences = document.getElementById("contentPreferences");
// Ensure preferences are "visible" to ensure bindings work.
preferences.hidden = false;
// Force flush:
preferences.clientHeight;
var langGroupPref = document.getElementById("font.language.group");
this._selectDefaultLanguageGroup(langGroupPref.value,
this._readDefaultFontTypeForLanguage(langGroupPref.value) == "serif");
@ -115,6 +120,7 @@ var gContentPane = {
const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
const kFontSizeFmtVariable = "font.size.variable.%LANG%";
var preferences = document.getElementById("contentPreferences");
var prefs = [{ format : aIsSerif ? kFontNameFmtSerif : kFontNameFmtSansSerif,
type : "fontname",
element : "defaultFont",
@ -127,11 +133,6 @@ var gContentPane = {
type : "int",
element : "defaultFontSize",
fonttype : null }];
var preferences = document.getElementById("contentPreferences");
// Ensure preferences are "visible" to ensure bindings work.
preferences.hidden = false;
// Force flush:
preferences.clientHeight;
for (var i = 0; i < prefs.length; ++i) {
var preference = document.getElementById(prefs[i].format.replace(/%LANG%/, aLanguageGroup));
if (!preference) {

View File

@ -50,6 +50,7 @@ let gSubDialog = {
this._closingEvent = null;
this._isClosing = false;
this._openedURL = aURL;
features = features.replace(/,/g, "&");
let featureParams = new URLSearchParams(features.toLowerCase());
@ -124,7 +125,7 @@ let gSubDialog = {
/* Private methods */
_onUnload: function(aEvent) {
if (aEvent.target.location.href != "about:blank") {
if (aEvent.target.location.href == this._openedURL) {
this.close(this._closingEvent);
}
},
@ -222,6 +223,11 @@ let gSubDialog = {
},
_onKeyDown: function(aEvent) {
if (aEvent.currentTarget == window && aEvent.keyCode == aEvent.DOM_VK_ESCAPE &&
!aEvent.defaultPrevented) {
this.close(aEvent);
return;
}
if (aEvent.keyCode != aEvent.DOM_VK_TAB ||
aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) {
return;
@ -278,6 +284,10 @@ let gSubDialog = {
this._frame.addEventListener("load", this);
chromeBrowser.addEventListener("unload", this, true);
// Ensure we get <esc> keypresses even if nothing in the subdialog is focusable
// (happens on OS X when only text inputs and lists are focusable, and
// the subdialog only has checkboxes/radiobuttons/buttons)
window.addEventListener("keydown", this, true);
},
_removeDialogEventListeners: function() {
@ -290,6 +300,7 @@ let gSubDialog = {
window.removeEventListener("DOMFrameContentLoaded", this, true);
this._frame.removeEventListener("load", this);
this._frame.contentWindow.removeEventListener("dialogclosing", this);
window.removeEventListener("keydown", this, true);
this._untrapFocus();
},

View File

@ -5,6 +5,7 @@ support-files =
privacypane_tests_perwindow.js
[browser_advanced_update.js]
[browser_basic_rebuild_fonts_test.js]
[browser_bug410900.js]
[browser_bug731866.js]
[browser_bug795764_cachedisabled.js]

View File

@ -0,0 +1,26 @@
Services.prefs.setBoolPref("browser.preferences.inContent", true);
Services.prefs.setBoolPref("browser.preferences.instantApply", true);
registerCleanupFunction(function() {
Services.prefs.clearUserPref("browser.preferences.inContent");
Services.prefs.clearUserPref("browser.preferences.instantApply");
});
add_task(function() {
yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
let doc = gBrowser.contentDocument;
var langGroup = Services.prefs.getComplexValue("font.language.group", Ci.nsIPrefLocalizedString).data
is(doc.getElementById("font.language.group").value, langGroup,
"Language group should be set correctly.");
let defaultFontType = Services.prefs.getCharPref("font.default." + langGroup);
let fontFamily = Services.prefs.getCharPref("font.name." + defaultFontType + "." + langGroup);
let fontFamilyField = doc.getElementById("defaultFont");
is(fontFamilyField.value, fontFamily, "Font family should be set correctly.");
let defaultFontSize = Services.prefs.getIntPref("font.size.variable." + langGroup);
let fontSizeField = doc.getElementById("defaultFontSize");
is(fontSizeField.value, defaultFontSize, "Font size should be set correctly.");
gBrowser.removeCurrentTab();
});

View File

@ -1,5 +1,5 @@
[DEFAULT]
skip-if = buildapp == "mulet" || e10s
skip-if = buildapp == "mulet"
support-files =
browser_privatebrowsing_concurrent_page.html
browser_privatebrowsing_cookieacceptdialog.html
@ -17,11 +17,15 @@ support-files =
[browser_privatebrowsing_DownloadLastDirWithCPS.js]
[browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js]
skip-if = true # Bug 1142678 - Loading a message sending frame script into a private about:home tab causes leaks
[browser_privatebrowsing_aboutHomeButtonAfterWindowClose_old.js]
skip-if = e10s
[browser_privatebrowsing_aboutSessionRestore.js]
[browser_privatebrowsing_cache.js]
[browser_privatebrowsing_certexceptionsui.js]
[browser_privatebrowsing_concurrent.js]
[browser_privatebrowsing_cookieacceptdialog.js]
skip-if = e10s # Bug 1139953 - Accept cookie dialog shown in private window when e10s enabled
[browser_privatebrowsing_crh.js]
[browser_privatebrowsing_downloadLastDir.js]
[browser_privatebrowsing_downloadLastDir_c.js]
@ -37,10 +41,14 @@ support-files =
[browser_privatebrowsing_placestitle.js]
[browser_privatebrowsing_popupblocker.js]
[browser_privatebrowsing_protocolhandler.js]
skip-if = e10s # Bug 940206 - nsIWebContentHandlerRegistrar::registerProtocolHandler doesn't work in e10s
[browser_privatebrowsing_sidebar.js]
[browser_privatebrowsing_theming.js]
[browser_privatebrowsing_ui.js]
[browser_privatebrowsing_urlbarfocus.js]
[browser_privatebrowsing_windowtitle.js]
skip-if = e10s
[browser_privatebrowsing_zoom.js]
skip-if = e10s
[browser_privatebrowsing_zoomrestore.js]
skip-if = e10s

View File

@ -2,49 +2,25 @@
* 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 test checks that the Session Restore about:home button
// is disabled in private mode
// This test checks that the Session Restore "Restore Previous Session"
// button on about:home is disabled in private mode
add_task(function* test_no_sessionrestore_button() {
// Opening, then closing, a private window shouldn't create session data.
(yield BrowserTestUtils.openNewBrowserWindow({private: true})).close();
function test() {
waitForExplicitFinish();
let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
let tab = win.gBrowser.addTab("about:home");
let browser = tab.linkedBrowser;
function testNoSessionRestoreButton() {
let win = OpenBrowserWindow({private: true});
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
executeSoon(function() {
info("The second private window got loaded");
let newTab = win.gBrowser.addTab();
win.gBrowser.selectedTab = newTab;
let tabBrowser = win.gBrowser.getBrowserForTab(newTab);
tabBrowser.addEventListener("load", function tabLoadListener() {
if (win.content.location != "about:home") {
win.content.location = "about:home";
return;
}
tabBrowser.removeEventListener("load", tabLoadListener, true);
executeSoon(function() {
info("about:home got loaded");
let sessionRestoreButton = win.gBrowser
.contentDocument
.getElementById("restorePreviousSession");
is(win.getComputedStyle(sessionRestoreButton).display,
"none", "The Session Restore about:home button should be disabled");
win.close();
finish();
});
}, true);
});
}, false);
}
yield BrowserTestUtils.browserLoaded(browser);
let win = OpenBrowserWindow({private: true});
win.addEventListener("load", function onload() {
win.removeEventListener("load", onload, false);
executeSoon(function() {
info("The first private window got loaded");
win.close();
testNoSessionRestoreButton();
});
}, false);
}
let display = yield ContentTask.spawn(browser, {}, function* (){
let button = content.document.getElementById("restorePreviousSession");
return content.getComputedStyle(button).display;
});
is(display, "none",
"The Session Restore about:home button should be disabled");
win.close();
});

View File

@ -0,0 +1,50 @@
/* 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 test checks that the Session Restore about:home button
// is disabled in private mode
function test() {
waitForExplicitFinish();
function testNoSessionRestoreButton() {
let win = OpenBrowserWindow({private: true});
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
executeSoon(function() {
info("The second private window got loaded");
let newTab = win.gBrowser.addTab();
win.gBrowser.selectedTab = newTab;
let tabBrowser = win.gBrowser.getBrowserForTab(newTab);
tabBrowser.addEventListener("load", function tabLoadListener() {
if (win.content.location != "about:home") {
win.content.location = "about:home";
return;
}
tabBrowser.removeEventListener("load", tabLoadListener, true);
executeSoon(function() {
info("about:home got loaded");
let sessionRestoreButton = win.gBrowser
.contentDocument
.getElementById("restorePreviousSession");
is(win.getComputedStyle(sessionRestoreButton).display,
"none", "The Session Restore about:home button should be disabled");
win.close();
finish();
});
}, true);
});
}, false);
}
let win = OpenBrowserWindow({private: true});
win.addEventListener("load", function onload() {
win.removeEventListener("load", onload, false);
executeSoon(function() {
info("The first private window got loaded");
win.close();
testNoSessionRestoreButton();
});
}, false);
}

View File

@ -4,46 +4,21 @@
// This test checks that the session restore button from about:sessionrestore
// is disabled in private mode
add_task(function* testNoSessionRestoreButton() {
// Opening, then closing, a private window shouldn't create session data.
(yield BrowserTestUtils.openNewBrowserWindow({private: true})).close();
function test() {
waitForExplicitFinish();
let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
let tab = win.gBrowser.addTab("about:sessionrestore");
let browser = tab.linkedBrowser;
function testNoSessionRestoreButton() {
let win = OpenBrowserWindow({private: true});
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
executeSoon(function() {
info("The second private window got loaded");
let newTab = win.gBrowser.addTab("about:sessionrestore");
win.gBrowser.selectedTab = newTab;
let tabBrowser = win.gBrowser.getBrowserForTab(newTab);
tabBrowser.addEventListener("load", function tabLoadListener() {
if (win.gBrowser.contentWindow.location != "about:sessionrestore") {
win.gBrowser.selectedBrowser.loadURI("about:sessionrestore");
return;
}
tabBrowser.removeEventListener("load", tabLoadListener, true);
executeSoon(function() {
info("about:sessionrestore got loaded");
let restoreButton = win.gBrowser.contentDocument
.getElementById("errorTryAgain");
ok(restoreButton.disabled,
"The Restore about:sessionrestore button should be disabled");
win.close();
finish();
});
}, true);
});
}, false);
}
yield BrowserTestUtils.browserLoaded(browser);
let win = OpenBrowserWindow({private: true});
win.addEventListener("load", function onload() {
win.removeEventListener("load", onload, false);
executeSoon(function() {
info("The first private window got loaded");
win.close();
testNoSessionRestoreButton();
});
}, false);
}
let disabled = yield ContentTask.spawn(browser, {}, function* (){
return content.document.getElementById("errorTryAgain").disabled;
});
ok(disabled, "The Restore about:sessionrestore button should be disabled");
win.close();
});

View File

@ -11,63 +11,74 @@
// Step 3: load a page in the tab from step 1 that checks the value of test2 is value2 and the total count in non-private storage is 1
// Step 4: load a page in the tab from step 2 that checks the value of test is value and the total count in private storage is 1
function test() {
add_task(function test() {
let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html';
waitForExplicitFinish();
function setUsePrivateBrowsing(browser, val) {
return ContentTask.spawn(browser, val, function* (val) {
docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = val;
});
};
function getElts(browser) {
return browser.contentTitle.split('|');
};
// Step 1
gBrowser.selectedTab = gBrowser.addTab(prefix + '?action=set&name=test&value=value&initial=true');
let non_private_browser = gBrowser.selectedBrowser;
yield BrowserTestUtils.browserLoaded(non_private_browser);
// Step 2
gBrowser.selectedTab = gBrowser.addTab();
let non_private_tab = gBrowser.selectedBrowser;
non_private_tab.addEventListener('load', function() {
non_private_tab.removeEventListener('load', arguments.callee, true);
gBrowser.selectedTab = gBrowser.addTab();
let private_tab = gBrowser.selectedBrowser;
private_tab.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = true;
private_tab.addEventListener('load', function() {
private_tab.removeEventListener('load', arguments.callee, true);
let private_browser = gBrowser.selectedBrowser;
yield BrowserTestUtils.browserLoaded(private_browser);
yield setUsePrivateBrowsing(private_browser, true);
private_browser.loadURI(prefix + '?action=set&name=test2&value=value2');
yield BrowserTestUtils.browserLoaded(private_browser);
non_private_tab.addEventListener('load', function() {
non_private_tab.removeEventListener('load', arguments.callee, true);
var elts = non_private_tab.contentWindow.document.title.split('|');
isnot(elts[0], 'value2', "public window shouldn't see private storage");
is(elts[1], '1', "public window should only see public items");
private_tab.addEventListener('load', function() {
private_tab.removeEventListener('load', arguments.callee, true);
var elts = private_tab.contentWindow.document.title.split('|');
isnot(elts[0], 'value', "private window shouldn't see public storage");
is(elts[1], '1', "private window should only see private items");
private_tab.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = false;
// Step 3
non_private_browser.loadURI(prefix + '?action=get&name=test2');
yield BrowserTestUtils.browserLoaded(non_private_browser);
let elts = yield getElts(non_private_browser);
isnot(elts[0], 'value2', "public window shouldn't see private storage");
is(elts[1], '1', "public window should only see public items");
Components.utils.schedulePreciseGC(function() {
private_tab.addEventListener('load', function() {
private_tab.removeEventListener('load', arguments.callee, true);
var elts = private_tab.contentWindow.document.title.split('|');
isnot(elts[0], 'value2', "public window shouldn't see cleared private storage");
is(elts[1], '1', "public window should only see public items");
private_tab.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = true;
private_tab.addEventListener('load', function() {
private_tab.removeEventListener('load', arguments.callee, true);
var elts = private_tab.contentWindow.document.title.split('|');
is(elts[1], '1', "private window should only see new private items");
// Step 4
private_browser.loadURI(prefix + '?action=get&name=test');
yield BrowserTestUtils.browserLoaded(private_browser);
elts = yield getElts(private_browser);
isnot(elts[0], 'value', "private window shouldn't see public storage");
is(elts[1], '1', "private window should only see private items");
non_private_tab.addEventListener('load', function() {
gBrowser.removeCurrentTab();
gBrowser.removeCurrentTab();
finish();
}, true);
non_private_tab.loadURI(prefix + '?final=true');
}, true);
private_tab.loadURI(prefix + '?action=set&name=test3&value=value3');
}, true);
private_tab.loadURI(prefix + '?action=get&name=test2');
});
}, true);
private_tab.loadURI(prefix + '?action=get&name=test');
}, true);
non_private_tab.loadURI(prefix + '?action=get&name=test2');
}, true);
private_tab.loadURI(prefix + '?action=set&name=test2&value=value2');
}, true);
non_private_tab.loadURI(prefix + '?action=set&name=test&value=value&initial=true');
}
// Make the private tab public again, which should clear the
// the private storage.
yield setUsePrivateBrowsing(private_browser, false);
yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
private_browser.loadURI(prefix + '?action=get&name=test2');
yield BrowserTestUtils.browserLoaded(private_browser);
elts = yield getElts(private_browser);
isnot(elts[0], 'value2', "public window shouldn't see cleared private storage");
is(elts[1], '1', "public window should only see public items");
// Making it private again should clear the storage and it shouldn't
// be able to see the old private storage as well.
yield setUsePrivateBrowsing(private_browser, true);
private_browser.loadURI(prefix + '?action=set&name=test3&value=value3');
BrowserTestUtils.browserLoaded(private_browser);
elts = yield getElts(private_browser);
is(elts[1], '1', "private window should only see new private items");
// Cleanup.
non_private_browser.loadURI(prefix + '?final=true');
yield BrowserTestUtils.browserLoaded(non_private_browser);
gBrowser.removeCurrentTab();
gBrowser.removeCurrentTab();
});

View File

@ -5,7 +5,7 @@
// This test makes sure that private browsing mode disables the "remember"
// option in the cookie accept dialog.
function test() {
add_task(function* test() {
// initialization
const TEST_URL = "http://mochi.test:8888/browser/browser/components/" +
"privatebrowsing/test/browser/" +
@ -14,36 +14,8 @@ function test() {
let cp = Cc["@mozilla.org/embedcomp/cookieprompt-service;1"].
getService(Ci.nsICookiePromptService);
waitForExplicitFinish();
function checkRememberOption(expectedDisabled, aWindow, callback) {
function observer(aSubject, aTopic, aData) {
if (aTopic != "domwindowopened")
return;
Services.ww.unregisterNotification(observer);
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("load", function onLoad(event) {
win.removeEventListener("load", onLoad, false);
executeSoon(function () {
let doc = win.document;
let remember = doc.getElementById("persistDomainAcceptance");
ok(remember, "The remember checkbox should exist");
if (expectedDisabled)
is(remember.getAttribute("disabled"), "true",
"The checkbox should be disabled");
else
ok(!remember.hasAttribute("disabled"),
"The checkbox should not be disabled");
waitForWindowClose(win, callback);
});
}, false);
}
Services.ww.registerNotification(observer);
function openCookieDialog(aWindow) {
let remember = {};
const time = (new Date("Jan 1, 2030")).getTime() / 1000;
let cookie = {
@ -67,95 +39,90 @@ function test() {
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
cp.cookieDialog(aWindow, cookie, "mozilla.org", 10, false, remember);
}
function checkSettingDialog(aIsPrivateWindow, aWindow, aCallback) {
let selectedBrowser = aWindow.gBrowser.selectedBrowser;
executeSoon(function () {
cp.cookieDialog(aWindow, cookie, "mozilla.org", 10, false, remember);
});
return BrowserTestUtils.domWindowOpened();
};
function onLoad() {
if (aWindow.content.location.href != TEST_URL) {
selectedBrowser.loadURI(TEST_URL);
return;
}
selectedBrowser.removeEventListener("load", onLoad, true);
Services.ww.unregisterNotification(observer);
ok(aIsPrivateWindow,
"Confirm setting dialog is not displayed for private window");
function checkRememberOption(expectedDisabled, aWindow) {
return Task.spawn(function* () {
let dialogWin = yield openCookieDialog(aWindow);
executeSoon(aCallback);
}
selectedBrowser.addEventListener("load", onLoad, true);
function observer(aSubject, aTopic, aData) {
if (aTopic != "domwindowopened")
return;
selectedBrowser.removeEventListener("load", onLoad, true);
Services.ww.unregisterNotification(observer);
ok(!aIsPrivateWindow,
"Confirm setting dialog is displayed for normal window");
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
executeSoon(function () {
info("Wait for window close");
waitForWindowClose(win, aCallback);
yield new Promise(resolve => {
dialogWin.addEventListener("load", function onLoad(event) {
dialogWin.removeEventListener("load", onLoad, false);
resolve();
}, false);
});
}
Services.ww.registerNotification(observer);
selectedBrowser.loadURI(TEST_URL);
}
let doc = dialogWin.document;
let remember = doc.getElementById("persistDomainAcceptance");
ok(remember, "The remember checkbox should exist");
let windowsToClose = [];
function testOnWindow(aIsPrivate, aCallback) {
whenNewWindowLoaded({private: aIsPrivate}, function(aWin) {
windowsToClose.push(aWin);
let selectedBrowser = aWin.gBrowser.selectedBrowser;
selectedBrowser.addEventListener("load", function onLoad() {
if (aWin.content.location.href != BLANK_URL) {
selectedBrowser.loadURI(BLANK_URL);
return;
}
selectedBrowser.removeEventListener("load", onLoad, true);
executeSoon(function() aCallback(aWin));
}, true);
selectedBrowser.loadURI(BLANK_URL);
if (expectedDisabled)
is(remember.getAttribute("disabled"), "true",
"The checkbox should be disabled");
else
ok(!remember.hasAttribute("disabled"),
"The checkbox should not be disabled");
yield BrowserTestUtils.closeWindow(dialogWin);
});
}
};
registerCleanupFunction(function() {
Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
windowsToClose.forEach(function(aWin) {
aWin.close();
function checkSettingDialog(aIsPrivateWindow, aWindow) {
return Task.spawn(function* () {
let dialogOpened = false;
let promiseDialogClosed = null;
function observer(subject, topic, data) {
if (topic != "domwindowopened") { return; }
Services.ww.unregisterNotification(observer);
dialogOpened = true;
promiseDialogClosed = BrowserTestUtils.closeWindow(
subject.QueryInterface(Ci.nsIDOMWindow));
}
Services.ww.registerNotification(observer);
let selectedBrowser = aWindow.gBrowser.selectedBrowser;
selectedBrowser.loadURI(TEST_URL);
yield BrowserTestUtils.browserLoaded(selectedBrowser);;
if (dialogOpened) {
ok(!aIsPrivateWindow,
"Setting dialog shown, confirm normal window");
} else {
Services.ww.unregisterNotification(observer);
ok(aIsPrivateWindow,
"Confirm setting dialog is not displayed for private window");
}
yield promiseDialogClosed;
});
});
};
// Ask all cookies
Services.prefs.setIntPref("network.cookie.lifetimePolicy", 1);
testOnWindow(false, function(aWin) {
info("Test on public window");
checkRememberOption(false, aWin, function() {
checkSettingDialog(false, aWin, function() {
testOnWindow(true, function(aPrivWin) {
info("Test on private window");
checkRememberOption(true, aPrivWin, function() {
checkSettingDialog(true, aPrivWin, finish);
});
});
});
});
});
}
let win = yield BrowserTestUtils.openNewBrowserWindow();
info("Test on public window");
function waitForWindowClose(aWin, aCallback) {
function observer(aSubject, aTopic, aData) {
if (aTopic == "domwindowclosed") {
info("Window closed");
Services.ww.unregisterNotification(observer);
executeSoon(aCallback);
}
}
Services.ww.registerNotification(observer);
aWin.close();
}
yield checkRememberOption(false, win);
yield checkSettingDialog(false, win);
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
info("Test on private window");
yield checkRememberOption(true, privateWin);
yield checkSettingDialog(true, privateWin);
// Cleanup
Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
yield BrowserTestUtils.closeWindow(win);
yield BrowserTestUtils.closeWindow(privateWin);
});

View File

@ -5,55 +5,38 @@
// This test makes sure that the Clear Recent History menu item and command
// is disabled inside the private browsing mode.
function test() {
waitForExplicitFinish();
add_task(function test() {
function checkDisableOption(aPrivateMode, aWindow) {
let crhCommand = aWindow.document.getElementById("Tools:Sanitize");
ok(crhCommand, "The clear recent history command should exist");
function checkDisableOption(aPrivateMode, aWindow, aCallback) {
executeSoon(function() {
let crhCommand = aWindow.document.getElementById("Tools:Sanitize");
ok(crhCommand, "The clear recent history command should exist");
is(PrivateBrowsingUtils.isWindowPrivate(aWindow), aPrivateMode,
"PrivateBrowsingUtils should report the correct per-window private browsing status");
is(crhCommand.hasAttribute("disabled"), aPrivateMode,
"Clear Recent History command should be disabled according to the private browsing mode");
executeSoon(aCallback);
});
is(PrivateBrowsingUtils.isWindowPrivate(aWindow), aPrivateMode,
"PrivateBrowsingUtils should report the correct per-window private browsing status");
is(crhCommand.hasAttribute("disabled"), aPrivateMode,
"Clear Recent History command should be disabled according to the private browsing mode");
};
let windowsToClose = [];
let testURI = "http://mochi.test:8888/";
function testOnWindow(aIsPrivate, aCallback) {
whenNewWindowLoaded({private: aIsPrivate}, function(aWin) {
windowsToClose.push(aWin);
aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
if (aWin.content.location.href != testURI) {
aWin.gBrowser.loadURI(testURI);
return;
}
aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
executeSoon(function() aCallback(aWin));
}, true);
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
let privateBrowser = privateWin.gBrowser.selectedBrowser;
privateBrowser.loadURI(testURI);
yield BrowserTestUtils.browserLoaded(privateBrowser);
aWin.gBrowser.loadURI(testURI);
});
};
info("Test on private window");
checkDisableOption(true, privateWin);
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();
});
});
testOnWindow(true, function(aWin) {
info("Test on private window");
checkDisableOption(true, aWin, function() {
testOnWindow(false, function(aPrivWin) {
info("Test on public window");
checkDisableOption(false, aPrivWin, finish);
});
});
});
}
let win = yield BrowserTestUtils.openNewBrowserWindow();
let browser = win.gBrowser.selectedBrowser;
browser.loadURI(testURI);
yield BrowserTestUtils.browserLoaded(browser);
info("Test on public window");
checkDisableOption(false, win);
// Cleanup
yield BrowserTestUtils.closeWindow(privateWin);
yield BrowserTestUtils.closeWindow(win);
});

View File

@ -5,65 +5,50 @@
// This test makes sure that the geolocation prompt does not show a remember
// control inside the private browsing mode.
function test() {
add_task(function* test() {
const testPageURL = "http://mochi.test:8888/browser/" +
"browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html";
waitForExplicitFinish();
function checkGeolocation(aPrivateMode, aWindow, aCallback) {
executeSoon(function() {
aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab();
aWindow.gBrowser.selectedBrowser.addEventListener("load", function () {
if (aWindow.content.location != testPageURL) {
aWindow.content.location = testPageURL;
return;
}
aWindow.gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
function checkGeolocation(aPrivateMode, aWindow) {
return Task.spawn(function* () {
aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(testPageURL);
yield BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser);
function runTest() {
let notification = aWindow.PopupNotifications.getNotification("geolocation");
if (!notification) {
// Wait until the notification is available
executeSoon(runTest);
return;
}
if (aPrivateMode) {
// Make sure the notification is correctly displayed without a remember control
is(notification.secondaryActions.length, 0, "Secondary actions shouldn't exist (always/never remember)");
} else {
ok(notification.secondaryActions.length > 1, "Secondary actions should exist (always/never remember)");
}
notification.remove();
let notification = aWindow.PopupNotifications.getNotification("geolocation");
aWindow.gBrowser.removeCurrentTab();
aCallback();
}
runTest();
}, true);
// Wait until the notification is available.
while (!notification){
yield new Promise(resolve => { executeSoon(resolve); });
let notification = aWindow.PopupNotifications.getNotification("geolocation");
}
if (aPrivateMode) {
// Make sure the notification is correctly displayed without a remember control
is(notification.secondaryActions.length, 0, "Secondary actions shouldn't exist (always/never remember)");
} else {
ok(notification.secondaryActions.length > 1, "Secondary actions should exist (always/never remember)");
}
notification.remove();
aWindow.gBrowser.removeCurrentTab();
});
};
let windowsToClose = [];
function testOnWindow(options, callback) {
let win = OpenBrowserWindow(options);
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
windowsToClose.push(win);
callback(win);
}, false);
};
let win = yield BrowserTestUtils.openNewBrowserWindow();
let browser = win.gBrowser.selectedBrowser;
browser.loadURI(testPageURL);
yield BrowserTestUtils.browserLoaded(browser);
registerCleanupFunction(function() {
windowsToClose.forEach(function(win) {
win.close();
});
});
yield checkGeolocation(false, win);
testOnWindow({private: false}, function(win) {
checkGeolocation(false, win, function() {
testOnWindow({private: true}, function(win) {
checkGeolocation(true, win, finish);
});
});
});
}
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
let privateBrowser = privateWin.gBrowser.selectedBrowser;
privateBrowser.loadURI(testPageURL);
yield BrowserTestUtils.browserLoaded(privateBrowser);
yield checkGeolocation(true, privateWin);
// Cleanup
yield BrowserTestUtils.closeWindow(win);
yield BrowserTestUtils.closeWindow(privateWin);
});

View File

@ -2,53 +2,24 @@
* 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/. */
function test() {
add_task(function test() {
requestLongerTimeout(2);
waitForExplicitFinish();
const page1 = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
'browser_privatebrowsing_localStorage_page1.html'
function checkLocalStorage(aWindow, aCallback) {
executeSoon(function() {
let tab = aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab();
let browser = aWindow.gBrowser.selectedBrowser;
browser.addEventListener('load', function() {
if (browser.contentWindow.location != page1) {
browser.loadURI(page1);
return;
}
browser.removeEventListener('load', arguments.callee, true);
let tab2 = aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab();
browser.contentWindow.location = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
'browser_privatebrowsing_localStorage_page2.html';
browser.addEventListener('load', function() {
browser.removeEventListener('load', arguments.callee, true);
is(browser.contentWindow.document.title, '2', "localStorage should contain 2 items");
aCallback();
}, true);
}, true);
});
}
let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
let windowsToClose = [];
function testOnWindow(options, callback) {
let win = OpenBrowserWindow(options);
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
windowsToClose.push(win);
callback(win);
}, false);
};
let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(page1);
let browser = win.gBrowser.selectedBrowser;
yield BrowserTestUtils.browserLoaded(browser);
registerCleanupFunction(function() {
windowsToClose.forEach(function(win) {
win.close();
});
});
browser.loadURI(
'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
'browser_privatebrowsing_localStorage_page2.html');
yield BrowserTestUtils.browserLoaded(browser);
testOnWindow({private: true}, function(win) {
checkLocalStorage(win, finish);
});
is(browser.contentTitle, '2', "localStorage should contain 2 items");
}
// Cleanup
yield BrowserTestUtils.closeWindow(win);
});

View File

@ -10,57 +10,27 @@
// Step 2: Load the same page in a non-private tab, ensuring that the storage instance reports only one item
// existing.
function test() {
// initialization
waitForExplicitFinish();
let windowsToClose = [];
add_task(function test() {
let testURI = "about:blank";
let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/';
function doTest(aIsPrivateMode, aWindow, aCallback) {
aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
// Step 1.
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
let privateBrowser = privateWin.gBrowser.addTab(
prefix + 'browser_privatebrowsing_localStorage_before_after_page.html').linkedBrowser;
yield BrowserTestUtils.browserLoaded(privateBrowser);
if (aIsPrivateMode) {
// do something when aIsPrivateMode is true
is(aWindow.gBrowser.contentWindow.document.title, '1', "localStorage should contain 1 item");
} else {
// do something when aIsPrivateMode is false
is(aWindow.gBrowser.contentWindow.document.title, 'null|0', 'localStorage should contain 0 items');
}
is(privateBrowser.contentTitle, '1', "localStorage should contain 1 item");
aCallback();
}, true);
// Step 2.
let win = yield BrowserTestUtils.openNewBrowserWindow();
let browser = win.gBrowser.addTab(
prefix + 'browser_privatebrowsing_localStorage_before_after_page2.html').linkedBrowser;
yield BrowserTestUtils.browserLoaded(browser);
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
}
is(browser.contentTitle, 'null|0', 'localStorage should contain 0 items');
function testOnWindow(aOptions, aCallback) {
whenNewWindowLoaded(aOptions, function(aWin) {
windowsToClose.push(aWin);
// execute should only be called when need, like when you are opening
// web pages on the test. If calling executeSoon() is not necesary, then
// call whenNewWindowLoaded() instead of testOnWindow() on your test.
executeSoon(function() aCallback(aWin));
});
};
// this function is called after calling finish() on the test.
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();
});
});
// test first when on private mode
testOnWindow({private: true}, function(aWin) {
testURI = prefix + 'browser_privatebrowsing_localStorage_before_after_page.html';
doTest(true, aWin, function() {
// then test when not on private mode
testOnWindow({}, function(aWin) {
testURI = prefix + 'browser_privatebrowsing_localStorage_before_after_page2.html';
doTest(false, aWin, finish);
});
});
});
}
// Cleanup
yield BrowserTestUtils.closeWindow(privateWin);
yield BrowserTestUtils.closeWindow(win);
});

View File

@ -4,108 +4,69 @@
// Test to make sure that the visited page titles do not get updated inside the
// private browsing mode.
"use strict";
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");
function test() {
waitForExplicitFinish();
add_task(function* test() {
const TEST_URL = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html"
const TEST_URI = Services.io.newURI(TEST_URL, null, null);
const TITLE_1 = "Title 1";
const TITLE_2 = "Title 2";
let selectedWin = null;
let windowsToClose = [];
let tabToClose = null;
let testNumber = 0;
let historyObserver;
function waitForTitleChanged() {
return new Promise(resolve => {
let historyObserver = {
onTitleChanged: function(uri, pageTitle) {
PlacesUtils.history.removeObserver(historyObserver, false);
resolve({uri: uri, pageTitle: pageTitle});
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onVisit: function () {},
onDeleteURI: function () {},
onClearHistory: function () {},
onPageChanged: function () {},
onDeleteVisits: function() {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
};
registerCleanupFunction(function() {
PlacesUtils.history.removeObserver(historyObserver, false);
windowsToClose.forEach(function(aWin) {
aWin.close();
PlacesUtils.history.addObserver(historyObserver, false);
});
gBrowser.removeTab(tabToClose);
};
yield PlacesTestUtils.clearHistory();
let tabToClose = gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
yield waitForTitleChanged();
is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_1, "The title matches the orignal title after first visit");
let place = {
uri: TEST_URI,
title: TITLE_2,
visits: [{
visitDate: Date.now() * 1000,
transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
}]
};
PlacesUtils.asyncHistory.updatePlaces(place, {
handleError: function () ok(false, "Unexpected error in adding visit."),
handleResult: function () { },
handleCompletion: function () {}
});
yield waitForTitleChanged();
is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title matches the updated title after updating visit");
PlacesTestUtils.clearHistory().then(() => {
historyObserver = {
onTitleChanged: function(aURI, aPageTitle) {
switch (++testNumber) {
case 1:
afterFirstVisit();
break;
case 2:
afterUpdateVisit();
break;
}
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onVisit: function () {},
onDeleteURI: function () {},
onClearHistory: function () {},
onPageChanged: function () {},
onDeleteVisits: function() {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
};
PlacesUtils.history.addObserver(historyObserver, false);
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private:true});
yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.addTab(TEST_URL).linkedBrowser);
tabToClose = gBrowser.addTab();
gBrowser.selectedTab = tabToClose;
whenPageLoad(window, function() {});
});
is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title remains the same after visiting in private window");
yield PlacesTestUtils.clearHistory();
function afterFirstVisit() {
is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_1, "The title matches the orignal title after first visit");
let place = {
uri: TEST_URI,
title: TITLE_2,
visits: [{
visitDate: Date.now() * 1000,
transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
}]
};
PlacesUtils.asyncHistory.updatePlaces(place, {
handleError: function () do_throw("Unexpected error in adding visit."),
handleResult: function () { },
handleCompletion: function () {}
});
}
function afterUpdateVisit() {
is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title matches the updated title after updating visit");
testOnWindow(true, function(aWin) {
whenPageLoad(aWin, function() {
executeSoon(afterFirstVisitInPrivateWindow);
});
});
}
function afterFirstVisitInPrivateWindow() {
is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title remains the same after visiting in private window");
PlacesTestUtils.clearHistory().then(finish);
}
function whenPageLoad(aWin, aCallback) {
aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
aCallback();
}, true);
aWin.gBrowser.selectedBrowser.loadURI(TEST_URL);
}
function testOnWindow(aPrivate, aCallback) {
whenNewWindowLoaded({ private: aPrivate }, function(aWin) {
selectedWin = aWin;
windowsToClose.push(aWin);
executeSoon(function() { aCallback(aWin) });
});
}
}
// Cleanup
BrowserTestUtils.closeWindow(privateWin);
gBrowser.removeTab(tabToClose);
});

View File

@ -5,20 +5,24 @@
// This test makes sure that the title of existing history entries does not
// change inside a private window.
function test() {
waitForExplicitFinish();
add_task(function* test() {
const TEST_URL = "http://mochi.test:8888/browser/browser/components/" +
"privatebrowsing/test/browser/title.sjs";
let cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
let cm = Services.cookies;
function waitForCleanup(aCallback) {
function cleanup() {
// delete all cookies
cm.removeAll();
// delete all history items
PlacesTestUtils.clearHistory().then(aCallback);
return PlacesTestUtils.clearHistory();
}
yield cleanup();
let deferredFirst = PromiseUtils.defer();
let deferredSecond = PromiseUtils.defer();
let deferredThird = PromiseUtils.defer();
let testNumber = 0;
let historyObserver = {
onTitleChanged: function(aURI, aPageTitle) {
@ -27,27 +31,15 @@ function test() {
switch (++testNumber) {
case 1:
// The first time that the page is loaded
is(aPageTitle, "No Cookie",
"The page should be loaded without any cookie for the first time");
openTestPage(selectedWin);
deferredFirst.resolve(aPageTitle);
break;
case 2:
// The second time that the page is loaded
is(aPageTitle, "Cookie",
"The page should be loaded with a cookie for the second time");
waitForCleanup(function () {
openTestPage(selectedWin);
});
deferredSecond.resolve(aPageTitle);
break;
case 3:
// After clean up
is(aPageTitle, "No Cookie",
"The page should be loaded without any cookie again");
testOnWindow(true, function(win) {
whenPageLoad(win, function() {
waitForCleanup(finish);
});
});
deferredThird.resolve(aPageTitle);
break;
default:
// Checks that opening the page in a private window should not fire a
@ -67,38 +59,37 @@ function test() {
};
PlacesUtils.history.addObserver(historyObserver, false);
let selectedWin = null;
let windowsToClose = [];
registerCleanupFunction(function() {
PlacesUtils.history.removeObserver(historyObserver);
windowsToClose.forEach(function(win) {
win.close();
});
});
function openTestPage(aWin) {
aWin.gBrowser.selectedTab = aWin.gBrowser.addTab(TEST_URL);
}
let win = yield BrowserTestUtils.openNewBrowserWindow();
win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
let aPageTitle = yield deferredFirst.promise;
// The first time that the page is loaded
is(aPageTitle, "No Cookie",
"The page should be loaded without any cookie for the first time");
function whenPageLoad(aWin, aCallback) {
aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
aCallback();
}, true);
aWin.gBrowser.selectedBrowser.loadURI(TEST_URL);
}
win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
aPageTitle = yield deferredSecond.promise;
// The second time that the page is loaded
is(aPageTitle, "Cookie",
"The page should be loaded with a cookie for the second time");
function testOnWindow(aPrivate, aCallback) {
whenNewWindowLoaded({ private: aPrivate }, function(win) {
selectedWin = win;
windowsToClose.push(win);
executeSoon(function() { aCallback(win) });
});
}
yield cleanup();
waitForCleanup(function() {
testOnWindow(false, function(win) {
openTestPage(win);
});
});
}
win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
aPageTitle = yield deferredThird.promise;
// After clean up
is(aPageTitle, "No Cookie",
"The page should be loaded without any cookie again");
let win2 = yield BrowserTestUtils.openNewBrowserWindow({private: true});
let private_tab = win2.gBrowser.addTab(TEST_URL);
win2.gBrowser.selectedTab = private_tab;
yield BrowserTestUtils.browserLoaded(private_tab.linkedBrowser);
// Cleanup
yield cleanup();
PlacesUtils.history.removeObserver(historyObserver);
yield BrowserTestUtils.closeWindow(win);
yield BrowserTestUtils.closeWindow(win2);
});

View File

@ -4,15 +4,15 @@
// This test makes sure that private browsing mode disables the remember option
// for the popup blocker menu.
function test() {
// initialization
waitForExplicitFinish();
add_task(function* test() {
let testURI = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/popup.html";
let windowsToClose = [];
let oldPopupPolicy = gPrefService.getBoolPref("dom.disable_open_during_load");
gPrefService.setBoolPref("dom.disable_open_during_load", true);
registerCleanupFunction(() => {
gPrefService.setBoolPref("dom.disable_open_during_load", oldPopupPolicy);
});
function testPopupBlockerMenuItem(aExpectedDisabled, aWindow, aCallback) {
aWindow.gBrowser.addEventListener("DOMUpdatePageReport", function() {
@ -51,32 +51,17 @@ function test() {
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
}
function finishTest() {
// cleanup
gPrefService.setBoolPref("dom.disable_open_during_load", oldPopupPolicy);
finish();
};
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
yield new Promise(resolve => testPopupBlockerMenuItem(false, win1, resolve));
function testOnWindow(options, callback) {
let win = whenNewWindowLoaded(options, callback);
windowsToClose.push(win);
};
let win2 = yield BrowserTestUtils.openNewBrowserWindow({private: true});
yield new Promise(resolve => testPopupBlockerMenuItem(true, win2, resolve));
registerCleanupFunction(function() {
windowsToClose.forEach(function(win) {
win.close();
});
});
let win3 = yield BrowserTestUtils.openNewBrowserWindow();
yield new Promise(resolve => testPopupBlockerMenuItem(false, win3, resolve));
testOnWindow({}, function(win) {
testPopupBlockerMenuItem(false, win, function() {
testOnWindow({private: true}, function(win) {
testPopupBlockerMenuItem(true, win, function() {
testOnWindow({}, function(win) {
testPopupBlockerMenuItem(false, win, finishTest);
})
});
})
});
});
}
// Cleanup
yield BrowserTestUtils.closeWindow(win1);
yield BrowserTestUtils.closeWindow(win2);
yield BrowserTestUtils.closeWindow(win3);
});

View File

@ -5,61 +5,43 @@
// This test makes sure that the web pages can't register protocol handlers
// inside the private browsing mode.
function test() {
// initialization
waitForExplicitFinish();
let windowsToClose = [];
add_task(function* test() {
let notificationValue = "Protocol Registration: testprotocol";
let testURI = "http://example.com/browser/" +
"browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html";
function doTest(aIsPrivateMode, aWindow, aCallback) {
aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
let doTest = Task.async(function* (aIsPrivateMode, aWindow) {
let tab = aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(testURI);
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
setTimeout(function() {
let notificationBox = aWindow.gBrowser.getNotificationBox();
let notification = notificationBox.getNotificationWithValue(notificationValue);
let promiseFinished = PromiseUtils.defer();
setTimeout(function() {
let notificationBox = aWindow.gBrowser.getNotificationBox();
let notification = notificationBox.getNotificationWithValue(notificationValue);
if (aIsPrivateMode) {
// Make sure the notification is correctly displayed without a remember control
ok(!notification, "Notification box should not be displayed inside of private browsing mode");
} else {
// Make sure the notification is correctly displayed with a remember control
ok(notification, "Notification box should be displaying outside of private browsing mode");
}
if (aIsPrivateMode) {
// Make sure the notification is correctly displayed without a remember control
ok(!notification, "Notification box should not be displayed inside of private browsing mode");
} else {
// Make sure the notification is correctly displayed with a remember control
ok(notification, "Notification box should be displaying outside of private browsing mode");
}
aCallback();
}, 100); // remember control is added in a setTimeout(0) call
}, true);
promiseFinished.resolve();
}, 100); // remember control is added in a setTimeout(0) call
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
}
function testOnWindow(aOptions, aCallback) {
whenNewWindowLoaded(aOptions, function(aWin) {
windowsToClose.push(aWin);
// execute should only be called when need, like when you are opening
// web pages on the test. If calling executeSoon() is not necesary, then
// call whenNewWindowLoaded() instead of testOnWindow() on your test.
executeSoon(function() aCallback(aWin));
});
};
// this function is called after calling finish() on the test.
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();
});
yield promiseFinished.promise;
});
// test first when not on private mode
testOnWindow({}, function(aWin) {
doTest(false, aWin, function() {
// then test when on private mode
testOnWindow({private: true}, function(aWin) {
doTest(true, aWin, finish);
});
});
});
}
let win = yield BrowserTestUtils.openNewBrowserWindow();
yield doTest(false, win);
// then test when on private mode
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
yield doTest(true, privateWin);
// Cleanup
yield BrowserTestUtils.closeWindow(win);
yield BrowserTestUtils.closeWindow(privateWin);
});

View File

@ -1,6 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
let {PromiseUtils} = Cu.import("resource://gre/modules/PromiseUtils.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");

View File

@ -270,9 +270,7 @@ let RLSidebar = {
* @param {Event} event - KeyEvent or MouseEvent that triggered this action.
*/
openURL(url, event) {
// TODO: Disabled while working on the listbox mechanics.
log.debug(`Opening page ${url}`);
return;
let mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)

View File

@ -33,6 +33,7 @@ support-files =
[browser_cmd_cookie.js]
support-files =
browser_cmd_cookie.html
[browser_cmd_cookie_host.js]
[browser_cmd_csscoverage_oneshot.js]
support-files =
browser_cmd_csscoverage_page1.html

View File

@ -0,0 +1,39 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the cookie command works for host with a port specified
const TEST_URI = "http://mochi.test:8888/browser/browser/devtools/commandline/"+
"test/browser_cmd_cookie.html";
function test() {
helpers.addTabWithToolbar(TEST_URI, function(options) {
return helpers.audit(options, [
{
setup: 'cookie list',
exec: {
output: [ /zap=zep/, /zip=zop/ ],
}
},
{
setup: "cookie set zup banana",
check: {
args: {
name: { value: 'zup' },
value: { value: 'banana' },
}
},
exec: {
output: ""
}
},
{
setup: "cookie list",
exec: {
output: [ /zap=zep/, /zip=zop/, /zup=banana/, /Edit/ ]
}
}
]);
}).then(finish, helpers.handleError);
}

View File

@ -808,7 +808,7 @@ InspectorPanel.prototype = {
let jsterm = panel.hud.jsterm;
jsterm.execute("inspect($0)");
jsterm.focusInput();
jsterm.inputNode.focus();
});
},

View File

@ -12,13 +12,13 @@ support-files =
doc_inspector_highlighter-geometry_02.html
doc_inspector_highlighter_csstransform.html
doc_inspector_highlighter_dom.html
doc_inspector_highlighter_inline.html
doc_inspector_highlighter.html
doc_inspector_highlighter_rect.html
doc_inspector_highlighter_rect_iframe.html
doc_inspector_infobar_01.html
doc_inspector_infobar_02.html
doc_inspector_menu-01.html
doc_inspector_menu-02.html
doc_inspector_menu.html
doc_inspector_remove-iframe-during-load.html
doc_inspector_search.html
doc_inspector_search-suggestions.html
@ -52,6 +52,7 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
[browser_inspector_highlighter-hover_02.js]
[browser_inspector_highlighter-hover_03.js]
[browser_inspector_highlighter-iframes.js]
[browser_inspector_highlighter-inline.js]
[browser_inspector_highlighter-keybinding_01.js]
[browser_inspector_highlighter-keybinding_02.js]
[browser_inspector_highlighter-keybinding_03.js]
@ -67,8 +68,10 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
[browser_inspector_inspect-object-element.js]
[browser_inspector_invalidate.js]
[browser_inspector_keyboard-shortcuts.js]
[browser_inspector_menu-01.js]
[browser_inspector_menu-02.js]
[browser_inspector_menu-01-sensitivity.js]
[browser_inspector_menu-02-copy-items.js]
[browser_inspector_menu-03-paste-items.js]
[browser_inspector_menu-04-other.js]
[browser_inspector_navigation.js]
[browser_inspector_picker-stop-on-destroy.js]
[browser_inspector_picker-stop-on-tool-change.js]

View File

@ -21,7 +21,7 @@ add_task(function*() {
let highlightedNode = yield getHighlitNode(toolbox);
is(highlightedNode, getNode("#simple-div"),
"The highlighter's outline corresponds to the simple div");
yield isNodeCorrectlyHighlighted(getNode("#simple-div"), toolbox,
yield isNodeCorrectlyHighlighted("#simple-div", toolbox,
"non-zoomed");
info("Selecting the rotated DIV");
@ -29,7 +29,7 @@ add_task(function*() {
isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown");
yield isNodeCorrectlyHighlighted(getNode("#rotated-div"), toolbox,
yield isNodeCorrectlyHighlighted("#rotated-div", toolbox,
"rotated");
info("Selecting the zero width height DIV");
@ -37,6 +37,6 @@ add_task(function*() {
isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown");
yield isNodeCorrectlyHighlighted(getNode("#widthHeightZero-div"), toolbox,
yield isNodeCorrectlyHighlighted("#widthHeightZero-div", toolbox,
"zero width height");
});

View File

@ -45,7 +45,7 @@ add_task(function* () {
yield moveMouseOver(iframeNode, 1, 1);
info("Performing checks");
yield isNodeCorrectlyHighlighted(iframeNode, toolbox);
yield isNodeCorrectlyHighlighted("iframe", toolbox);
info("Scrolling the document");
iframeNode.style.marginBottom = content.innerHeight + "px";
@ -58,7 +58,7 @@ add_task(function* () {
let highlightedNode = yield getHighlitNode(toolbox);
is(highlightedNode, iframeBodyNode, "highlighter shows the right node");
yield isNodeCorrectlyHighlighted(iframeBodyNode, toolbox);
yield isNodeCorrectlyHighlighted("iframe || body", toolbox);
info("Waiting for the element picker to deactivate.");
yield inspector.toolbox.highlighterUtils.stopPicker();

View File

@ -26,14 +26,15 @@ add_task(function*() {
let highlighter = yield front.getHighlighterByType("CssTransformHighlighter");
let node = getNode("#test-node");
let nodeFront = yield getNodeFront("#test-node", inspector);
info("Displaying the transform highlighter on test node");
yield highlighter.show(nodeFront);
let {data} = yield executeInContent("Test:GetAllAdjustedQuads", null, {node});
let expected = data.border;
let {data} = yield executeInContent("Test:GetAllAdjustedQuads", {
selector: "#test-node"
});
let [expected] = data.border;
let points = yield getHighlighterNodeAttribute(highlighter,
"css-transform-transformed", "points");

View File

@ -0,0 +1,72 @@
/* 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";
// Test that highlighting various inline boxes displays the right number of
// polygons in the page.
const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_inline.html";
const TEST_DATA = [
"body",
"h1",
"h2",
"h2 em",
"p",
"p span",
// The following test case used to fail. See bug 1139925.
"[dir=rtl] > span"
];
add_task(function*() {
info("Loading the test document and opening the inspector");
let {toolbox, inspector} = yield openInspectorForURL(TEST_URL);
for (let selector of TEST_DATA) {
info("Selecting and highlighting node " + selector);
yield selectAndHighlightNode(selector, inspector);
info("Get all quads for this node");
let {data} = yield executeInContent("Test:GetAllAdjustedQuads", {selector});
info("Iterate over the box-model regions and verify that the highlighter is correct");
for (let region of ["margin", "border", "padding", "content"]) {
let {points} = yield getHighlighterRegionPath(region, toolbox.highlighter);
is(points.length, data[region].length,
"The highlighter's " + region + " path defines the correct number of boxes");
}
info("Verify that the guides define a rectangle that contains all content boxes");
let expectedContentRect = {
p1: {x: Infinity, y: Infinity},
p2: {x: -Infinity, y: Infinity},
p3: {x: -Infinity, y: -Infinity},
p4: {x: Infinity, y: -Infinity}
};
for (let {p1, p2, p3, p4} of data.content) {
expectedContentRect.p1.x = Math.min(expectedContentRect.p1.x, p1.x);
expectedContentRect.p1.y = Math.min(expectedContentRect.p1.y, p1.y);
expectedContentRect.p2.x = Math.max(expectedContentRect.p2.x, p2.x);
expectedContentRect.p2.y = Math.min(expectedContentRect.p2.y, p2.y);
expectedContentRect.p3.x = Math.max(expectedContentRect.p3.x, p3.x);
expectedContentRect.p3.y = Math.max(expectedContentRect.p3.y, p3.y);
expectedContentRect.p4.x = Math.min(expectedContentRect.p4.x, p4.x);
expectedContentRect.p4.y = Math.max(expectedContentRect.p4.y, p4.y);
}
let contentRect = yield getGuidesRectangle(toolbox);
for (let point of ["p1", "p2", "p3", "p4"]) {
is((contentRect[point].x),
(expectedContentRect[point].x),
"x coordinate of point " + point +
" of the content rectangle defined by the outer guides is correct");
is((contentRect[point].y),
(expectedContentRect[point].y),
"y coordinate of point " + point +
" of the content rectangle defined by the outer guides is correct");
}
}
});

View File

@ -41,9 +41,8 @@ const TEST_DATA = [
options: {},
checkHighlighter: function*(toolbox) {
for (let region of ["margin", "border", "padding", "content"]) {
let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-" + region, "points");
ok(points, "Region " + region + " has set coordinates");
let {d} = yield getHighlighterRegionPath(region, toolbox.highlighter);
ok(d, "Region " + region + " has set coordinates");
}
}
},
@ -71,42 +70,34 @@ const TEST_DATA = [
desc: "One region only can be shown (1)",
options: {showOnly: "content"},
checkHighlighter: function*(toolbox) {
let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-margin", "points");
ok(!points, "margin region is hidden");
let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
ok(!d, "margin region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-border", "points");
ok(!points, "border region is hidden");
({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter);
ok(!d, "border region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-padding", "points");
ok(!points, "padding region is hidden");
({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter);
ok(!d, "padding region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-content", "points");
ok(points, "content region is shown");
({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter);
ok(d, "content region is shown");
}
},
{
desc: "One region only can be shown (2)",
options: {showOnly: "margin"},
checkHighlighter: function*(toolbox) {
let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-margin", "points");
ok(points, "margin region is shown");
let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
ok(d, "margin region is shown");
points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-border", "points");
ok(!points, "border region is hidden");
({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter);
ok(!d, "border region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-padding", "points");
ok(!points, "padding region is hidden");
({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter);
ok(!d, "padding region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-content", "points");
ok(!points, "content region is hidden");
({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter);
ok(!d, "content region is hidden");
}
},
{
@ -122,9 +113,8 @@ const TEST_DATA = [
let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-guide-left", "x1");
let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-padding", "points");
points = points.split(" ").map(xy => xy.split(","));
let {points} = yield getHighlighterRegionPath("padding", toolbox.highlighter);
points = points[0];
is(Math.ceil(topY1), points[0][1], "Top guide's y1 is correct");
is(Math.floor(rightX1), points[1][0], "Right guide's x1 is correct");
@ -145,10 +135,9 @@ const TEST_DATA = [
let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-guide-left", "x1");
let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-margin", "points");
let {points} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
points = points[0];
points = points.split(" ").map(xy => xy.split(","));
is(Math.ceil(topY1), points[0][1], "Top guide's y1 is correct");
is(Math.floor(rightX1), points[1][0], "Right guide's x1 is correct");
is(Math.floor(bottomY1), points[2][1], "Bottom guide's y1 is correct");

View File

@ -41,7 +41,7 @@ add_task(function*() {
isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is still visible at zoom level " + level);
yield isNodeCorrectlyHighlighted(getNode("div"), toolbox);
yield isNodeCorrectlyHighlighted("div", toolbox);
info("Check that the highlighter root wrapper node was scaled down");

View File

@ -0,0 +1,202 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that context menu items are enabled / disabled correctly.
const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
const PASTE_MENU_ITEMS = [
"node-menu-pasteinnerhtml",
"node-menu-pasteouterhtml",
"node-menu-pastebefore",
"node-menu-pasteafter",
"node-menu-pastefirstchild",
"node-menu-pastelastchild",
];
const ALL_MENU_ITEMS = [
"node-menu-edithtml",
"node-menu-copyinner",
"node-menu-copyouter",
"node-menu-copyuniqueselector",
"node-menu-copyimagedatauri",
"node-menu-showdomproperties",
"node-menu-delete",
"node-menu-pseudo-hover",
"node-menu-pseudo-active",
"node-menu-pseudo-focus"
].concat(PASTE_MENU_ITEMS);
const ITEMS_WITHOUT_SHOWDOMPROPS =
ALL_MENU_ITEMS.filter(item => item != "node-menu-showdomproperties");
const TEST_CASES = [
{
desc: "doctype node with empty clipboard",
selector: null,
disabled: ITEMS_WITHOUT_SHOWDOMPROPS,
},
{
desc: "doctype node with html on clipboard",
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
selector: null,
disabled: ITEMS_WITHOUT_SHOWDOMPROPS,
},
{
desc: "element node HTML on the clipboard",
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
disabled: ["node-menu-copyimagedatauri"],
selector: "#sensitivity",
},
{
desc: "<html> element",
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
selector: "html",
disabled: [
"node-menu-copyimagedatauri",
"node-menu-pastebefore",
"node-menu-pasteafter",
"node-menu-pastefirstchild",
"node-menu-pastelastchild",
],
},
{
desc: "<body> with HTML on clipboard",
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
selector: "body",
disabled: [
"node-menu-copyimagedatauri",
"node-menu-pastebefore",
"node-menu-pasteafter",
]
},
{
desc: "<img> with HTML on clipboard",
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
selector: "img",
disabled: []
},
{
desc: "<head> with HTML on clipboard",
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
selector: "head",
disabled: [
"node-menu-copyimagedatauri",
"node-menu-pastebefore",
"node-menu-pasteafter",
]
},
{
desc: "<element> with text on clipboard",
clipboardData: "some text",
clipboardDataType: undefined,
selector: "#paste-area",
disabled: ["node-menu-copyimagedatauri"],
},
{
desc: "<element> with base64 encoded image data uri on clipboard",
clipboardData:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
"AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
clipboardDataType: undefined,
selector: "#paste-area",
disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
},
{
desc: "<element> with empty string on clipboard",
clipboardData: "",
clipboardDataType: undefined,
selector: "#paste-area",
disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
},
{
desc: "<element> with whitespace only on clipboard",
clipboardData: " \n\n\t\n\n \n",
clipboardDataType: undefined,
selector: "#paste-area",
disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
},
];
let clipboard = require("sdk/clipboard");
registerCleanupFunction(() => {
clipboard = null;
});
add_task(function *() {
let { inspector } = yield openInspectorForURL(TEST_URL);
for (let test of TEST_CASES) {
let { desc, disabled, selector } = test;
info(`Test ${desc}`);
setupClipboard(test.clipboardData, test.clipboardDataType);
let front = yield getNodeFrontForSelector(selector, inspector);
info("Selecting the specified node.");
yield selectNode(front, inspector);
info("Simulating context menu click on the selected node container.");
contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
for (let menuitem of ALL_MENU_ITEMS) {
let elt = inspector.panelDoc.getElementById(menuitem);
let shouldBeDisabled = disabled.indexOf(menuitem) !== -1;
let isDisabled = elt.hasAttribute("disabled");
is(isDisabled, shouldBeDisabled,
`#${menuitem} should be ${shouldBeDisabled ? "disabled" : "enabled"} `);
}
}
});
/**
* A helper that fetches a front for a node that matches the given selector or
* doctype node if the selector is falsy.
*/
function* getNodeFrontForSelector(selector, inspector) {
if (selector) {
info("Retrieving front for selector " + selector);
return getNodeFront(selector, inspector);
} else {
info("Retrieving front for doctype node");
let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
return nodes[0];
}
}
/**
* A helper that populates the clipboard with data of given type. Clears the
* clipboard if data is falsy.
*/
function setupClipboard(data, type) {
if (data) {
info("Populating clipboard with " + type + " data.");
clipboard.set(data, type);
} else {
info("Clearing clipboard.");
clipboard.set("", "text");
}
}
/**
* A helper that simulates a contextmenu event on the given chrome DOM element.
*/
function contextMenuClick(element) {
let evt = element.ownerDocument.createEvent('MouseEvents');
let button = 2; // right click
evt.initMouseEvent('contextmenu', true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, button, null);
element.dispatchEvent(evt);
}

View File

@ -1,201 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
///////////////////
//
// Whitelisting this test.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
//
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: jsterm.focusInput is not a function");
// Test context menu functionality:
// 1) menu items are disabled/enabled depending on the clicked node
// 2) actions triggered by the items work correctly
const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu-01.html";
const MENU_SENSITIVITY_TEST_DATA = [
{
desc: "doctype node",
selector: null,
disabled: true,
},
{
desc: "element node",
selector: "p",
disabled: false,
}
];
const COPY_ITEMS_TEST_DATA = [
{
desc: "copy inner html",
id: "node-menu-copyinner",
text: "This is some example text",
},
{
desc: "copy outer html",
id: "node-menu-copyouter",
text: "<p>This is some example text</p>",
},
{
desc: "copy unique selector",
id: "node-menu-copyuniqueselector",
text: "body > div:nth-child(1) > p:nth-child(2)",
},
];
let clipboard = require("sdk/clipboard");
registerCleanupFunction(() => {
clipboard = null;
});
add_task(function* () {
let { inspector, toolbox } = yield openInspectorForURL(TEST_URL);
yield testMenuItemSensitivity();
yield testCopyMenuItems();
yield testShowDOMProperties();
yield testDeleteNode();
yield testDeleteRootNode();
function* testMenuItemSensitivity() {
info("Testing sensitivity of menu items for different elements.");
// The sensibility for paste options are described in browser_inspector_menu-02.js
const MENU_ITEMS = [
"node-menu-copyinner",
"node-menu-copyouter",
"node-menu-copyuniqueselector",
"node-menu-delete",
"node-menu-pseudo-hover",
"node-menu-pseudo-active",
"node-menu-pseudo-focus"
];
for (let {desc, selector, disabled} of MENU_SENSITIVITY_TEST_DATA) {
info("Testing context menu entries for " + desc);
let front;
if (selector) {
front = yield getNodeFront(selector, inspector);
} else {
// Select the docType if no selector is provided
let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
front = nodes[0];
}
yield selectNode(front, inspector);
contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
for (let name of MENU_ITEMS) {
checkMenuItem(name, disabled);
}
}
}
function* testCopyMenuItems() {
info("Testing various copy actions of context menu.");
for (let {desc, id, text} of COPY_ITEMS_TEST_DATA) {
info("Testing " + desc);
let item = inspector.panelDoc.getElementById(id);
ok(item, "The popup has a " + desc + " menu item.");
let deferred = promise.defer();
waitForClipboard(text, () => item.doCommand(),
deferred.resolve, deferred.reject);
yield deferred.promise;
}
}
function* testShowDOMProperties() {
info("Testing 'Show DOM Properties' menu item.");
let showDOMPropertiesNode = inspector.panelDoc.getElementById("node-menu-showdomproperties");
ok(showDOMPropertiesNode, "the popup menu has a show dom properties item");
let consoleOpened = toolbox.once("webconsole-ready");
info("Triggering 'Show DOM Properties' and waiting for inspector open");
dispatchCommandEvent(showDOMPropertiesNode);
yield consoleOpened;
let webconsoleUI = toolbox.getPanel("webconsole").hud.ui;
let messagesAdded = webconsoleUI.once("new-messages");
yield messagesAdded;
info("Checking if 'inspect($0)' was evaluated");
ok(webconsoleUI.jsterm.history[0] === 'inspect($0)');
yield toolbox.toggleSplitConsole();
}
function* testDeleteNode() {
info("Testing 'Delete Node' menu item for normal elements.");
yield selectNode("p", inspector);
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
ok(deleteNode, "the popup menu has a delete menu item");
let updated = inspector.once("inspector-updated");
info("Triggering 'Delete Node' and waiting for inspector to update");
dispatchCommandEvent(deleteNode);
yield updated;
ok(!getNode("p", { expectNoMatch: true }), "Node deleted");
}
function* testDeleteRootNode() {
info("Testing 'Delete Node' menu item does not delete root node.");
yield selectNode(inspector.walker.rootNode, inspector);
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
dispatchCommandEvent(deleteNode);
executeSoon(() => {
ok(content.document.documentElement, "Document element still alive.");
});
}
function checkMenuItem(elementId, disabled) {
if (disabled) {
checkDisabled(elementId);
} else {
checkEnabled(elementId);
}
}
function checkEnabled(elementId) {
let elt = inspector.panelDoc.getElementById(elementId);
ok(!elt.hasAttribute("disabled"),
'"' + elt.label + '" context menu option is not disabled');
}
function checkDisabled(elementId) {
let elt = inspector.panelDoc.getElementById(elementId);
ok(elt.hasAttribute("disabled"),
'"' + elt.label + '" context menu option is disabled');
}
function dispatchCommandEvent(node) {
info("Dispatching command event on " + node);
let commandEvent = document.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
node.dispatchEvent(commandEvent);
}
function contextMenuClick(element) {
info("Simulating contextmenu event on " + element);
let evt = element.ownerDocument.createEvent('MouseEvents');
let button = 2; // right click
evt.initMouseEvent('contextmenu', true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, button, null);
element.dispatchEvent(evt);
}
});

View File

@ -0,0 +1,51 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the various copy items in the context menu works correctly.
const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
const COPY_ITEMS_TEST_DATA = [
{
desc: "copy inner html",
id: "node-menu-copyinner",
selector: "[data-id=\"copy\"]",
text: "Paragraph for testing copy",
},
{
desc: "copy outer html",
id: "node-menu-copyouter",
selector: "[data-id=\"copy\"]",
text: "<p data-id=\"copy\">Paragraph for testing copy</p>",
},
{
desc: "copy unique selector",
id: "node-menu-copyuniqueselector",
selector: "[data-id=\"copy\"]",
text: "body > div:nth-child(1) > p:nth-child(2)",
},
{
desc: "copy image data uri",
id: "node-menu-copyimagedatauri",
selector: "#copyimage",
text: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
"AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
},
];
add_task(function *() {
let { inspector } = yield openInspectorForURL(TEST_URL);
for (let {desc, id, selector, text} of COPY_ITEMS_TEST_DATA) {
info("Testing " + desc);
yield selectNode(selector, inspector);
let item = inspector.panelDoc.getElementById(id);
ok(item, "The popup has a " + desc + " menu item.");
let deferred = promise.defer();
waitForClipboard(text, () => item.doCommand(),
deferred.resolve, deferred.reject);
yield deferred.promise;
}
});

View File

@ -1,326 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test context menu functionality:
// 1) menu items are disabled/enabled depending on the clicked node
// 2) actions triggered by the items work correctly
///////////////////
//
// Whitelisting this test.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
//
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: jsterm.focusInput is not a function");
const MENU_SENSITIVITY_TEST_DATA = [
{
desc: "doctype node",
selector: null,
disabled: true,
},
{
desc: "element node",
selector: "#sensitivity",
disabled: false,
},
{
desc: "document element",
selector: "html",
disabled: {
"node-menu-pastebefore": true,
"node-menu-pasteafter": true,
"node-menu-pastefirstchild": true,
"node-menu-pastelastchild": true,
}
},
{
desc: "body",
selector: "body",
disabled: {
"node-menu-pastebefore": true,
"node-menu-pasteafter": true,
}
},
{
desc: "head",
selector: "head",
disabled: {
"node-menu-pastebefore": true,
"node-menu-pasteafter": true,
}
}
];
const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu-02.html";
const PASTE_HTML_TEST_SENSITIVITY_DATA = [
{
desc: "some text",
clipboardData: "some text",
clipboardDataType: undefined,
disabled: false
},
{
desc: "base64 encoded image data uri",
clipboardData:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
"AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
clipboardDataType: undefined,
disabled: true
},
{
desc: "html",
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
disabled: false
},
{
desc: "empty string",
clipboardData: "",
clipboardDataType: undefined,
disabled: true
},
{
desc: "whitespace only",
clipboardData: " \n\n\t\n\n \n",
clipboardDataType: undefined,
disabled: true
},
];
const PASTE_ADJACENT_HTML_DATA = [
{
desc: "As First Child",
clipboardData: "2",
menuId: "node-menu-pastefirstchild",
},
{
desc: "As Last Child",
clipboardData: "4",
menuId: "node-menu-pastelastchild",
},
{
desc: "Before",
clipboardData: "1",
menuId: "node-menu-pastebefore",
},
{
desc: "After",
clipboardData: "<span>5</span>",
menuId: "node-menu-pasteafter",
},
];
let clipboard = require("sdk/clipboard");
registerCleanupFunction(() => {
clipboard = null;
});
add_task(function* () {
let { inspector, toolbox } = yield openInspectorForURL(TEST_URL);
yield testMenuItemSensitivity();
yield testPasteHTMLMenuItemsSensitivity();
yield testPasteOuterHTMLMenu();
yield testPasteInnerHTMLMenu();
yield testPasteAdjacentHTMLMenu();
function* testMenuItemSensitivity() {
info("Testing sensitivity of menu items for different elements.");
const MENU_ITEMS = [
"node-menu-pasteinnerhtml",
"node-menu-pasteouterhtml",
"node-menu-pastebefore",
"node-menu-pasteafter",
"node-menu-pastefirstchild",
"node-menu-pastelastchild",
];
// To ensure clipboard contains something to paste.
clipboard.set("<p>test</p>", "html");
for (let {desc, selector, disabled} of MENU_SENSITIVITY_TEST_DATA) {
info("Testing context menu entries for " + desc);
let front;
if (selector) {
front = yield getNodeFront(selector, inspector);
} else {
// Select the docType if no selector is provided
let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
front = nodes[0];
}
yield selectNode(front, inspector);
contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
for (let name of MENU_ITEMS) {
let disabledForMenu = typeof disabled === "object" ?
disabled[name] : disabled;
info(`${name} should be ${disabledForMenu ? "disabled" : "enabled"} ` +
`for ${desc}`);
checkMenuItem(name, disabledForMenu);
}
}
}
function* testPasteHTMLMenuItemsSensitivity() {
let menus = [
"node-menu-pasteinnerhtml",
"node-menu-pasteouterhtml",
"node-menu-pastebefore",
"node-menu-pasteafter",
"node-menu-pastefirstchild",
"node-menu-pastelastchild",
];
info("Checking Paste menu items sensitivity for different types" +
"of data");
let nodeFront = yield getNodeFront("#paste-area", inspector);
let markupTagLine = getContainerForNodeFront(nodeFront, inspector).tagLine;
for (let menuId of menus) {
for (let data of PASTE_HTML_TEST_SENSITIVITY_DATA) {
let { desc, clipboardData, clipboardDataType, disabled } = data;
let menuLabel = getLabelFor("#" + menuId);
info(`Checking ${menuLabel} for ${desc}`);
clipboard.set(clipboardData, clipboardDataType);
yield selectNode(nodeFront, inspector);
contextMenuClick(markupTagLine);
checkMenuItem(menuId, disabled);
}
}
}
function* testPasteOuterHTMLMenu() {
info("Testing that 'Paste Outer HTML' menu item works.");
clipboard.set("this was pasted (outerHTML)");
let outerHTMLSelector = "#paste-area h1";
let nodeFront = yield getNodeFront(outerHTMLSelector, inspector);
yield selectNode(nodeFront, inspector);
contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine);
let onNodeReselected = inspector.markup.once("reselectedonremoved");
let menu = inspector.panelDoc.getElementById("node-menu-pasteouterhtml");
dispatchCommandEvent(menu);
info("Waiting for inspector selection to update");
yield onNodeReselected;
ok(content.document.body.outerHTML.contains(clipboard.get()),
"Clipboard content was pasted into the node's outer HTML.");
ok(!getNode(outerHTMLSelector, { expectNoMatch: true }),
"The original node was removed.");
}
function* testPasteInnerHTMLMenu() {
info("Testing that 'Paste Inner HTML' menu item works.");
clipboard.set("this was pasted (innerHTML)");
let innerHTMLSelector = "#paste-area .inner";
let getInnerHTML = () => content.document.querySelector(innerHTMLSelector).innerHTML;
let origInnerHTML = getInnerHTML();
let nodeFront = yield getNodeFront(innerHTMLSelector, inspector);
yield selectNode(nodeFront, inspector);
contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine);
let onMutation = inspector.once("markupmutation");
let menu = inspector.panelDoc.getElementById("node-menu-pasteinnerhtml");
dispatchCommandEvent(menu);
info("Waiting for mutation to occur");
yield onMutation;
ok(getInnerHTML() === clipboard.get(),
"Clipboard content was pasted into the node's inner HTML.");
ok(getNode(innerHTMLSelector), "The original node has been preserved.");
yield undoChange(inspector);
ok(getInnerHTML() === origInnerHTML, "Previous innerHTML has been " +
"restored after undo");
}
function* testPasteAdjacentHTMLMenu() {
let refSelector = "#paste-area .adjacent .ref";
let adjacentNode = content.document.querySelector(refSelector).parentNode;
let nodeFront = yield getNodeFront(refSelector, inspector);
yield selectNode(nodeFront, inspector);
let markupTagLine = getContainerForNodeFront(nodeFront, inspector).tagLine;
for (let { desc, clipboardData, menuId } of PASTE_ADJACENT_HTML_DATA) {
let menu = inspector.panelDoc.getElementById(menuId);
info(`Testing ${getLabelFor(menu)} for ${clipboardData}`);
clipboard.set(clipboardData);
contextMenuClick(markupTagLine);
let onMutation = inspector.once("markupmutation");
dispatchCommandEvent(menu);
info("Waiting for mutation to occur");
yield onMutation;
}
ok(adjacentNode.innerHTML.trim() === "1<span class=\"ref\">234</span>" +
"<span>5</span>", "The Paste as Last Child / as First Child / Before " +
"/ After worked as expected");
yield undoChange(inspector);
ok(adjacentNode.innerHTML.trim() === "1<span class=\"ref\">234</span>",
"Undo works for paste adjacent HTML");
}
function checkMenuItem(elementId, disabled) {
if (disabled) {
checkDisabled(elementId);
} else {
checkEnabled(elementId);
}
}
function checkEnabled(elementId) {
let elt = inspector.panelDoc.getElementById(elementId);
ok(!elt.hasAttribute("disabled"),
'"' + elt.label + '" context menu option is not disabled');
}
function checkDisabled(elementId) {
let elt = inspector.panelDoc.getElementById(elementId);
ok(elt.hasAttribute("disabled"),
'"' + elt.label + '" context menu option is disabled');
}
function dispatchCommandEvent(node) {
info("Dispatching command event on " + node);
let commandEvent = document.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
node.dispatchEvent(commandEvent);
}
function contextMenuClick(element) {
info("Simulating contextmenu event on " + element);
let evt = element.ownerDocument.createEvent('MouseEvents');
let button = 2; // right click
evt.initMouseEvent('contextmenu', true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, button, null);
element.dispatchEvent(evt);
}
function getLabelFor(elt) {
if (typeof elt === "string")
elt = inspector.panelDoc.querySelector(elt);
let isInPasteSubMenu = elt.matches("#node-menu-paste-extra-submenu *");
return `"${isInPasteSubMenu ? "Paste > " : ""}${elt.label}"`;
}
});

View File

@ -0,0 +1,150 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that different paste items work in the context menu
const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
const PASTE_ADJACENT_HTML_DATA = [
{
desc: "As First Child",
clipboardData: "2",
menuId: "node-menu-pastefirstchild",
},
{
desc: "As Last Child",
clipboardData: "4",
menuId: "node-menu-pastelastchild",
},
{
desc: "Before",
clipboardData: "1",
menuId: "node-menu-pastebefore",
},
{
desc: "After",
clipboardData: "<span>5</span>",
menuId: "node-menu-pasteafter",
},
];
let clipboard = require("sdk/clipboard");
registerCleanupFunction(() => {
clipboard = null;
});
add_task(function* () {
let { inspector } = yield openInspectorForURL(TEST_URL);
yield testPasteOuterHTMLMenu();
yield testPasteInnerHTMLMenu();
yield testPasteAdjacentHTMLMenu();
function* testPasteOuterHTMLMenu() {
info("Testing that 'Paste Outer HTML' menu item works.");
clipboard.set("this was pasted (outerHTML)");
let outerHTMLSelector = "#paste-area h1";
let nodeFront = yield getNodeFront(outerHTMLSelector, inspector);
yield selectNode(nodeFront, inspector);
contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine);
let onNodeReselected = inspector.markup.once("reselectedonremoved");
let menu = inspector.panelDoc.getElementById("node-menu-pasteouterhtml");
dispatchCommandEvent(menu);
info("Waiting for inspector selection to update");
yield onNodeReselected;
ok(content.document.body.outerHTML.contains(clipboard.get()),
"Clipboard content was pasted into the node's outer HTML.");
ok(!getNode(outerHTMLSelector, { expectNoMatch: true }),
"The original node was removed.");
}
function* testPasteInnerHTMLMenu() {
info("Testing that 'Paste Inner HTML' menu item works.");
clipboard.set("this was pasted (innerHTML)");
let innerHTMLSelector = "#paste-area .inner";
let getInnerHTML = () => content.document.querySelector(innerHTMLSelector).innerHTML;
let origInnerHTML = getInnerHTML();
let nodeFront = yield getNodeFront(innerHTMLSelector, inspector);
yield selectNode(nodeFront, inspector);
contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine);
let onMutation = inspector.once("markupmutation");
let menu = inspector.panelDoc.getElementById("node-menu-pasteinnerhtml");
dispatchCommandEvent(menu);
info("Waiting for mutation to occur");
yield onMutation;
ok(getInnerHTML() === clipboard.get(),
"Clipboard content was pasted into the node's inner HTML.");
ok(getNode(innerHTMLSelector), "The original node has been preserved.");
yield undoChange(inspector);
ok(getInnerHTML() === origInnerHTML, "Previous innerHTML has been " +
"restored after undo");
}
function* testPasteAdjacentHTMLMenu() {
let refSelector = "#paste-area .adjacent .ref";
let adjacentNode = content.document.querySelector(refSelector).parentNode;
let nodeFront = yield getNodeFront(refSelector, inspector);
yield selectNode(nodeFront, inspector);
let markupTagLine = getContainerForNodeFront(nodeFront, inspector).tagLine;
for (let { clipboardData, menuId } of PASTE_ADJACENT_HTML_DATA) {
let menu = inspector.panelDoc.getElementById(menuId);
info(`Testing ${getLabelFor(menu)} for ${clipboardData}`);
clipboard.set(clipboardData);
contextMenuClick(markupTagLine);
let onMutation = inspector.once("markupmutation");
dispatchCommandEvent(menu);
info("Waiting for mutation to occur");
yield onMutation;
}
ok(adjacentNode.innerHTML.trim() === "1<span class=\"ref\">234</span>" +
"<span>5</span>", "The Paste as Last Child / as First Child / Before " +
"/ After worked as expected");
yield undoChange(inspector);
ok(adjacentNode.innerHTML.trim() === "1<span class=\"ref\">234</span>",
"Undo works for paste adjacent HTML");
}
function dispatchCommandEvent(node) {
info("Dispatching command event on " + node);
let commandEvent = document.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
node.dispatchEvent(commandEvent);
}
function contextMenuClick(element) {
info("Simulating contextmenu event on " + element);
let evt = element.ownerDocument.createEvent('MouseEvents');
let button = 2; // right click
evt.initMouseEvent('contextmenu', true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, button, null);
element.dispatchEvent(evt);
}
function getLabelFor(elt) {
if (typeof elt === "string")
elt = inspector.panelDoc.querySelector(elt);
let isInPasteSubMenu = elt.matches("#node-menu-paste-extra-submenu *");
return `"${isInPasteSubMenu ? "Paste > " : ""}${elt.label}"`;
}
});

View File

@ -0,0 +1,73 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests for menuitem functionality that doesn't fit into any specific category
const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
add_task(function* () {
let { inspector, toolbox } = yield openInspectorForURL(TEST_URL);
yield testShowDOMProperties();
yield testDeleteNode();
yield testDeleteRootNode();
function* testShowDOMProperties() {
info("Testing 'Show DOM Properties' menu item.");
let showDOMPropertiesNode = inspector.panelDoc.getElementById("node-menu-showdomproperties");
ok(showDOMPropertiesNode, "the popup menu has a show dom properties item");
let consoleOpened = toolbox.once("webconsole-ready");
info("Triggering 'Show DOM Properties' and waiting for inspector open");
dispatchCommandEvent(showDOMPropertiesNode);
yield consoleOpened;
let webconsoleUI = toolbox.getPanel("webconsole").hud.ui;
let messagesAdded = webconsoleUI.once("new-messages");
yield messagesAdded;
info("Checking if 'inspect($0)' was evaluated");
ok(webconsoleUI.jsterm.history[0] === 'inspect($0)');
yield toolbox.toggleSplitConsole();
}
function* testDeleteNode() {
info("Testing 'Delete Node' menu item for normal elements.");
yield selectNode("#delete", inspector);
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
ok(deleteNode, "the popup menu has a delete menu item");
let updated = inspector.once("inspector-updated");
info("Triggering 'Delete Node' and waiting for inspector to update");
dispatchCommandEvent(deleteNode);
yield updated;
ok(!getNode("#delete", { expectNoMatch: true }), "Node deleted");
}
function* testDeleteRootNode() {
info("Testing 'Delete Node' menu item does not delete root node.");
yield selectNode("html", inspector);
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
dispatchCommandEvent(deleteNode);
executeSoon(() => {
ok(content.document.documentElement, "Document element still alive.");
});
}
function dispatchCommandEvent(node) {
info("Dispatching command event on " + node);
let commandEvent = document.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
node.dispatchEvent(commandEvent);
}
});

View File

@ -214,12 +214,14 @@ addMessageListener("Test:ElementFromPoint", function(msg) {
/**
* Get all box-model regions' adjusted boxquads for the given element
* @param {Object} msg The msg.objects part should be the element
* @param {Object} msg The msg.data part should contain the node selector.
* @return {Object} An object with each property being a box-model region, each
* of them being an object with the p1/p2/p3/p4 properties
* of them being an array of objects with the p1/p2/p3/p4 properties.
*/
addMessageListener("Test:GetAllAdjustedQuads", function(msg) {
let {node} = msg.objects;
let {selector} = msg.data;
let node = superQuerySelector(selector);
let regions = {};
let helper = new LayoutHelpers(content);
@ -250,7 +252,7 @@ addMessageListener("Test:SynthesizeMouse", function(msg) {
let {node} = msg.objects;
if (!node && selector) {
node = content.document.querySelector(selector);
node = superQuerySelector(selector);
}
if (center) {
@ -293,4 +295,30 @@ addMessageListener("Test:HasPseudoClassLock", function(msg) {
sendAsyncMessage("Test:HasPseudoClassLock", DOMUtils.hasPseudoClassLock(node, pseudo));
});
/**
* Like document.querySelector but can go into iframes too.
* ".container iframe || .sub-container div" will first try to find the node
* matched by ".container iframe" in the root document, then try to get the
* content document inside it, and then try to match ".sub-container div" inside
* this document.
* Any selector coming before the || separator *MUST* match a frame node.
* @param {String} superSelector.
* @return {DOMNode} The node, or null if not found.
*/
function superQuerySelector(superSelector, root=content.document) {
let frameIndex = superSelector.indexOf("||");
if (frameIndex === -1) {
return root.querySelector(superSelector);
} else {
let rootSelector = superSelector.substring(0, frameIndex).trim();
let childSelector = superSelector.substring(frameIndex+2).trim();
root = root.querySelector(rootSelector);
if (!root || !root.contentWindow) {
return null;
}
return superQuerySelector(childSelector, root.contentWindow.document);
}
}
let dumpn = msg => dump(msg + "\n");

View File

@ -1,6 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
div {
position:absolute;

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html {
height: 100%;
background: #eee;
}
body {
margin: 0 auto;
padding: 1em;
box-sizing: border-box;
width: 500px;
height: 100%;
background: white;
font-family: Arial;
font-size: 15px;
line-height: 40px;
}
p span {
padding: 5px 0;
margin: 0 5px;
border: 5px solid #eee;
}
</style>
</head>
<body>
<h1>Lorem Ipsum</h1>
<h2>Lorem ipsum <em>dolor sit amet</em></h2>
<p><span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, nisl eget semper maximus, dui tellus tempor leo, at pharetra eros tortor sed odio. Nullam sagittis ex nec mi sagittis pulvinar. Pellentesque dapibus feugiat fermentum. Curabitur lacinia quis enim et tristique. Aliquam in semper massa. In ac vulputate nunc, at rutrum neque. Fusce condimentum, tellus quis placerat imperdiet, dolor tortor mattis erat, nec luctus magna diam pharetra mauris.</span></p>
<div dir="rtl">
<span><span></span>some ltr text in an rtl container</span>
</div>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<head>
<title>Inspector Tree Menu Test</title>
</head>
<body>
<div>
<h1>Inspector Tree Menu Test</h1>
<p>This is some example text</p>
</div>
</body>

View File

@ -16,6 +16,7 @@
<p data-id="copy">Paragraph for testing copy</p>
<p id="sensitivity">Paragraph for sensitivity</p>
<p id="delete">This has to be deleted</p>
<img id="copyimage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==" />
</div>
</body>
</html>

View File

@ -306,14 +306,22 @@ let getBoxModelStatus = Task.async(function*(toolbox) {
return ret;
});
let getGuideStatus = Task.async(function*(location, toolbox) {
/**
* Get data about one of the toolbox box-model highlighter's guides.
* @param {String} location One of top, right, bottom, left.
* @param {Toolbox} toolbox The toolbox instance, used to retrieve the highlighter.
* @return {Object} The returned object has the following form:
* - visible {Boolean} Whether that guide is visible.
* - x1/y1/x2/y2 {String} The <line>'s coordinates.
*/
let getGuideStatus = Task.async(function*(location, {highlighter}) {
let id = "box-model-guide-" + location;
let hidden = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "hidden");
let x1 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "x1");
let y1 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "y1");
let x2 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "x2");
let y2 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "y2");
let hidden = yield getHighlighterNodeAttribute(highlighter, id, "hidden");
let x1 = yield getHighlighterNodeAttribute(highlighter, id, "x1");
let y1 = yield getHighlighterNodeAttribute(highlighter, id, "y1");
let x2 = yield getHighlighterNodeAttribute(highlighter, id, "x2");
let y2 = yield getHighlighterNodeAttribute(highlighter, id, "y2");
return {
visible: !hidden,
@ -325,36 +333,68 @@ let getGuideStatus = Task.async(function*(location, toolbox) {
});
/**
* Get the coordinate (points attribute) from one of the polygon elements in the
* box model highlighter.
* Get the coordinates of the rectangle that is defined by the 4 guides displayed
* in the toolbox box-model highlighter.
* @param {Toolbox} toolbox The toolbox instance, used to retrieve the highlighter.
* @return {Object} Null if at least one guide is hidden. Otherwise an object
* with p1, p2, p3, p4 properties being {x, y} objects.
*/
let getGuidesRectangle = Task.async(function*(toolbox) {
let tGuide = yield getGuideStatus("top", toolbox);
let rGuide = yield getGuideStatus("right", toolbox);
let bGuide = yield getGuideStatus("bottom", toolbox);
let lGuide = yield getGuideStatus("left", toolbox);
if (!tGuide.visible || !rGuide.visible || !bGuide.visible || !lGuide.visible) {
return null;
}
return {
p1: {x: lGuide.x1, y: tGuide.y1},
p2: {x: rGuide.x1, y: tGuide. y1},
p3: {x: rGuide.x1, y: bGuide.y1},
p4: {x: lGuide.x1, y: bGuide.y1}
};
});
/**
* Get the coordinate (points defined by the d attribute) from one of the path
* elements in the box model highlighter.
*/
let getPointsForRegion = Task.async(function*(region, toolbox) {
let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-" + region, "points");
points = points.split(/[, ]/);
let d = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-" + region, "d");
let polygons = d.match(/M[^M]+/g);
if (!polygons) {
return null;
}
let points = polygons[0].trim().split(" ").map(i => {
return i.replace(/M|L/, "").split(",")
});
return {
p1: {
x: parseFloat(points[0]),
y: parseFloat(points[1])
x: parseFloat(points[0][0]),
y: parseFloat(points[0][1])
},
p2: {
x: parseFloat(points[2]),
y: parseFloat(points[3])
x: parseFloat(points[1][0]),
y: parseFloat(points[1][1])
},
p3: {
x: parseFloat(points[4]),
y: parseFloat(points[5])
x: parseFloat(points[2][0]),
y: parseFloat(points[2][1])
},
p4: {
x: parseFloat(points[6]),
y: parseFloat(points[7])
x: parseFloat(points[3][0]),
y: parseFloat(points[3][1])
}
};
});
/**
* Is a given region polygon element of the box-model highlighter currently
* Is a given region path element of the box-model highlighter currently
* hidden?
*/
let isRegionHidden = Task.async(function*(region, toolbox) {
@ -387,27 +427,24 @@ let getHighlitNode = Task.async(function*(toolbox) {
/**
* Assert that the box-model highlighter's current position corresponds to the
* given node boxquads.
* @param {DOMNode|CPOW} node The node to get the boxQuads from
* @param {String} selector The selector for the node to get the boxQuads from
* @param {String} prefix An optional prefix for logging information to the
* console.
*/
let isNodeCorrectlyHighlighted = Task.async(function*(node, toolbox, prefix="") {
prefix += (prefix ? " " : "") + node.nodeName;
prefix += (node.id ? "#" + node.id : "");
prefix += (node.classList.length ? "." + [...node.classList].join(".") : "");
prefix += " ";
let isNodeCorrectlyHighlighted = Task.async(function*(selector, toolbox, prefix="") {
let boxModel = yield getBoxModelStatus(toolbox);
let {data: regions} = yield executeInContent("Test:GetAllAdjustedQuads", null,
{node});
let {data: regions} = yield executeInContent("Test:GetAllAdjustedQuads",
{selector});
for (let boxType of ["content", "padding", "border", "margin"]) {
let quads = regions[boxType];
let [quad] = regions[boxType];
for (let point in boxModel[boxType].points) {
is(boxModel[boxType].points[point].x, quads[point].x,
prefix + boxType + " point " + point + " x coordinate is correct");
is(boxModel[boxType].points[point].y, quads[point].y,
prefix + boxType + " point " + point + " y coordinate is correct");
is(boxModel[boxType].points[point].x, quad[point].x,
"Node " + selector + " " + boxType + " point " + point +
" x coordinate is correct");
is(boxModel[boxType].points[point].y, quad[point].y,
"Node " + selector + " " + boxType + " point " + point +
" y coordinate is correct");
}
}
});
@ -552,6 +589,37 @@ let getHighlighterNodeAttribute = Task.async(function*(highlighter, nodeID, name
return value;
});
/**
* Get the "d" attribute value for one of the box-model highlighter's region
* <path> elements, and parse it to a list of points.
* @param {String} region The box model region name.
* @param {Front} highlighter The front of the highlighter.
* @return {Object} The object returned has the following form:
* - d {String} the d attribute value
* - points {Array} an array of all the polygons defined by the path. Each box
* is itself an Array of points, themselves being [x,y] coordinates arrays.
*/
let getHighlighterRegionPath = Task.async(function*(region, highlighter) {
let d = yield getHighlighterNodeAttribute(highlighter, "box-model-" + region, "d");
if (!d) {
return {d: null};
}
let polygons = d.match(/M[^M]+/g);
if (!polygons) {
return {d};
}
let points = [];
for (let polygon of polygons) {
points.push(polygon.trim().split(" ").map(i => {
return i.replace(/M|L/, "").split(",")
}));
}
return {d, points};
});
/**
* Get the textContent value of one of the highlighter's node.
* @param {Front} highlighter The front of the highlighter.

View File

@ -59,4 +59,7 @@
<div id="sub-scrolled-node">
<div id="inner-scrolled-node"></div>
</div>
</div>
</div>
<span id="inline">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porttitor luctus sem id scelerisque. Cras quis velit sed risus euismod lacinia. Donec viverra enim eu ligula efficitur, quis vulputate metus cursus. Duis sed interdum risus. Ut blandit velit vitae faucibus efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br/ >
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed vitae dolor metus. Aliquam sed velit sit amet libero vestibulum aliquam vel a lorem. Integer eget ex eget justo auctor ullamcorper.<br/ >
Praesent tristique maximus lacus, nec ultricies neque ultrices non. Phasellus vel lobortis justo. </span>

View File

@ -21,13 +21,14 @@ function test() {
info("Running tests");
returnsTheRightDataStructure(doc, helper);
returnsNullForMissingNode(doc, helper);
returnsNullForHiddenNodes(doc, helper);
isEmptyForMissingNode(doc, helper);
isEmptyForHiddenNodes(doc, helper);
defaultsToBorderBoxIfNoneProvided(doc, helper);
returnsLikeGetBoxQuadsInSimpleCase(doc, helper);
takesIframesOffsetsIntoAccount(doc, helper);
takesScrollingIntoAccount(doc, helper);
takesZoomIntoAccount(doc, helper);
returnsMultipleItemsForWrappingInlineElements(doc, helper);
gBrowser.removeCurrentTab();
finish();
@ -38,7 +39,7 @@ function returnsTheRightDataStructure(doc, helper) {
info("Checks that the returned data contains bounds and 4 points");
let node = doc.querySelector("body");
let res = helper.getAdjustedQuads(node, "content");
let [res] = helper.getAdjustedQuads(node, "content");
ok("bounds" in res, "The returned data has a bounds property");
ok("p1" in res, "The returned data has a p1 property");
@ -58,24 +59,24 @@ function returnsTheRightDataStructure(doc, helper) {
}
}
function returnsNullForMissingNode(doc, helper) {
function isEmptyForMissingNode(doc, helper) {
info("Checks that null is returned for invalid nodes");
for (let input of [null, undefined, "", 0]) {
ok(helper.getAdjustedQuads(input) === null, "null is returned for input " +
input);
is(helper.getAdjustedQuads(input).length, 0, "A 0-length array is returned" +
"for input " + input);
}
}
function returnsNullForHiddenNodes(doc, helper) {
function isEmptyForHiddenNodes(doc, helper) {
info("Checks that null is returned for nodes that aren't rendered");
let style = doc.querySelector("#styles");
ok(helper.getAdjustedQuads(style) === null,
is(helper.getAdjustedQuads(style).length, 0,
"null is returned for a <style> node");
let hidden = doc.querySelector("#hidden-node");
ok(helper.getAdjustedQuads(hidden) === null,
is(helper.getAdjustedQuads(hidden).length, 0,
"null is returned for a hidden node");
}
@ -83,8 +84,8 @@ function defaultsToBorderBoxIfNoneProvided(doc, helper) {
info("Checks that if no boxtype is passed, then border is the default one");
let node = doc.querySelector("#simple-node-with-margin-padding-border");
let withBoxType = helper.getAdjustedQuads(node, "border");
let withoutBoxType = helper.getAdjustedQuads(node);
let [withBoxType] = helper.getAdjustedQuads(node, "border");
let [withoutBoxType] = helper.getAdjustedQuads(node);
for (let boundProp of
["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
@ -111,7 +112,7 @@ function returnsLikeGetBoxQuadsInSimpleCase(doc, helper) {
let expected = node.getBoxQuads({
box: region
})[0];
let actual = helper.getAdjustedQuads(node, region);
let [actual] = helper.getAdjustedQuads(node, region);
for (let boundProp of
["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
@ -138,7 +139,7 @@ function takesIframesOffsetsIntoAccount(doc, helper) {
let subIframe = rootIframe.contentDocument.querySelector("iframe");
let innerNode = subIframe.contentDocument.querySelector("#inner-node");
let quad = helper.getAdjustedQuads(innerNode, "content");
let [quad] = helper.getAdjustedQuads(innerNode, "content");
//rootIframe margin + subIframe margin + node margin + node border + node padding
let p1x = 10 + 10 + 10 + 10 + 10;
@ -163,7 +164,7 @@ function takesScrollingIntoAccount(doc, helper) {
subScrolledNode.scrollTop = 200;
let innerNode = doc.querySelector("#inner-scrolled-node");
let quad = helper.getAdjustedQuads(innerNode, "content");
let [quad] = helper.getAdjustedQuads(innerNode, "content");
is(quad.p1.x, 0, "p1.x of the scrolled node is correct after scrolling down");
is(quad.p1.y, -300, "p1.y of the scrolled node is correct after scrolling down");
@ -171,7 +172,7 @@ function takesScrollingIntoAccount(doc, helper) {
scrolledNode.scrollTop = 0;
subScrolledNode.scrollTop = 0;
quad = helper.getAdjustedQuads(innerNode, "content");
[quad] = helper.getAdjustedQuads(innerNode, "content");
is(quad.p1.x, 0, "p1.x of the scrolled node is correct after scrolling up");
is(quad.p1.y, 0, "p1.y of the scrolled node is correct after scrolling up");
}
@ -184,11 +185,11 @@ function takesZoomIntoAccount(doc, helper) {
// bigger quad and zooming out produces a smaller quad
let node = doc.querySelector("#simple-node-with-margin-padding-border");
let defaultQuad = helper.getAdjustedQuads(node);
let [defaultQuad] = helper.getAdjustedQuads(node);
info("Zoom in");
window.FullZoom.enlarge();
let zoomedInQuad = helper.getAdjustedQuads(node);
let [zoomedInQuad] = helper.getAdjustedQuads(node);
ok(zoomedInQuad.bounds.width > defaultQuad.bounds.width,
"The zoomed in quad is bigger than the default one");
@ -198,7 +199,7 @@ function takesZoomIntoAccount(doc, helper) {
info("Zoom out");
window.FullZoom.reset();
window.FullZoom.reduce();
let zoomedOutQuad = helper.getAdjustedQuads(node);
let [zoomedOutQuad] = helper.getAdjustedQuads(node);
ok(zoomedOutQuad.bounds.width < defaultQuad.bounds.width,
"The zoomed out quad is smaller than the default one");
@ -207,3 +208,15 @@ function takesZoomIntoAccount(doc, helper) {
window.FullZoom.reset();
}
function returnsMultipleItemsForWrappingInlineElements(doc, helper) {
info("Checks that several quads are returned for inline elements that span line-breaks");
let node = doc.querySelector("#inline");
let quads = helper.getAdjustedQuads(node, "content");
// At least 3 because of the 2 <br />, maybe more depending on the window size.
ok(quads.length >= 3, "Multiple quads were returned");
is(quads.length, node.getBoxQuads().length,
"The same number of boxes as getBoxQuads was returned");
}

View File

@ -293,6 +293,20 @@ function test() {
test: fragment => {
is(countAll(fragment), 0);
}
},
{
name: "background",
value: "rgb(255, var(--g-value), 192)",
test: fragment => {
is(fragment.textContent, "rgb(255, var(--g-value), 192)");
}
},
{
name: "background",
value: "rgb(255, var(--g-value, 0), 192)",
test: fragment => {
is(fragment.textContent, "rgb(255, var(--g-value, 0), 192)");
}
}
];

View File

@ -93,7 +93,7 @@
<style name="Widget.MenuItemSecondaryActionBar">
<item name="android:padding">8dip</item>
<item name="android:background">@drawable/action_bar_button</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:scaleType">centerInside</item>
</style>
<style name="Widget.MenuItemActionView">

View File

@ -6442,7 +6442,10 @@ var ViewportHandler = {
* metadata is available for that document.
*/
getMetadataForDocument: function getMetadataForDocument(aDocument) {
let metadata = this._metadata.get(aDocument, new ViewportMetadata());
let metadata = this._metadata.get(aDocument);
if (metadata === undefined) {
metadata = new ViewportMetadata();
}
return metadata;
},

View File

@ -13,6 +13,12 @@
#include "nsString.h"
#include "nsTArray.h"
#define VARIANT_BASE_IID \
{ /* 78888042-0fa3-4f7a-8b19-7996f99bf1aa */ \
0x78888042, 0x0fa3, 0x4f7a, \
{ 0x8b, 0x19, 0x79, 0x96, 0xf9, 0x9b, 0xf1, 0xaa } \
}
/**
* This class is used by the storage module whenever an nsIVariant needs to be
* returned. We provide traits for the basic sqlite types to make use easier.
@ -36,11 +42,15 @@ class Variant_base : public nsIVariant
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIVARIANT
NS_DECLARE_STATIC_IID_ACCESSOR(VARIANT_BASE_IID)
protected:
virtual ~Variant_base() { }
};
NS_DEFINE_STATIC_IID_ACCESSOR(Variant_base,
VARIANT_BASE_IID)
////////////////////////////////////////////////////////////////////////////////
//// Traits
@ -366,7 +376,7 @@ public:
//// Template Implementation
template <typename DataType, bool Adopting=false>
class Variant : public Variant_base
class Variant MOZ_FINAL : public Variant_base
{
~Variant()
{

View File

@ -227,7 +227,6 @@ AsyncStatement::getParams()
AsyncStatement::~AsyncStatement()
{
destructorAsyncFinalize();
cleanupJSHelpers();
// If we are getting destroyed on the wrong thread, proxy the connection
// release to the right thread. I'm not sure why we do this.
@ -243,23 +242,6 @@ AsyncStatement::~AsyncStatement()
}
}
void
AsyncStatement::cleanupJSHelpers()
{
// We are considered dead at this point, so any wrappers for row or params
// need to lose their reference to us.
if (mStatementParamsHolder) {
nsCOMPtr<nsIXPConnectWrappedNative> wrapper =
do_QueryInterface(mStatementParamsHolder);
nsCOMPtr<mozIStorageStatementParams> iParams =
do_QueryWrappedNative(wrapper);
AsyncStatementParams *params =
static_cast<AsyncStatementParams *>(iParams.get());
params->mStatement = nullptr;
mStatementParamsHolder = nullptr;
}
}
////////////////////////////////////////////////////////////////////////////////
//// nsISupports
@ -369,7 +351,9 @@ AsyncStatement::Finalize()
mSQLString.get()));
asyncFinalize();
cleanupJSHelpers();
// Release the params holder, so it can release the reference to us.
mStatementParamsHolder = nullptr;
return NS_OK;
}

View File

@ -67,13 +67,6 @@ public:
private:
~AsyncStatement();
/**
* Clean up the references JS helpers hold to us. For cycle-avoidance reasons
* they do not hold reference-counted references to us, so it is important
* we do this.
*/
void cleanupJSHelpers();
/**
* @return a pointer to the BindingParams object to use with our Bind*
* method.
@ -95,7 +88,7 @@ private:
/**
* Caches the JS 'params' helper for this statement.
*/
nsCOMPtr<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
/**
* Have we been explicitly finalized by the user?

View File

@ -30,6 +30,7 @@ AsyncStatementJSHelper::getParams(AsyncStatement *aStatement,
JSObject *aScopeObj,
jsval *_params)
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv;
#ifdef DEBUG
@ -45,15 +46,20 @@ AsyncStatementJSHelper::getParams(AsyncStatement *aStatement,
NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
JS::RootedObject scope(aCtx, aScopeObj);
nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
rv = xpc->WrapNative(
aCtx,
::JS_GetGlobalForObject(aCtx, scope),
params,
NS_GET_IID(mozIStorageStatementParams),
getter_AddRefs(aStatement->mStatementParamsHolder)
getter_AddRefs(holder)
);
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<AsyncStatementParamsHolder> paramsHolder =
new AsyncStatementParamsHolder(holder);
aStatement->mStatementParamsHolder =
new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(paramsHolder);
}
JS::Rooted<JSObject*> obj(aCtx);
@ -112,5 +118,34 @@ AsyncStatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper,
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// AsyncStatementParamsHolder
NS_IMPL_ISUPPORTS(AsyncStatementParamsHolder, nsIXPConnectJSObjectHolder);
JSObject*
AsyncStatementParamsHolder::GetJSObject()
{
return mHolder->GetJSObject();
}
AsyncStatementParamsHolder::AsyncStatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder)
: mHolder(aHolder)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mHolder);
}
AsyncStatementParamsHolder::~AsyncStatementParamsHolder()
{
MOZ_ASSERT(NS_IsMainThread());
// We are considered dead at this point, so any wrappers for row or params
// need to lose their reference to the statement.
nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder);
nsCOMPtr<mozIStorageStatementParams> iObj = do_QueryWrappedNative(wrapper);
AsyncStatementParams *obj = static_cast<AsyncStatementParams *>(iObj.get());
obj->mStatement = nullptr;
}
} // namespace storage
} // namespace mozilla

View File

@ -8,6 +8,7 @@
#define mozilla_storage_mozStorageAsyncStatementJSHelper_h_
#include "nsIXPCScriptable.h"
#include "nsIXPConnect.h"
class AsyncStatement;
@ -29,6 +30,24 @@ private:
nsresult getParams(AsyncStatement *, JSContext *, JSObject *, JS::Value *);
};
/**
* Wrapper used to clean up the references JS helpers hold to the statement.
* For cycle-avoidance reasons they do not hold reference-counted references,
* so it is important we do this.
*/
class AsyncStatementParamsHolder MOZ_FINAL : public nsIXPConnectJSObjectHolder
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIXPCONNECTJSOBJECTHOLDER
explicit AsyncStatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder);
private:
virtual ~AsyncStatementParamsHolder();
nsCOMPtr<nsIXPConnectJSObjectHolder> mHolder;
};
} // namespace storage
} // namespace mozilla

View File

@ -16,8 +16,6 @@ class mozIStorageAsyncStatement;
namespace mozilla {
namespace storage {
class AsyncStatement;
/*
* Since mozIStorageStatementParams is just a tagging interface we do not have
* an async variant.
@ -38,7 +36,7 @@ protected:
AsyncStatement *mStatement;
friend class AsyncStatement;
friend class AsyncStatementParamsHolder;
};
} // namespace storage

View File

@ -215,7 +215,7 @@ already_AddRefed<mozIStorageError>
BindingParams::bind(sqlite3_stmt *aStatement)
{
// Iterate through all of our stored data, and bind it.
for (int32_t i = 0; i < mParameters.Count(); i++) {
for (size_t i = 0; i < mParameters.Length(); i++) {
int rc = variantToSQLiteT(BindingColumnData(aStatement, i), mParameters[i]);
if (rc != SQLITE_OK) {
// We had an error while trying to bind. Now we need to create an error
@ -274,7 +274,11 @@ AsyncBindingParams::BindByName(const nsACString &aName,
{
NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
mNamedParameters.Put(aName, aValue);
nsRefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue);
if (!variant)
return NS_ERROR_UNEXPECTED;
mNamedParameters.Put(aName, variant);
return NS_OK;
}
@ -378,8 +382,17 @@ BindingParams::BindByIndex(uint32_t aIndex,
ENSURE_INDEX_VALUE(aIndex, mParamCount);
// Store the variant for later use.
NS_ENSURE_TRUE(mParameters.ReplaceObjectAt(aValue, aIndex),
NS_ERROR_OUT_OF_MEMORY);
nsRefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue);
if (!variant)
return NS_ERROR_UNEXPECTED;
if (mParameters.Length() <= aIndex) {
(void)mParameters.SetLength(aIndex);
(void)mParameters.AppendElement(variant);
}
else {
NS_ENSURE_TRUE(mParameters.ReplaceElementAt(aIndex, variant),
NS_ERROR_OUT_OF_MEMORY);
}
return NS_OK;
}
@ -391,9 +404,17 @@ AsyncBindingParams::BindByIndex(uint32_t aIndex,
// In the asynchronous case we do not know how many parameters there are to
// bind to, so we cannot check the validity of aIndex.
// Store the variant for later use.
NS_ENSURE_TRUE(mParameters.ReplaceObjectAt(aValue, aIndex),
NS_ERROR_OUT_OF_MEMORY);
nsRefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue);
if (!variant)
return NS_ERROR_UNEXPECTED;
if (mParameters.Length() <= aIndex) {
mParameters.SetLength(aIndex);
mParameters.AppendElement(variant);
}
else {
NS_ENSURE_TRUE(mParameters.ReplaceElementAt(aIndex, variant),
NS_ERROR_OUT_OF_MEMORY);
}
return NS_OK;
}

View File

@ -14,6 +14,7 @@
#include "mozStorageBindingParamsArray.h"
#include "mozStorageStatement.h"
#include "mozStorageAsyncStatement.h"
#include "Variant.h"
#include "mozIStorageBindingParams.h"
#include "IStorageBindingParamsInternal.h"
@ -57,7 +58,9 @@ protected:
virtual ~BindingParams() {}
explicit BindingParams(mozIStorageBindingParamsArray *aOwningArray);
nsCOMArray<nsIVariant> mParameters;
// Note that this is managed as a sparse array, so particular caution should
// be used for out-of-bounds usage.
nsTArray<nsRefPtr<Variant_base> > mParameters;
bool mLocked;
private:

View File

@ -154,6 +154,95 @@ convertJSValToVariant(
return nullptr;
}
Variant_base *
convertVariantToStorageVariant(nsIVariant* aVariant)
{
nsRefPtr<Variant_base> variant = do_QueryObject(aVariant);
if (variant) {
// JS helpers already convert the JS representation to a Storage Variant,
// in such a case there's nothing left to do here, so just pass-through.
return variant;
}
if (!aVariant)
return new NullVariant();
uint16_t dataType;
nsresult rv = aVariant->GetDataType(&dataType);
NS_ENSURE_SUCCESS(rv, nullptr);
switch (dataType) {
case nsIDataType::VTYPE_BOOL:
case nsIDataType::VTYPE_INT8:
case nsIDataType::VTYPE_INT16:
case nsIDataType::VTYPE_INT32:
case nsIDataType::VTYPE_UINT8:
case nsIDataType::VTYPE_UINT16:
case nsIDataType::VTYPE_UINT32:
case nsIDataType::VTYPE_INT64:
case nsIDataType::VTYPE_UINT64: {
int64_t v;
rv = aVariant->GetAsInt64(&v);
NS_ENSURE_SUCCESS(rv, nullptr);
return new IntegerVariant(v);
}
case nsIDataType::VTYPE_FLOAT:
case nsIDataType::VTYPE_DOUBLE: {
double v;
rv = aVariant->GetAsDouble(&v);
NS_ENSURE_SUCCESS(rv, nullptr);
return new FloatVariant(v);
}
case nsIDataType::VTYPE_CHAR:
case nsIDataType::VTYPE_CHAR_STR:
case nsIDataType::VTYPE_STRING_SIZE_IS:
case nsIDataType::VTYPE_UTF8STRING:
case nsIDataType::VTYPE_CSTRING: {
nsCString v;
rv = aVariant->GetAsAUTF8String(v);
NS_ENSURE_SUCCESS(rv, nullptr);
return new UTF8TextVariant(v);
}
case nsIDataType::VTYPE_WCHAR:
case nsIDataType::VTYPE_DOMSTRING:
case nsIDataType::VTYPE_WCHAR_STR:
case nsIDataType::VTYPE_WSTRING_SIZE_IS:
case nsIDataType::VTYPE_ASTRING: {
nsString v;
rv = aVariant->GetAsAString(v);
NS_ENSURE_SUCCESS(rv, nullptr);
return new TextVariant(v);
}
case nsIDataType::VTYPE_ARRAY: {
uint16_t type;
nsIID iid;
uint32_t len;
void *rawArray;
// Note this copies the array data.
rv = aVariant->GetAsArray(&type, &iid, &len, &rawArray);
NS_ENSURE_SUCCESS(rv, nullptr);
if (type == nsIDataType::VTYPE_UINT8) {
std::pair<uint8_t *, int> v(static_cast<uint8_t *>(rawArray), len);
// Take ownership of the data avoiding a further copy.
return new AdoptedBlobVariant(v);
}
}
case nsIDataType::VTYPE_EMPTY:
case nsIDataType::VTYPE_EMPTY_ARRAY:
case nsIDataType::VTYPE_VOID:
return new NullVariant();
case nsIDataType::VTYPE_ID:
case nsIDataType::VTYPE_INTERFACE:
case nsIDataType::VTYPE_INTERFACE_IS:
default:
NS_WARNING("Unsupported variant type");
return nullptr;
}
return nullptr;
}
namespace {
class CallbackEvent : public nsRunnable
{

View File

@ -16,6 +16,7 @@
#include "nsError.h"
#include "nsAutoPtr.h"
#include "js/TypeDecls.h"
#include "Variant.h"
class mozIStorageCompletionCallback;
class mozIStorageBaseStatement;
@ -68,6 +69,16 @@ void checkAndLogStatementPerformance(sqlite3_stmt *aStatement);
*/
nsIVariant *convertJSValToVariant(JSContext *aCtx, JS::Value aValue);
/**
* Convert a provided nsIVariant implementation to our own thread-safe
* refcounting implementation, if needed.
*
* @param aValue
* The original nsIVariant to be converted.
* @return a thread-safe refcounting nsIVariant implementation.
*/
Variant_base *convertVariantToStorageVariant(nsIVariant *aVariant);
/**
* Obtains an event that will notify a completion callback about completion.
*

View File

@ -431,27 +431,9 @@ Statement::internalFinalize(bool aDestructing)
asyncFinalize();
}
// We are considered dead at this point, so any wrappers for row or params
// need to lose their reference to us.
if (mStatementParamsHolder) {
nsCOMPtr<nsIXPConnectWrappedNative> wrapper =
do_QueryInterface(mStatementParamsHolder);
nsCOMPtr<mozIStorageStatementParams> iParams =
do_QueryWrappedNative(wrapper);
StatementParams *params = static_cast<StatementParams *>(iParams.get());
params->mStatement = nullptr;
mStatementParamsHolder = nullptr;
}
if (mStatementRowHolder) {
nsCOMPtr<nsIXPConnectWrappedNative> wrapper =
do_QueryInterface(mStatementRowHolder);
nsCOMPtr<mozIStorageStatementRow> iRow =
do_QueryWrappedNative(wrapper);
StatementRow *row = static_cast<StatementRow *>(iRow.get());
row->mStatement = nullptr;
mStatementRowHolder = nullptr;
}
// Release the holders, so they can release the reference to us.
mStatementParamsHolder = nullptr;
mStatementRowHolder = nullptr;
return convertResultCode(srv);
}

View File

@ -96,8 +96,8 @@ private:
* The following two members are only used with the JS helper. They cache
* the row and params objects.
*/
nsCOMPtr<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
nsCOMPtr<nsIXPConnectJSObjectHolder> mStatementRowHolder;
nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementRowHolder;
/**
* Internal version of finalize that allows us to tell it if it is being
@ -109,7 +109,7 @@ private:
*/
nsresult internalFinalize(bool aDestructing);
friend class StatementJSHelper;
friend class StatementJSHelper;
};
} // storage

View File

@ -84,6 +84,7 @@ StatementJSHelper::getRow(Statement *aStatement,
JSObject *aScopeObj,
jsval *_row)
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv;
#ifdef DEBUG
@ -98,15 +99,19 @@ StatementJSHelper::getRow(Statement *aStatement,
nsCOMPtr<mozIStorageStatementRow> row(new StatementRow(aStatement));
NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
rv = xpc->WrapNative(
aCtx,
::JS_GetGlobalForObject(aCtx, scope),
row,
NS_GET_IID(mozIStorageStatementRow),
getter_AddRefs(aStatement->mStatementRowHolder)
getter_AddRefs(holder)
);
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<StatementRowHolder> rowHolder = new StatementRowHolder(holder);
aStatement->mStatementRowHolder =
new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(rowHolder);
}
JS::Rooted<JSObject*> obj(aCtx);
@ -123,6 +128,7 @@ StatementJSHelper::getParams(Statement *aStatement,
JSObject *aScopeObj,
jsval *_params)
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv;
#ifdef DEBUG
@ -138,15 +144,20 @@ StatementJSHelper::getParams(Statement *aStatement,
new StatementParams(aStatement);
NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
rv = xpc->WrapNative(
aCtx,
::JS_GetGlobalForObject(aCtx, scope),
params,
NS_GET_IID(mozIStorageStatementParams),
getter_AddRefs(aStatement->mStatementParamsHolder)
getter_AddRefs(holder)
);
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<StatementParamsHolder> paramsHolder =
new StatementParamsHolder(holder);
aStatement->mStatementParamsHolder =
new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(paramsHolder);
}
JS::Rooted<JSObject*> obj(aCtx);
@ -230,5 +241,45 @@ StatementJSHelper::Resolve(nsIXPConnectWrappedNative *aWrapper,
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// StatementJSObjectHolder
NS_IMPL_ISUPPORTS(StatementJSObjectHolder, nsIXPConnectJSObjectHolder);
JSObject*
StatementJSObjectHolder::GetJSObject()
{
return mHolder->GetJSObject();
}
StatementJSObjectHolder::StatementJSObjectHolder(nsIXPConnectJSObjectHolder* aHolder)
: mHolder(aHolder)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mHolder);
}
StatementParamsHolder::~StatementParamsHolder()
{
MOZ_ASSERT(NS_IsMainThread());
// We are considered dead at this point, so any wrappers for row or params
// need to lose their reference to the statement.
nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder);
nsCOMPtr<mozIStorageStatementParams> iObj = do_QueryWrappedNative(wrapper);
StatementParams *obj = static_cast<StatementParams *>(iObj.get());
obj->mStatement = nullptr;
}
StatementRowHolder::~StatementRowHolder()
{
MOZ_ASSERT(NS_IsMainThread());
// We are considered dead at this point, so any wrappers for row or params
// need to lose their reference to the statement.
nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder);
nsCOMPtr<mozIStorageStatementRow> iObj = do_QueryWrappedNative(wrapper);
StatementRow *obj = static_cast<StatementRow *>(iObj.get());
obj->mStatement = nullptr;
}
} // namespace storage
} // namespace mozilla

View File

@ -25,6 +25,44 @@ private:
nsresult getParams(Statement *, JSContext *, JSObject *, JS::Value *);
};
/**
* Wrappers used to clean up the references JS helpers hold to the statement.
* For cycle-avoidance reasons they do not hold reference-counted references,
* so it is important we do this.
*/
class StatementJSObjectHolder : public nsIXPConnectJSObjectHolder
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIXPCONNECTJSOBJECTHOLDER
explicit StatementJSObjectHolder(nsIXPConnectJSObjectHolder* aHolder);
protected:
virtual ~StatementJSObjectHolder() {};
nsCOMPtr<nsIXPConnectJSObjectHolder> mHolder;
};
class StatementParamsHolder MOZ_FINAL: public StatementJSObjectHolder {
public:
explicit StatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder)
: StatementJSObjectHolder(aHolder) {
}
private:
virtual ~StatementParamsHolder();
};
class StatementRowHolder MOZ_FINAL: public StatementJSObjectHolder {
public:
explicit StatementRowHolder(nsIXPConnectJSObjectHolder* aHolder)
: StatementJSObjectHolder(aHolder) {
}
private:
virtual ~StatementRowHolder();
};
} // namespace storage
} // namespace mozilla

View File

@ -16,8 +16,6 @@ class mozIStorageStatement;
namespace mozilla {
namespace storage {
class Statement;
class StatementParams MOZ_FINAL : public mozIStorageStatementParams
, public nsIXPCScriptable
{
@ -35,7 +33,8 @@ protected:
mozIStorageStatement *mStatement;
uint32_t mParamCount;
friend class Statement;
friend class StatementParamsHolder;
friend class StatementRowHolder;
};
} // namespace storage

View File

@ -14,8 +14,6 @@
namespace mozilla {
namespace storage {
class Statement;
class StatementRow MOZ_FINAL : public mozIStorageStatementRow
, public nsIXPCScriptable
{
@ -31,7 +29,7 @@ protected:
Statement *mStatement;
friend class Statement;
friend class StatementRowHolder;
};
} // namespace storage

View File

@ -715,12 +715,7 @@ function test_bind_bogus_type_by_index()
let array = stmt.newBindingParamsArray();
let bp = array.newBindingParams();
// We get an error after calling executeAsync, not when we bind.
bp.bindByIndex(0, run_test);
array.addParams(bp);
stmt.bindParameters(array);
execAsync(stmt, {error: Ci.mozIStorageError.MISMATCH});
Assert.throws(() => bp.bindByIndex(0, run_test), /NS_ERROR_UNEXPECTED/);
stmt.finalize();
run_next_test();
@ -736,12 +731,7 @@ function test_bind_bogus_type_by_name()
let array = stmt.newBindingParamsArray();
let bp = array.newBindingParams();
// We get an error after calling executeAsync, not when we bind.
bp.bindByName("blob", run_test);
array.addParams(bp);
stmt.bindParameters(array);
execAsync(stmt, {error: Ci.mozIStorageError.MISMATCH});
Assert.throws(() => bp.bindByName("blob", run_test), /NS_ERROR_UNEXPECTED/);
stmt.finalize();
run_next_test();

View File

@ -59,6 +59,23 @@ this.BrowserTestUtils = {
});
},
/**
* @return {Promise}
* A Promise which resolves when a "domwindowopened" notification
* has been fired by the window watcher.
*/
domWindowOpened() {
return new Promise(resolve => {
function observer(subject, topic, data) {
if (topic != "domwindowopened") { return; }
Services.ww.unregisterNotification(observer);
resolve(subject.QueryInterface(Ci.nsIDOMWindow));
}
Services.ww.registerNotification(observer);
});
},
/**
* @param {Object} options
* {
@ -89,6 +106,29 @@ this.BrowserTestUtils = {
subject => subject == win).then(() => win);
},
/**
* Closes a window.
*
* @param {Window}
* A window to close.
*
* @return {Promise}
* Resolves when the provided window has been closed.
*/
closeWindow(win) {
return new Promise(resolve => {
function observer(subject, topic, data) {
if (topic == "domwindowclosed" && subject === win) {
Services.ww.unregisterNotification(observer);
resolve();
}
}
Services.ww.registerNotification(observer);
win.close();
});
},
/**
* Waits a specified number of miliseconds for a specified event to be
* fired on a specified element.

View File

@ -26,6 +26,12 @@ window.addEventListener("load", function testOnLoad() {
let sdkpath = null;
// Strip off the chrome prefix to get the actual path of the test directory
function realPath(chrome) {
return chrome.substring("chrome://mochitests/content/jetpack-addon/".length)
.replace(".xpi", "");
}
// Installs a single add-on returning a promise for when install is completed
function installAddon(url) {
return new Promise(function(resolve, reject) {
@ -68,8 +74,12 @@ function installAddon(url) {
resolve(addon);
},
onDownloadFailed: function(install) {
reject("Download failed: " + install.error);
},
onInstallFailed: function(install) {
reject();
reject("Install failed: " + install.error);
}
});
@ -110,10 +120,15 @@ function waitForResults() {
// Runs tests for the add-on available at URL.
let testAddon = Task.async(function*({ url, expected }) {
dump("TEST-INFO | jetpack-addon-harness.js | Installing test add-on " + realPath(url) + "\n");
let addon = yield installAddon(url);
let results = yield waitForResults();
dump("TEST-INFO | jetpack-addon-harness.js | Uninstalling test add-on " + realPath(url) + "\n");
yield uninstallAddon(addon);
dump("TEST-INFO | jetpack-addon-harness.js | Testing add-on " + realPath(url) + " is complete\n");
return results;
});
@ -196,13 +211,20 @@ function testInit() {
testAddon(filename).then(results => {
passed += results.passed;
failed += results.failed;
}).then(testNextAddon);
}).then(testNextAddon, error => {
// If something went wrong during the test then a previous test add-on
// may still be installed, this leaves us in an unexpected state so
// probably best to just abandon testing at this point
failed++;
dump("TEST-UNEXPECTED-FAIL | jetpack-addon-harness.js | Error testing " + realPath(filename.url) + ": " + error + "\n");
finish();
});
}
testNextAddon();
}
catch (e) {
dump("TEST-UNEXPECTED-FAIL: jetpack-addon-harness.js | error starting test harness (" + e + ")\n");
dump("TEST-UNEXPECTED-FAIL | jetpack-addon-harness.js | error starting test harness (" + e + ")\n");
dump(e.stack);
}
});

View File

@ -25,6 +25,8 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
@ -88,23 +90,11 @@ const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
/**
* Indicates the delay between a change to the downloads data and the related
* save operation. This value is the result of a delicate trade-off, assuming
* the host application uses the browser history instead of the download store
* to save completed downloads.
* save operation.
*
* If a download takes less than this interval to complete (for example, saving
* a page that is already displayed), then no input/output is triggered by the
* download store except for an existence check, resulting in the best possible
* efficiency.
*
* Conversely, if the browser is closed before this interval has passed, the
* download will not be saved. This prevents it from being restored in the next
* session, and if there is partial data associated with it, then the ".part"
* file will not be deleted when the browser starts again.
*
* In all cases, for best efficiency, this value should be high enough that the
* input/output for opening or closing the target file does not overlap with the
* one for saving the list of downloads.
* For best efficiency, this value should be high enough that the input/output
* for opening or closing the target file does not overlap with the one for
* saving the list of downloads.
*/
const kSaveDelayMs = 1500;
@ -1226,6 +1216,8 @@ this.DownloadAutoSaveView = function (aList, aStore)
this._store = aStore;
this._downloadsMap = new Map();
this._writer = new DeferredTask(() => this._store.save(), kSaveDelayMs);
AsyncShutdown.profileBeforeChange.addBlocker("DownloadAutoSaveView: writing data",
() => this._writer.finalize());
}
this.DownloadAutoSaveView.prototype = {

View File

@ -23,6 +23,7 @@ this.EXPORTED_SYMBOLS = [
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
////////////////////////////////////////////////////////////////////////////////
@ -32,6 +33,33 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
* Contains functions shared by different Login Manager components.
*/
this.LoginHelper = {
/**
* Warning: this only updates if a logger was created.
*/
debug: Services.prefs.getBoolPref("signon.debug"),
createLogger(aLogPrefix) {
let getMaxLogLevel = () => {
return this.debug ? "debug" : "error";
};
// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
let ConsoleAPI = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).ConsoleAPI;
let consoleOptions = {
maxLogLevel: getMaxLogLevel(),
prefix: aLogPrefix,
};
let logger = new ConsoleAPI(consoleOptions);
// Watch for pref changes and update this.debug and the maxLogLevel for created loggers
Services.prefs.addObserver("signon.", () => {
this.debug = Services.prefs.getBoolPref("signon.debug");
logger.maxLogLevel = getMaxLogLevel();
}, false);
return logger;
},
/**
* Due to the way the signons2.txt file is formatted, we need to make
* sure certain field values or characters do not cause the file to

View File

@ -5,12 +5,12 @@
"use strict";
const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.importGlobalProperties(["URL"]);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UserAutoCompleteResult",
"resource://gre/modules/LoginManagerContent.jsm");
@ -120,6 +120,12 @@ var LoginManagerParent = {
mm.addMessageListener("RemoteLogins:findLogins", this);
mm.addMessageListener("RemoteLogins:onFormSubmit", this);
mm.addMessageListener("RemoteLogins:autoCompleteLogins", this);
XPCOMUtils.defineLazyGetter(this, "recipeParentPromise", () => {
const { LoginRecipesParent } = Cu.import("resource://gre/modules/LoginRecipes.jsm", {});
let parent = new LoginRecipesParent();
return parent.initializationPromise;
});
},
receiveMessage: function (msg) {
@ -127,11 +133,11 @@ var LoginManagerParent = {
switch (msg.name) {
case "RemoteLogins:findLogins": {
// TODO Verify msg.target's principals against the formOrigin?
this.findLogins(data.options.showMasterPassword,
data.formOrigin,
data.actionOrigin,
data.requestId,
msg.target.messageManager);
this.sendLoginDataToChild(data.options.showMasterPassword,
data.formOrigin,
data.actionOrigin,
data.requestId,
msg.target.messageManager);
break;
}
@ -154,19 +160,40 @@ var LoginManagerParent = {
}
},
findLogins: function(showMasterPassword, formOrigin, actionOrigin,
requestId, target) {
/**
* Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
*/
sendLoginDataToChild: Task.async(function*(showMasterPassword, formOrigin, actionOrigin,
requestId, target) {
let recipes = [];
if (formOrigin) {
let formHost;
try {
formHost = (new URL(formOrigin)).host;
let recipeManager = yield this.recipeParentPromise;
recipes = recipeManager.getRecipesForHost(formHost);
} catch (ex) {
// Some schemes e.g. chrome aren't supported by URL
}
}
if (!showMasterPassword && !Services.logins.isLoggedIn) {
target.sendAsyncMessage("RemoteLogins:loginsFound",
{ requestId: requestId, logins: [] });
target.sendAsyncMessage("RemoteLogins:loginsFound", {
requestId: requestId,
logins: [],
recipes,
});
return;
}
let allLoginsCount = Services.logins.countLogins(formOrigin, "", null);
// If there are no logins for this site, bail out now.
if (!allLoginsCount) {
target.sendAsyncMessage("RemoteLogins:loginsFound",
{ requestId: requestId, logins: [] });
target.sendAsyncMessage("RemoteLogins:loginsFound", {
requestId: requestId,
logins: [],
recipes,
});
return;
}
@ -185,13 +212,16 @@ var LoginManagerParent = {
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
if (topic == "passwordmgr-crypto-loginCanceled") {
target.sendAsyncMessage("RemoteLogins:loginsFound",
{ requestId: requestId, logins: [] });
target.sendAsyncMessage("RemoteLogins:loginsFound", {
requestId: requestId,
logins: [],
recipes,
});
return;
}
self.findLogins(showMasterPassword, formOrigin, actionOrigin,
requestId, target);
self.sendLoginDataToChild(showMasterPassword, formOrigin, actionOrigin,
requestId, target);
},
};
@ -212,6 +242,7 @@ var LoginManagerParent = {
target.sendAsyncMessage("RemoteLogins:loginsFound", {
requestId: requestId,
logins: jsLogins,
recipes,
});
const PWMGR_FORM_ACTION_EFFECT = Services.telemetry.getHistogramById("PWMGR_FORM_ACTION_EFFECT");
@ -223,7 +254,7 @@ var LoginManagerParent = {
// logins.length < allLoginsCount
PWMGR_FORM_ACTION_EFFECT.add(1);
}
},
}),
doAutocompleteSearch: function({ formOrigin, actionOrigin,
searchString, previousResult,

View File

@ -0,0 +1,123 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["LoginRecipesParent"];
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const REQUIRED_KEYS = ["hosts"];
const OPTIONAL_KEYS = ["description", "pathRegex"];
const SUPPORTED_KEYS = REQUIRED_KEYS.concat(OPTIONAL_KEYS);
Cu.importGlobalProperties(["URL"]);
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyGetter(this, "log", () => LoginHelper.createLogger("LoginRecipes"));
function LoginRecipesParent() {
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
throw new Error("LoginRecipesParent should only be used from the main process");
}
this._recipesByHost = new Map();
this.initializationPromise = Promise.resolve(this);
}
LoginRecipesParent.prototype = {
/**
* Promise resolved with an instance of itself when the module is ready.
*
* @type {Promise}
*/
initializationPromise: null,
/**
* @type {Map} Map of hosts (including non-default port numbers) to Sets of recipes.
* e.g. "example.com:8080" => Set({...})
*/
_recipesByHost: null,
/**
* @param {Object} aRecipes an object containing recipes to load for use. The object
* should be compatible with JSON (e.g. no RegExp).
* @return {Promise} resolving when the recipes are loaded
*/
load(aRecipes) {
for (let rawRecipe of aRecipes.siteRecipes) {
try {
rawRecipe.pathRegex = rawRecipe.pathRegex ? new RegExp(rawRecipe.pathRegex) : undefined;
this.add(rawRecipe);
} catch (ex) {
log.error("Error loading recipe", rawRecipe, ex);
}
}
return Promise.resolve();
},
/**
* Validate the recipe is sane and then add it to the set of recipes.
*
* @param {Object} recipe
*/
add(recipe) {
log.debug("Adding recipe:", recipe);
let recipeKeys = Object.keys(recipe);
let unknownKeys = recipeKeys.filter(key => SUPPORTED_KEYS.indexOf(key) == -1);
if (unknownKeys.length > 0) {
throw new Error("The following recipe keys aren't supported: " + unknownKeys.join(", "));
}
let missingRequiredKeys = REQUIRED_KEYS.filter(key => recipeKeys.indexOf(key) == -1);
if (missingRequiredKeys.length > 0) {
throw new Error("The following required recipe keys are missing: " + missingRequiredKeys.join(", "));
}
if (!Array.isArray(recipe.hosts)) {
throw new Error("'hosts' must be a array");
}
if (!recipe.hosts.length) {
throw new Error("'hosts' must be a non-empty array");
}
if (recipe.pathRegex && recipe.pathRegex.constructor.name != "RegExp") {
throw new Error("'pathRegex' must be a regular expression");
}
if (recipe.description && typeof(recipe.description) != "string") {
throw new Error("'description' must be a string");
}
// Add the recipe to the map for each host
for (let host of recipe.hosts) {
if (!this._recipesByHost.has(host)) {
this._recipesByHost.set(host, new Set());
}
this._recipesByHost.get(host).add(recipe);
}
},
/**
* Currently only exact host matches are returned but this will eventually handle parent domains.
*
* @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
* @return {Set} of recipes that apply to the host ordered by host priority
*/
getRecipesForHost(aHost) {
let hostRecipes = this._recipesByHost.get(aHost);
if (!hostRecipes) {
return new Set();
}
return hostRecipes;
},
};

View File

@ -40,6 +40,7 @@ EXTRA_JS_MODULES += [
'LoginHelper.jsm',
'LoginManagerContent.jsm',
'LoginManagerParent.jsm',
'LoginRecipes.jsm',
]
if CONFIG['OS_TARGET'] == 'Android':

View File

@ -16,6 +16,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/LoginRecipes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
"resource://gre/modules/DownloadPaths.jsm");
@ -198,6 +199,12 @@ const LoginTest = {
}
};
const RecipeHelpers = {
initNewParent() {
return (new LoginRecipesParent()).initializationPromise;
},
};
////////////////////////////////////////////////////////////////////////////////
//// Predefined test data

View File

@ -0,0 +1,141 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests adding and retrieving LoginRecipes in the parent process.
*/
"use strict";
add_task(function* test_init() {
let parent = new LoginRecipesParent();
let initPromise1 = parent.initializationPromise;
let initPromise2 = parent.initializationPromise;
Assert.strictEqual(initPromise1, initPromise2, "Check that the same promise is returned");
let recipesParent = yield initPromise1;
Assert.ok(recipesParent instanceof LoginRecipesParent, "Check init return value");
Assert.strictEqual(recipesParent._recipesByHost.size, 0, "Initially 0 recipes");
});
add_task(function* test_get_missing_host() {
let recipesParent = yield RecipeHelpers.initNewParent();
let exampleRecipes = recipesParent.getRecipesForHost("example.invalid");
Assert.strictEqual(exampleRecipes.size, 0, "Check recipe count for example.invalid");
});
add_task(function* test_add_get_simple_host() {
let recipesParent = yield RecipeHelpers.initNewParent();
Assert.strictEqual(recipesParent._recipesByHost.size, 0, "Initially 0 recipes");
recipesParent.add({
hosts: ["example.com"],
});
Assert.strictEqual(recipesParent._recipesByHost.size, 1,
"Check number of hosts after the addition");
let exampleRecipes = recipesParent.getRecipesForHost("example.com");
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com");
let recipe = [...exampleRecipes][0];
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
Assert.strictEqual(recipe.hosts.length, 1, "Check that one host is present");
Assert.strictEqual(recipe.hosts[0], "example.com", "Check the one host");
});
add_task(function* test_add_get_non_standard_port_host() {
let recipesParent = yield RecipeHelpers.initNewParent();
recipesParent.add({
hosts: ["example.com:8080"],
});
Assert.strictEqual(recipesParent._recipesByHost.size, 1,
"Check number of hosts after the addition");
let exampleRecipes = recipesParent.getRecipesForHost("example.com:8080");
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com:8080");
let recipe = [...exampleRecipes][0];
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
Assert.strictEqual(recipe.hosts.length, 1, "Check that one host is present");
Assert.strictEqual(recipe.hosts[0], "example.com:8080", "Check the one host");
});
add_task(function* test_add_multiple_hosts() {
let recipesParent = yield RecipeHelpers.initNewParent();
recipesParent.add({
hosts: ["example.com", "foo.invalid"],
});
Assert.strictEqual(recipesParent._recipesByHost.size, 2,
"Check number of hosts after the addition");
let exampleRecipes = recipesParent.getRecipesForHost("example.com");
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com");
let recipe = [...exampleRecipes][0];
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
Assert.strictEqual(recipe.hosts.length, 2, "Check that two hosts are present");
Assert.strictEqual(recipe.hosts[0], "example.com", "Check the first host");
Assert.strictEqual(recipe.hosts[1], "foo.invalid", "Check the second host");
let fooRecipes = recipesParent.getRecipesForHost("foo.invalid");
Assert.strictEqual(fooRecipes.size, 1, "Check recipe count for foo.invalid");
let fooRecipe = [...fooRecipes][0];
Assert.strictEqual(fooRecipe, recipe, "Check that the recipe is shared");
Assert.strictEqual(typeof(fooRecipe), "object", "Check recipe type");
Assert.strictEqual(fooRecipe.hosts.length, 2, "Check that two hosts are present");
Assert.strictEqual(fooRecipe.hosts[0], "example.com", "Check the first host");
Assert.strictEqual(fooRecipe.hosts[1], "foo.invalid", "Check the second host");
});
add_task(function* test_add_pathRegex() {
let recipesParent = yield RecipeHelpers.initNewParent();
recipesParent.add({
hosts: ["example.com"],
pathRegex: /^\/mypath\//,
});
Assert.strictEqual(recipesParent._recipesByHost.size, 1,
"Check number of hosts after the addition");
let exampleRecipes = recipesParent.getRecipesForHost("example.com");
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com");
let recipe = [...exampleRecipes][0];
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
Assert.strictEqual(recipe.hosts.length, 1, "Check that one host is present");
Assert.strictEqual(recipe.hosts[0], "example.com", "Check the one host");
Assert.strictEqual(recipe.pathRegex.toString(), "/^\\/mypath\\//", "Check the pathRegex");
});
/* Begin checking errors with add */
add_task(function* test_add_missing_prop() {
let recipesParent = yield RecipeHelpers.initNewParent();
Assert.throws(() => recipesParent.add({}), /required/, "Some properties are required");
});
add_task(function* test_add_unknown_prop() {
let recipesParent = yield RecipeHelpers.initNewParent();
Assert.throws(() => recipesParent.add({
unknownProp: true,
}), /supported/, "Unknown properties should cause an error to help with typos");
});
add_task(function* test_add_invalid_hosts() {
let recipesParent = yield RecipeHelpers.initNewParent();
Assert.throws(() => recipesParent.add({
hosts: 404,
}), /array/, "hosts should be an array");
});
add_task(function* test_add_empty_host_array() {
let recipesParent = yield RecipeHelpers.initNewParent();
Assert.throws(() => recipesParent.add({
hosts: [],
}), /array/, "hosts should be a non-empty array");
});
add_task(function* test_add_pathRegex_non_regexp() {
let recipesParent = yield RecipeHelpers.initNewParent();
Assert.throws(() => recipesParent.add({
hosts: ["example.com"],
pathRegex: "foo",
}), /regular expression/, "pathRegex should be a RegExp");
});
/* End checking errors with add */

View File

@ -23,5 +23,6 @@ skip-if = os != "android"
[test_logins_metainfo.js]
[test_logins_search.js]
[test_notifications.js]
[test_recipes_add.js]
[test_storage.js]
[test_telemetry.js]

View File

@ -143,36 +143,19 @@ function isUSTimezone() {
}
/**
* Run some callback once metadata has been committed to disk.
* Waits for metadata being committed.
* @return {Promise} Resolved when the metadata is committed to disk.
*/
function afterCommit(callback)
{
let obs = function(result, topic, verb) {
if (verb == "write-metadata-to-disk-complete") {
Services.obs.removeObserver(obs, topic);
callback(result);
} else {
dump("TOPIC: " + topic+ "\n");
}
}
Services.obs.addObserver(obs, "browser-search-service", false);
function promiseAfterCommit() {
return waitForSearchNotification("write-metadata-to-disk-complete");
}
/**
* Run some callback once cache has been built.
* Waits for the cache file to be saved.
* @return {Promise} Resolved when the cache file is saved.
*/
function afterCache(callback)
{
let obs = function(result, topic, verb) {
do_print("afterCache: " + verb);
if (verb == "write-cache-to-disk-complete") {
Services.obs.removeObserver(obs, topic);
callback(result);
} else {
dump("TOPIC: " + topic+ "\n");
}
}
Services.obs.addObserver(obs, "browser-search-service", false);
function promiseAfterCache() {
return waitForSearchNotification("write-cache-to-disk-complete");
}
function parseJsonFromStream(aInputStream) {

View File

@ -23,24 +23,27 @@ function run_test()
add_task(function* test_nocache() {
let search = Services.search;
// Check that cache is created at startup
afterCache(function cacheCreated() {
// Check that search.json has been created.
let cache = gProfD.clone();
cache.append("search.json");
do_check_true(cache.exists());
});
let afterCachePromise = promiseAfterCache();
yield new Promise((resolve, reject) => search.init(rv => {
Components.isSuccessCode(rv) ? resolve() : reject();
}));
// Check that the cache is created at startup
yield afterCachePromise;
// Check that search.json has been created.
let cacheFile = gProfD.clone();
cacheFile.append("search.json");
do_check_true(cacheFile.exists());
// Add engine and wait for cache update
yield addTestEngines([
{ name: "Test search engine", xmlFileName: "engine.xml" },
]);
do_print("Engine has been added, let's wait for the cache to be built");
yield new Promise(resolve => afterCache(resolve));
yield promiseAfterCache();
do_print("Searching test engine in cache");
let path = OS.Path.join(OS.Constants.Path.profileDir, "search.json");

View File

@ -45,7 +45,7 @@ add_task(function* test_nodb_pluschanges() {
yield new Promise(resolve => do_execute_soon(resolve));
do_print("Forcing flush");
let promiseCommit = new Promise(resolve => afterCommit(resolve));
let promiseCommit = promiseAfterCommit();
search.QueryInterface(Ci.nsIObserver)
.observe(null, "quit-application", "");
yield promiseCommit;

View File

@ -39,7 +39,7 @@ add_task(function* test_save_sorted_engines() {
search.moveEngine(engine2, 1);
// Changes should be commited immediately
yield new Promise(resolve => afterCommit(resolve));
yield promiseAfterCommit();
do_print("Commit complete after moveEngine");
// Check that the entries are placed as specified correctly
@ -49,7 +49,7 @@ add_task(function* test_save_sorted_engines() {
// Test removing an engine
search.removeEngine(engine1);
yield new Promise(resolve => afterCommit(resolve));
yield promiseAfterCommit();
do_print("Commit complete after removeEngine");
// Check that the order of the remaining engine was updated correctly
@ -59,7 +59,7 @@ add_task(function* test_save_sorted_engines() {
// Test adding a new engine
search.addEngineWithDetails("foo", "", "foo", "", "GET",
"http://searchget/?search={searchTerms}");
yield new Promise(resolve => afterCommit(resolve));
yield promiseAfterCommit();
do_print("Commit complete after addEngineWithDetails");
json = getSearchMetadata();

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