forked from mirrors/gecko-dev
MozReview-Commit-ID: 4YcsNvRg7Hn --HG-- extra : rebase_source : 15518736c9cc52cf18a0540417e6a38c9bed630a
526 lines
16 KiB
JavaScript
526 lines
16 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
|
|
|
|
Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
|
|
|
|
// Opens and closes a new tab to clear any existing preloaded ones. This is
|
|
// necessary to prevent any left-over activity-stream preloaded new tabs from
|
|
// affecting these tests.
|
|
BrowserOpenTab();
|
|
const initialTab = gBrowser.selectedTab;
|
|
gBrowser.removeTab(initialTab);
|
|
|
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
|
|
PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
|
|
Sanitizer: "resource:///modules/Sanitizer.jsm",
|
|
});
|
|
|
|
var gWindow = window;
|
|
|
|
// Default to dummy/empty directory links
|
|
var gDirectorySource = 'data:application/json,{"test":1}';
|
|
var gOrigDirectorySource;
|
|
|
|
// The tests assume all 3 rows and all 3 columns of sites are shown, but the
|
|
// window may be too small to actually show everything. Resize it if necessary.
|
|
var requiredSize = {};
|
|
requiredSize.innerHeight =
|
|
40 + 32 + // undo container + bottom margin
|
|
44 + 32 + // search bar + bottom margin
|
|
(3 * (180 + 32)) + // 3 rows * (tile height + title and bottom margin)
|
|
100; // breathing room
|
|
requiredSize.innerWidth =
|
|
(3 * (290 + 20)) + // 3 cols * (tile width + side margins)
|
|
100; // breathing room
|
|
|
|
add_task(async function setupWindowSize() {
|
|
let [oldSize, curWidth, curHeight] = await ContentTask.spawn(gBrowser.selectedBrowser, requiredSize, (requiredSizeArg) => {
|
|
var oldSizeVar = {};
|
|
Object.keys(requiredSizeArg).forEach(prop => {
|
|
info([prop, content[prop], requiredSizeArg[prop]]);
|
|
if (content[prop] < requiredSizeArg[prop]) {
|
|
oldSizeVar[prop] = content[prop];
|
|
info("Changing browser " + prop + " from " + oldSizeVar[prop] + " to " +
|
|
requiredSizeArg[prop]);
|
|
content[prop] = requiredSizeArg[prop];
|
|
}
|
|
});
|
|
return [oldSizeVar, content.outerWidth, content.outerHeight];
|
|
});
|
|
|
|
var screenHeight = {};
|
|
var screenWidth = {};
|
|
Cc["@mozilla.org/gfx/screenmanager;1"].
|
|
getService(Ci.nsIScreenManager).
|
|
primaryScreen.
|
|
GetAvailRectDisplayPix({}, {}, screenWidth, screenHeight);
|
|
screenHeight = screenHeight.value;
|
|
screenWidth = screenWidth.value;
|
|
|
|
if (screenHeight < curHeight) {
|
|
info("Warning: Browser outer height is now " +
|
|
curHeight + ", which is larger than the " +
|
|
"available screen height, " + screenHeight +
|
|
". That may cause problems.");
|
|
}
|
|
|
|
if (screenWidth < curWidth) {
|
|
info("Warning: Browser outer width is now " +
|
|
curWidth + ", which is larger than the " +
|
|
"available screen width, " + screenWidth +
|
|
". That may cause problems.");
|
|
}
|
|
|
|
registerCleanupFunction(function() {
|
|
while (gWindow.gBrowser.tabs.length > 1)
|
|
gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
|
|
|
|
ContentTask.spawn(gBrowser.selectedBrowser, oldSize, (oldSizeArg) => {
|
|
Object.keys(oldSizeArg).forEach(prop => {
|
|
if (oldSizeArg[prop]) {
|
|
content[prop] = oldSizeArg[prop];
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
registerCleanupFunction(function() {
|
|
Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
|
|
});
|
|
|
|
function pushPrefs(...aPrefs) {
|
|
return SpecialPowers.pushPrefEnv({"set": aPrefs});
|
|
}
|
|
|
|
add_task(async function setup() {
|
|
registerCleanupFunction(function() {
|
|
return new Promise(resolve => {
|
|
function cleanupAndFinish() {
|
|
PlacesUtils.history.clear().then(() => {
|
|
whenPagesUpdated().then(resolve);
|
|
NewTabUtils.restore();
|
|
});
|
|
}
|
|
|
|
let callbacks = NewTabUtils.links._populateCallbacks;
|
|
let numCallbacks = callbacks.length;
|
|
|
|
if (numCallbacks)
|
|
callbacks.splice(0, numCallbacks, cleanupAndFinish);
|
|
else
|
|
cleanupAndFinish();
|
|
});
|
|
});
|
|
|
|
await whenPagesUpdated();
|
|
});
|
|
|
|
/** Perform an action on a cell within the newtab page.
|
|
* @param aIndex index of cell
|
|
* @param aFn function to call in child process or tab.
|
|
* @returns result of calling the function.
|
|
*/
|
|
function performOnCell(aIndex, aFn) {
|
|
return ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
|
|
{ index: aIndex, fn: aFn.toString() }, async function(args) {
|
|
let cell = content.gGrid.cells[args.index];
|
|
// eslint-disable-next-line no-eval
|
|
return eval(args.fn)(cell);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Allows to provide a list of links that is used to construct the grid.
|
|
* @param aLinksPattern the pattern (see below)
|
|
*
|
|
* Example: setLinks("-1,0,1,2,3")
|
|
* Result: [{url: "http://example.com/", title: "site#-1"},
|
|
* {url: "http://example0.com/", title: "site#0"},
|
|
* {url: "http://example1.com/", title: "site#1"},
|
|
* {url: "http://example2.com/", title: "site#2"},
|
|
* {url: "http://example3.com/", title: "site#3"}]
|
|
*/
|
|
function setLinks(aLinks) {
|
|
return new Promise(resolve => {
|
|
let links = aLinks;
|
|
|
|
if (typeof links == "string") {
|
|
links = aLinks.split(/\s*,\s*/).map(function(id) {
|
|
return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
|
|
title: "site#" + id};
|
|
});
|
|
}
|
|
|
|
// Call populateCache() once to make sure that all link fetching that is
|
|
// currently in progress has ended. We clear the history, fill it with the
|
|
// given entries and call populateCache() now again to make sure the cache
|
|
// has the desired contents.
|
|
NewTabUtils.links.populateCache(function() {
|
|
PlacesUtils.history.clear().then(() => {
|
|
fillHistory(links).then(() => {
|
|
NewTabUtils.links.populateCache(function() {
|
|
NewTabUtils.allPages.update();
|
|
resolve();
|
|
}, true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function fillHistory(aLinks) {
|
|
return new Promise(resolve => {
|
|
let numLinks = aLinks.length;
|
|
if (!numLinks) {
|
|
executeSoon(resolve);
|
|
return;
|
|
}
|
|
|
|
let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
|
|
|
|
// Important: To avoid test failures due to clock jitter on Windows XP, call
|
|
// Date.now() once here, not each time through the loop.
|
|
let now = Date.now() * 1000;
|
|
|
|
for (let i = 0; i < aLinks.length; i++) {
|
|
let link = aLinks[i];
|
|
let place = {
|
|
uri: makeURI(link.url),
|
|
title: link.title,
|
|
// Links are secondarily sorted by visit date descending, so decrease the
|
|
// visit date as we progress through the array so that links appear in the
|
|
// grid in the order they're present in the array.
|
|
visits: [{visitDate: now - i, transitionType: transitionLink}]
|
|
};
|
|
|
|
PlacesUtils.asyncHistory.updatePlaces(place, {
|
|
handleError: () => ok(false, "couldn't add visit to history"),
|
|
handleResult() {},
|
|
handleCompletion() {
|
|
if (--numLinks == 0) {
|
|
resolve();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Allows to specify the list of pinned links (that have a fixed position in
|
|
* the grid.
|
|
* @param aLinksPattern the pattern (see below)
|
|
*
|
|
* Example: setPinnedLinks("3,,1")
|
|
* Result: 'http://example3.com/' is pinned in the first cell. 'http://example1.com/' is
|
|
* pinned in the third cell.
|
|
*/
|
|
function setPinnedLinks(aLinks) {
|
|
let links = aLinks;
|
|
|
|
if (typeof links == "string") {
|
|
links = aLinks.split(/\s*,\s*/).map(function(id) {
|
|
if (id)
|
|
return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
|
|
title: "site#" + id,
|
|
type: "history"};
|
|
return undefined;
|
|
});
|
|
}
|
|
|
|
Services.prefs.setStringPref("browser.newtabpage.pinned", JSON.stringify(links));
|
|
|
|
NewTabUtils.pinnedLinks.resetCache();
|
|
NewTabUtils.allPages.update();
|
|
}
|
|
|
|
/**
|
|
* Restore the grid state.
|
|
*/
|
|
function restore() {
|
|
return new Promise(resolve => {
|
|
whenPagesUpdated().then(resolve);
|
|
NewTabUtils.restore();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait until a given condition becomes true.
|
|
*/
|
|
function waitForCondition(aConditionFn, aMaxTries = 50, aCheckInterval = 100) {
|
|
return new Promise((resolve, reject) => {
|
|
let tries = 0;
|
|
|
|
function tryNow() {
|
|
tries++;
|
|
|
|
if (aConditionFn()) {
|
|
resolve();
|
|
} else if (tries < aMaxTries) {
|
|
tryAgain();
|
|
} else {
|
|
reject("Condition timed out: " + aConditionFn.toSource());
|
|
}
|
|
}
|
|
|
|
function tryAgain() {
|
|
setTimeout(tryNow, aCheckInterval);
|
|
}
|
|
|
|
tryAgain();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a new tab containing 'about:newtab'.
|
|
*/
|
|
async function addNewTabPageTab() {
|
|
let tab = await BrowserTestUtils.openNewForegroundTab(gWindow.gBrowser, "about:newtab", false);
|
|
let browser = tab.linkedBrowser;
|
|
|
|
// Wait for the document to become visible in case it was preloaded.
|
|
await waitForCondition(() => !browser.contentDocument.hidden);
|
|
|
|
await new Promise(resolve => {
|
|
if (NewTabUtils.allPages.enabled) {
|
|
// Continue when the link cache has been populated.
|
|
NewTabUtils.links.populateCache(function() {
|
|
whenSearchInitDone().then(resolve);
|
|
});
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
return tab;
|
|
}
|
|
|
|
/**
|
|
* Compares the current grid arrangement with the given pattern.
|
|
* @param the pattern (see below)
|
|
*
|
|
* Example: checkGrid("3p,2,,4p")
|
|
* Result: We expect the first cell to contain the pinned site 'http://example3.com/'.
|
|
* The second cell contains 'http://example2.com/'. The third cell is empty.
|
|
* The fourth cell contains the pinned site 'http://example4.com/'.
|
|
*/
|
|
async function checkGrid(pattern) {
|
|
let length = pattern.split(",").length;
|
|
|
|
await ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
|
|
{ length, pattern }, async function(args) {
|
|
let grid = content.wrappedJSObject.gGrid;
|
|
|
|
let sites = grid.sites.slice(0, args.length);
|
|
let foundPattern = sites.map(function(aSite) {
|
|
if (!aSite)
|
|
return "";
|
|
|
|
let pinned = aSite.isPinned();
|
|
let hasPinnedAttr = aSite.node.hasAttribute("pinned");
|
|
|
|
if (pinned != hasPinnedAttr)
|
|
ok(false, "invalid state (site.isPinned() != site[pinned])");
|
|
|
|
return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
|
|
});
|
|
|
|
Assert.equal(foundPattern, args.pattern, "grid status = " + args.pattern);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Blocks a site from the grid.
|
|
* @param aIndex The cell index.
|
|
*/
|
|
function blockCell(aIndex) {
|
|
return new Promise(resolve => {
|
|
whenPagesUpdated().then(resolve);
|
|
performOnCell(aIndex, cell => {
|
|
return cell.site.block();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pins a site on a given position.
|
|
* @param aIndex The cell index.
|
|
* @param aPinIndex The index the defines where the site should be pinned.
|
|
*/
|
|
function pinCell(aIndex) {
|
|
performOnCell(aIndex, cell => {
|
|
cell.site.pin();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Unpins the given cell's site.
|
|
* @param aIndex The cell index.
|
|
*/
|
|
function unpinCell(aIndex) {
|
|
return new Promise(resolve => {
|
|
whenPagesUpdated().then(resolve);
|
|
performOnCell(aIndex, cell => {
|
|
cell.site.unpin();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Simulates a drag and drop operation. Instead of rearranging a site that is
|
|
* is already contained in the newtab grid, this is used to simulate dragging
|
|
* an external link onto the grid e.g. the text from the URL bar.
|
|
* @param aDestIndex The cell index of the drop target.
|
|
*/
|
|
async function simulateExternalDrop(aDestIndex) {
|
|
let pagesUpdatedPromise = whenPagesUpdated();
|
|
|
|
await ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aDestIndex, async function(dropIndex) {
|
|
return new Promise(resolve => {
|
|
const url = "data:text/html;charset=utf-8," +
|
|
"<a id='link' href='http://example99.com/'>link</a>";
|
|
|
|
let doc = content.document;
|
|
let iframe = doc.createElement("iframe");
|
|
|
|
function iframeLoaded() {
|
|
let dataTransfer = new iframe.contentWindow.DataTransfer("dragstart", false);
|
|
dataTransfer.mozSetDataAt("text/x-moz-url", "http://example99.com/", 0);
|
|
|
|
let event = content.document.createEvent("DragEvent");
|
|
event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
|
|
false, false, false, false, 0, null, dataTransfer);
|
|
|
|
let target = content.gGrid.cells[dropIndex].node;
|
|
target.dispatchEvent(event);
|
|
|
|
iframe.remove();
|
|
|
|
resolve();
|
|
}
|
|
|
|
iframe.addEventListener("load", function() {
|
|
content.setTimeout(iframeLoaded, 0);
|
|
}, {once: true});
|
|
|
|
iframe.setAttribute("src", url);
|
|
iframe.style.width = "50px";
|
|
iframe.style.height = "50px";
|
|
iframe.style.position = "absolute";
|
|
iframe.style.zIndex = 50;
|
|
|
|
// the frame has to be attached to a visible element
|
|
let margin = doc.getElementById("newtab-search-container");
|
|
margin.appendChild(iframe);
|
|
});
|
|
});
|
|
|
|
await pagesUpdatedPromise;
|
|
}
|
|
|
|
/**
|
|
* Resumes testing when all pages have been updated.
|
|
*/
|
|
function whenPagesUpdated() {
|
|
return new Promise(resolve => {
|
|
let page = {
|
|
observe: _ => _,
|
|
|
|
update() {
|
|
NewTabUtils.allPages.unregister(this);
|
|
executeSoon(resolve);
|
|
}
|
|
};
|
|
|
|
NewTabUtils.allPages.register(page);
|
|
registerCleanupFunction(function() {
|
|
NewTabUtils.allPages.unregister(page);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Waits for the response to the page's initial search state request.
|
|
*/
|
|
function whenSearchInitDone() {
|
|
return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, async function() {
|
|
return new Promise(resolve => {
|
|
if (content.gSearch) {
|
|
let searchController = content.gSearch._contentSearchController;
|
|
if (searchController.defaultEngine) {
|
|
resolve();
|
|
return;
|
|
}
|
|
}
|
|
|
|
let eventName = "ContentSearchService";
|
|
content.addEventListener(eventName, function onEvent(event) {
|
|
if (event.detail.type == "State") {
|
|
content.removeEventListener(eventName, onEvent);
|
|
let resolver = function() {
|
|
// Wait for the search controller to receive the event, then resolve.
|
|
if (content.gSearch._contentSearchController.defaultEngine) {
|
|
resolve();
|
|
}
|
|
};
|
|
content.setTimeout(resolver, 0);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Changes the newtab customization option and waits for the panel to open and close
|
|
*
|
|
* @param {string} aTheme
|
|
* Can be any of("blank"|"classic"|"enhanced")
|
|
*/
|
|
function customizeNewTabPage(aTheme) {
|
|
return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aTheme, async function(contentTheme) {
|
|
|
|
let document = content.document;
|
|
let panel = document.getElementById("newtab-customize-panel");
|
|
let customizeButton = document.getElementById("newtab-customize-button");
|
|
|
|
function panelOpened(opened) {
|
|
return new Promise( (resolve) => {
|
|
let options = {attributes: true, oldValue: true};
|
|
let observer = new content.MutationObserver(function(mutations) {
|
|
mutations.forEach(function(mutation) {
|
|
document.getElementById("newtab-customize-" + contentTheme).click();
|
|
observer.disconnect();
|
|
if (opened == panel.hasAttribute("open")) {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
observer.observe(panel, options);
|
|
});
|
|
}
|
|
|
|
let opened = panelOpened(true);
|
|
customizeButton.click();
|
|
await opened;
|
|
|
|
let closed = panelOpened(false);
|
|
customizeButton.click();
|
|
await closed;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reports presence of a scrollbar
|
|
*/
|
|
function hasScrollbar() {
|
|
return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, async function() {
|
|
let docElement = content.document.documentElement;
|
|
return docElement.scrollHeight > docElement.clientHeight;
|
|
});
|
|
}
|