fune/browser/base/content/test/newtab/head.js
Dorel Luca eb65c24c7b Backed out 8 changesets (bug 1412456) for ESlint failure on browser_urlbarKeepStateAcrossTabSwitches.js:13:49 r=backout on a CLOSED TREE
Backed out changeset 0e88de036c55 (bug 1412456)
Backed out changeset 49b93f807db0 (bug 1412456)
Backed out changeset 039e980b7dc6 (bug 1412456)
Backed out changeset c7698410ddbd (bug 1412456)
Backed out changeset e56a1ba26b7c (bug 1412456)
Backed out changeset 0c4506e124ac (bug 1412456)
Backed out changeset a7aec2ce903b (bug 1412456)
Backed out changeset 3e9fb71f1e8e (bug 1412456)
2017-12-07 07:09:33 +02:00

547 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";
const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
const PREF_NEWTAB_ACTIVITY_STREAM = "browser.newtabpage.activity-stream.enabled";
Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
Services.prefs.setBoolPref(PREF_NEWTAB_ACTIVITY_STREAM, false);
// 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);
var tmp = {};
Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp);
Cu.import("resource://testing-common/PlacesTestUtils.jsm", tmp);
Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", tmp);
var {NewTabUtils, Sanitizer, DirectoryLinksProvider, PlacesTestUtils} = tmp;
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
var oldSize = {};
Object.keys(requiredSize).forEach(prop => {
info([prop, gBrowser.contentWindow[prop], requiredSize[prop]]);
if (gBrowser.contentWindow[prop] < requiredSize[prop]) {
oldSize[prop] = gBrowser.contentWindow[prop];
info("Changing browser " + prop + " from " + oldSize[prop] + " to " +
requiredSize[prop]);
gBrowser.contentWindow[prop] = requiredSize[prop];
}
});
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 < gBrowser.contentWindow.outerHeight) {
info("Warning: Browser outer height is now " +
gBrowser.contentWindow.outerHeight + ", which is larger than the " +
"available screen height, " + screenHeight +
". That may cause problems.");
}
if (screenWidth < gBrowser.contentWindow.outerWidth) {
info("Warning: Browser outer width is now " +
gBrowser.contentWindow.outerWidth + ", 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]);
Object.keys(oldSize).forEach(prop => {
if (oldSize[prop]) {
gBrowser.contentWindow[prop] = oldSize[prop];
}
});
Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
Services.prefs.clearUserPref(PREF_NEWTAB_ACTIVITY_STREAM);
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
return watchLinksChangeOnce();
});
function pushPrefs(...aPrefs) {
return SpecialPowers.pushPrefEnv({"set": aPrefs});
}
/**
* Resolves promise when directory links are downloaded and written to disk
*/
function watchLinksChangeOnce() {
return new Promise(resolve => {
let observer = {
onManyLinksChanged: () => {
DirectoryLinksProvider.removeObserver(observer);
resolve();
}
};
observer.onDownloadFail = observer.onManyLinksChanged;
DirectoryLinksProvider.addObserver(observer);
});
}
add_task(async function setup() {
registerCleanupFunction(function() {
return new Promise(resolve => {
function cleanupAndFinish() {
PlacesTestUtils.clearHistory().then(() => {
whenPagesUpdated().then(resolve);
NewTabUtils.restore();
});
}
let callbacks = NewTabUtils.links._populateCallbacks;
let numCallbacks = callbacks.length;
if (numCallbacks)
callbacks.splice(0, numCallbacks, cleanupAndFinish);
else
cleanupAndFinish();
});
});
let promiseReady = (async function() {
await watchLinksChangeOnce();
await whenPagesUpdated();
})();
// Save the original directory source (which is set globally for tests)
gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
await promiseReady;
});
/** 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() {
PlacesTestUtils.clearHistory().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;
});
}