gecko/browser/base/content/test/newtab/head.js

391 lines
10 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);
let tmp = {};
Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://browser/content/sanitize.js", tmp);
let {NewTabUtils, Sanitizer} = tmp;
let uri = Services.io.newURI("about:newtab", null, null);
let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
let gWindow = window;
registerCleanupFunction(function () {
while (gWindow.gBrowser.tabs.length > 1)
gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
});
/**
* Provide the default test function to start our test runner.
*/
function test() {
TestRunner.run();
}
/**
* The test runner that controls the execution flow of our tests.
*/
let TestRunner = {
/**
* Starts the test runner.
*/
run: function () {
waitForExplicitFinish();
this._iter = runTests();
this.next();
},
/**
* Runs the next available test or finishes if there's no test left.
*/
next: function () {
try {
TestRunner._iter.next();
} catch (e if e instanceof StopIteration) {
TestRunner.finish();
}
},
/**
* Finishes all tests and cleans up.
*/
finish: function () {
function cleanupAndFinish() {
clearHistory(function () {
whenPagesUpdated(finish);
NewTabUtils.restore();
});
}
let callbacks = NewTabUtils.links._populateCallbacks;
let numCallbacks = callbacks.length;
if (numCallbacks)
callbacks.splice(0, numCallbacks, cleanupAndFinish);
else
cleanupAndFinish();
}
};
/**
* Returns the selected tab's content window.
* @return The content window.
*/
function getContentWindow() {
return gWindow.gBrowser.selectedBrowser.contentWindow;
}
/**
* Returns the selected tab's content document.
* @return The content document.
*/
function getContentDocument() {
return gWindow.gBrowser.selectedBrowser.contentDocument;
}
/**
* Returns the newtab grid of the selected tab.
* @return The newtab grid.
*/
function getGrid() {
return getContentWindow().gGrid;
}
/**
* Returns the cell at the given index of the selected tab's newtab grid.
* @param aIndex The cell index.
* @return The newtab cell.
*/
function getCell(aIndex) {
return getGrid().cells[aIndex];
}
/**
* Allows to provide a list of links that is used to construct the grid.
* @param aLinksPattern the pattern (see below)
*
* Example: setLinks("1,2,3")
* Result: [{url: "http://example.com/#1", title: "site#1"},
* {url: "http://example.com/#2", title: "site#2"}
* {url: "http://example.com/#3", title: "site#3"}]
*/
function setLinks(aLinks) {
let links = aLinks;
if (typeof links == "string") {
links = aLinks.split(/\s*,\s*/).map(function (id) {
return {url: "http://example.com/#" + id, 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 () {
clearHistory(function () {
fillHistory(links, function () {
NewTabUtils.links.populateCache(function () {
NewTabUtils.allPages.update();
TestRunner.next();
}, true);
});
});
});
}
function clearHistory(aCallback) {
Services.obs.addObserver(function observe(aSubject, aTopic, aData) {
Services.obs.removeObserver(observe, aTopic);
executeSoon(aCallback);
}, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
PlacesUtils.history.removeAllPages();
}
function fillHistory(aLinks, aCallback) {
let numLinks = aLinks.length;
let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
for (let link of aLinks.reverse()) {
let place = {
uri: makeURI(link.url),
title: link.title,
visits: [{visitDate: Date.now() * 1000, transitionType: transitionLink}]
};
PlacesUtils.asyncHistory.updatePlaces(place, {
handleError: function () ok(false, "couldn't add visit to history"),
handleResult: function () {},
handleCompletion: function () {
if (--numLinks == 0)
aCallback();
}
});
}
}
/**
* 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://example.com/#3' is pinned in the first cell. 'http://example.com/#1' 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.com/#" + id, title: "site#" + id};
});
}
let string = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
string.data = JSON.stringify(links);
Services.prefs.setComplexValue("browser.newtabpage.pinned",
Ci.nsISupportsString, string);
NewTabUtils.pinnedLinks.resetCache();
NewTabUtils.allPages.update();
}
/**
* Restore the grid state.
*/
function restore() {
whenPagesUpdated();
NewTabUtils.restore();
}
/**
* Creates a new tab containing 'about:newtab'.
*/
function addNewTabPageTab() {
let tab = gWindow.gBrowser.selectedTab = gWindow.gBrowser.addTab("about:newtab");
let browser = tab.linkedBrowser;
function whenNewTabLoaded() {
if (NewTabUtils.allPages.enabled) {
// Continue when the link cache has been populated.
NewTabUtils.links.populateCache(function () {
executeSoon(TestRunner.next);
});
} else {
// It's important that we call next() asynchronously.
// 'yield addNewTabPageTab()' would fail if next() is called
// synchronously because the iterator is already executing.
executeSoon(TestRunner.next);
}
}
// The new tab page might have been preloaded in the background.
if (browser.contentDocument.readyState == "complete") {
whenNewTabLoaded();
return;
}
// Wait for the new tab page to be loaded.
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
whenNewTabLoaded();
}, true);
}
/**
* Compares the current grid arrangement with the given pattern.
* @param the pattern (see below)
* @param the array of sites to compare with (optional)
*
* Example: checkGrid("3p,2,,1p")
* Result: We expect the first cell to contain the pinned site 'http://example.com/#3'.
* The second cell contains 'http://example.com/#2'. The third cell is empty.
* The fourth cell contains the pinned site 'http://example.com/#4'.
*/
function checkGrid(aSitesPattern, aSites) {
let length = aSitesPattern.split(",").length;
let sites = (aSites || getGrid().sites).slice(0, length);
let current = sites.map(function (aSite) {
if (!aSite)
return "";
let pinned = aSite.isPinned();
let pinButton = aSite.node.querySelector(".newtab-control-pin");
let hasPinnedAttr = pinButton.hasAttribute("pinned");
if (pinned != hasPinnedAttr)
ok(false, "invalid state (site.isPinned() != site[pinned])");
return aSite.url.replace(/^http:\/\/example\.com\/#(\d+)$/, "$1") + (pinned ? "p" : "");
});
is(current, aSitesPattern, "grid status = " + aSitesPattern);
}
/**
* Blocks a site from the grid.
* @param aIndex The cell index.
*/
function blockCell(aIndex) {
whenPagesUpdated();
getCell(aIndex).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, aPinIndex) {
getCell(aIndex).site.pin(aPinIndex);
}
/**
* Unpins the given cell's site.
* @param aIndex The cell index.
*/
function unpinCell(aIndex) {
whenPagesUpdated();
getCell(aIndex).site.unpin();
}
/**
* Simulates a drop and drop operation.
* @param aDropIndex The cell index of the drop target.
* @param aDragIndex The cell index containing the dragged site (optional).
*/
function simulateDrop(aDropIndex, aDragIndex) {
let draggedSite;
let {gDrag: drag, gDrop: drop} = getContentWindow();
let event = createDragEvent("drop", "http://example.com/#99\nblank");
if (typeof aDragIndex != "undefined")
draggedSite = getCell(aDragIndex).site;
if (draggedSite)
drag.start(draggedSite, event);
whenPagesUpdated();
drop.drop(getCell(aDropIndex), event);
if (draggedSite)
drag.end(draggedSite);
}
/**
* Sends a custom drag event to a given DOM element.
* @param aEventType The drag event's type.
* @param aTarget The DOM element that the event is dispatched to.
* @param aData The event's drag data (optional).
*/
function sendDragEvent(aEventType, aTarget, aData) {
let event = createDragEvent(aEventType, aData);
let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.dispatchDOMEventViaPresShell(aTarget, event, true);
}
/**
* Creates a custom drag event.
* @param aEventType The drag event's type.
* @param aData The event's drag data (optional).
* @return The drag event.
*/
function createDragEvent(aEventType, aData) {
let dataTransfer = {
mozUserCancelled: false,
setData: function () null,
setDragImage: function () null,
getData: function () aData,
types: {
contains: function (aType) aType == "text/x-moz-url"
},
mozGetDataAt: function (aType, aIndex) {
if (aIndex || aType != "text/x-moz-url")
return null;
return aData;
}
};
let event = getContentDocument().createEvent("DragEvents");
event.initDragEvent(aEventType, true, true, getContentWindow(), 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
return event;
}
/**
* Resumes testing when all pages have been updated.
*/
function whenPagesUpdated(aCallback) {
let page = {
update: function () {
NewTabUtils.allPages.unregister(this);
executeSoon(aCallback || TestRunner.next);
}
};
NewTabUtils.allPages.register(page);
registerCleanupFunction(function () {
NewTabUtils.allPages.unregister(page);
});
}