forked from mirrors/gecko-dev
MozReview-Commit-ID: 7E7LPorrEje --HG-- extra : rebase_source : 0572a35415a766a3f31d266760ecd07f0dcc3f72
220 lines
7.9 KiB
JavaScript
220 lines
7.9 KiB
JavaScript
"use strict";
|
|
|
|
// The purpose of this test is to test the urlbar popup's add-on iframe. It has
|
|
// a few parts:
|
|
//
|
|
// (1) This file, a normal browser mochitest.
|
|
// (2) html/js files that are loaded in the urlbar popup's add-on iframe:
|
|
// urlbarAddonIframe.{html,js}
|
|
// (3) A content script that mediates between the first two parts:
|
|
// urlbarAddonIframeContentScript.js
|
|
//
|
|
// The main test file (this file) sends messages to the content script, which
|
|
// forwards them as events to the iframe. These messages tell the iframe js to
|
|
// do various things like call functions on the urlbar API and expect events.
|
|
// In response, the iframe js dispatches ack events to the content script, which
|
|
// forwards them as messages to the main test file.
|
|
//
|
|
// The content script may not be necessary right now since the iframe is not
|
|
// remote. But this structure ensures that if the iframe is made remote in the
|
|
// future, then the test won't have to change very much, and ideally not at all.
|
|
//
|
|
// Actually there's one other part:
|
|
//
|
|
// (4) The Panel.jsm that's bundled with add-ons that use the iframe.
|
|
//
|
|
// Panel.jsm defines the API that's made available to add-on scripts running in
|
|
// the iframe. This API is orthogonal to the add-on iframe itself. You could
|
|
// load any html/js in the iframe, technically. But the purpose of the iframe
|
|
// is to support this Panel.jsm API, so that's what this test tests.
|
|
|
|
const PANEL_JSM_BASENAME = "Panel.jsm";
|
|
const IFRAME_BASENAME = "urlbarAddonIframe.html";
|
|
const CONTENT_SCRIPT_BASENAME = "urlbarAddonIframeContentScript.js";
|
|
|
|
// The iframe's message manager.
|
|
let gMsgMan;
|
|
|
|
add_task(function* () {
|
|
let rootDirURL = getRootDirectory(gTestPath);
|
|
let jsmURL = rootDirURL + PANEL_JSM_BASENAME;
|
|
let iframeURL = rootDirURL + IFRAME_BASENAME;
|
|
let contentScriptURL = rootDirURL + CONTENT_SCRIPT_BASENAME;
|
|
|
|
let { Panel } = Cu.import(jsmURL, {});
|
|
let panel = new Panel(gURLBar.popup, iframeURL);
|
|
registerCleanupFunction(() => {
|
|
panel.destroy();
|
|
Assert.ok(gURLBar.popup._addonIframe === null, "iframe should be gone");
|
|
});
|
|
|
|
let iframe = gURLBar.popup._addonIframe;
|
|
Assert.ok(!!iframe, "iframe should not be null");
|
|
|
|
gMsgMan =
|
|
iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
|
|
gMsgMan.loadFrameScript(contentScriptURL, false);
|
|
|
|
yield promiseIframeLoad();
|
|
|
|
// urlbar.getValue
|
|
let value = "this value set by the test";
|
|
gURLBar.value = value;
|
|
let readValue = yield promiseUrlbarFunctionCall("getValue");
|
|
Assert.equal(readValue, value, "value");
|
|
|
|
// urlbar.setValue
|
|
value = "this value set by the iframe";
|
|
yield promiseUrlbarFunctionCall("setValue", value);
|
|
Assert.equal(gURLBar.value, value, "setValue");
|
|
|
|
// urlbar.getMaxResults
|
|
let maxResults = gURLBar.popup.maxResults;
|
|
Assert.equal(typeof(maxResults), "number", "Sanity check");
|
|
let readMaxResults = yield promiseUrlbarFunctionCall("getMaxResults");
|
|
Assert.equal(readMaxResults, maxResults, "getMaxResults");
|
|
|
|
// urlbar.setMaxResults
|
|
let newMaxResults = maxResults + 10;
|
|
yield promiseUrlbarFunctionCall("setMaxResults", newMaxResults);
|
|
Assert.equal(gURLBar.popup.maxResults, newMaxResults, "setMaxResults");
|
|
gURLBar.popup.maxResults = maxResults;
|
|
|
|
// urlbar.enter
|
|
value = "http://mochi.test:8888/";
|
|
yield promiseUrlbarFunctionCall("setValue", value);
|
|
Assert.equal(gURLBar.value, value, "setValue");
|
|
yield promiseUrlbarFunctionCall("enter");
|
|
let browser = gBrowser.selectedBrowser;
|
|
yield BrowserTestUtils.browserLoaded(browser);
|
|
Assert.equal(browser.currentURI.spec, value,
|
|
"enter should have loaded the URL");
|
|
|
|
// input, reset, and result events. There should always be at least one
|
|
// result, the heuristic result.
|
|
value = "test";
|
|
let promiseValues = yield Promise.all([
|
|
promiseEvent("input")[1],
|
|
promiseEvent("reset")[1],
|
|
promiseEvent("result")[1],
|
|
promiseAutocompleteResultPopup(value, window, true),
|
|
]);
|
|
|
|
// Check the heuristic result.
|
|
let result = promiseValues[2];
|
|
let engineName = Services.search.currentEngine.name;
|
|
Assert.equal(result.url,
|
|
`moz-action:searchengine,{"engineName":"${engineName}","input":"test","searchQuery":"test"}`,
|
|
"result.url");
|
|
Assert.ok("action" in result, "result.action");
|
|
Assert.equal(result.action.type, "searchengine", "result.action.type");
|
|
Assert.ok("params" in result.action, "result.action.params");
|
|
Assert.equal(result.action.params.engineName, engineName,
|
|
"result.action.params.engineName");
|
|
Assert.equal(typeof(result.image), "string", "result.image");
|
|
Assert.equal(result.title, engineName, "result.title");
|
|
Assert.equal(result.type, "action searchengine heuristic", "result.type");
|
|
Assert.equal(result.text, value, "result.text");
|
|
|
|
// keydown event. promiseEvent sends an async message to the iframe, but
|
|
// synthesizeKey is sync, so we need to wait until the content JS receives
|
|
// the message and adds its event listener before synthesizing the key.
|
|
let keydownPromises = promiseEvent("keydown");
|
|
yield keydownPromises[0];
|
|
EventUtils.synthesizeKey("KEY_ArrowDown", {
|
|
type: "keydown",
|
|
code: "ArrowDown",
|
|
});
|
|
yield keydownPromises[1];
|
|
|
|
// urlbar.getPanelHeight
|
|
let height = iframe.getBoundingClientRect().height;
|
|
let readHeight = yield promiseUrlbarFunctionCall("getPanelHeight");
|
|
Assert.equal(readHeight, height, "getPanelHeight");
|
|
|
|
// urlbar.setPanelHeight
|
|
let newHeight = height + 100;
|
|
yield promiseUrlbarFunctionCall("setPanelHeight", newHeight);
|
|
yield new Promise(resolve => {
|
|
// The height change is animated, so give it time to complete. Again, wait
|
|
// a sec to be safe.
|
|
setTimeout(resolve, 1000);
|
|
});
|
|
Assert.equal(iframe.getBoundingClientRect().height, newHeight,
|
|
"setPanelHeight");
|
|
});
|
|
|
|
function promiseIframeLoad() {
|
|
let msgName = "TestIframeLoadAck";
|
|
return new Promise(resolve => {
|
|
info("Waiting for iframe load ack");
|
|
gMsgMan.addMessageListener(msgName, function onMsg(msg) {
|
|
info("Received iframe load ack");
|
|
gMsgMan.removeMessageListener(msgName, onMsg);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a single promise that's resolved when the content JS has called the
|
|
* function.
|
|
*/
|
|
function promiseUrlbarFunctionCall(...args) {
|
|
return promiseMessage("function", args)[0];
|
|
}
|
|
|
|
/**
|
|
* Returns two promises in an array. The first is resolved when the content JS
|
|
* has added its event listener. The second is resolved when the content JS
|
|
* has received the event.
|
|
*/
|
|
function promiseEvent(type) {
|
|
return promiseMessage("event", type, 2);
|
|
}
|
|
|
|
let gNextMessageID = 1;
|
|
|
|
/**
|
|
* Returns an array of promises, one per ack. Each is resolved when the content
|
|
* JS acks the message. numExpectedAcks is the number of acks you expect.
|
|
*/
|
|
function promiseMessage(type, data, numExpectedAcks = 1) {
|
|
let testMsgName = "TestMessage";
|
|
let ackMsgName = "TestMessageAck";
|
|
let msgID = gNextMessageID++;
|
|
gMsgMan.sendAsyncMessage(testMsgName, {
|
|
type,
|
|
messageID: msgID,
|
|
data,
|
|
});
|
|
let ackPromises = [];
|
|
for (let i = 0; i < numExpectedAcks; i++) {
|
|
let ackIndex = i;
|
|
ackPromises.push(new Promise(resolve => {
|
|
info("Waiting for message ack: " + JSON.stringify({
|
|
type,
|
|
msgID,
|
|
ackIndex,
|
|
}));
|
|
gMsgMan.addMessageListener(ackMsgName, function onMsg(msg) {
|
|
// Messages have IDs so that an ack can be correctly paired with the
|
|
// initial message it's replying to. It's not an error if the ack's ID
|
|
// isn't equal to msgID here. That will happen when multiple messages
|
|
// have been sent in a single turn of the event loop so that they're all
|
|
// waiting on acks. Same goes for ackIndex.
|
|
if (msg.data.messageID != msgID || msg.data.ackIndex != ackIndex) {
|
|
return;
|
|
}
|
|
info("Received message ack: " + JSON.stringify({
|
|
type,
|
|
msgID: msg.data.messageID,
|
|
ackIndex,
|
|
}));
|
|
gMsgMan.removeMessageListener(ackMsgName, onMsg);
|
|
resolve(msg.data.data);
|
|
});
|
|
}));
|
|
}
|
|
return ackPromises;
|
|
}
|