Commit f0c297a1 authored by Olivier Yiptong's avatar Olivier Yiptong
Browse files

Bug 1210940 - New Browser Component: Newtab r=Mardak

--HG--
extra : commitid : Dnaq9WxbL5V
parent 871f8e88
......@@ -13,6 +13,7 @@ DIRS += [
'feeds',
'loop',
'migration',
'newtab',
'places',
'pocket',
'preferences',
......
......@@ -14,10 +14,15 @@ Components.utils.import("resource://gre/modules/Services.jsm");
this.NewTabURL = {
_url: "about:newtab",
_remoteUrl: "about:remote-newtab",
_overridden: false,
get: function() {
return this._url;
let output = this._url;
if (Services.prefs.getBoolPref("browser.newtabpage.remote")) {
output = this._remoteUrl;
}
return output;
},
get overridden() {
......
/* 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/. */
/* globals Services, XPCOMUtils, RemotePages, RemoteNewTabLocation, RemoteNewTabUtils, Task */
/* globals BackgroundPageThumbs, PageThumbs, RemoteDirectoryLinksProvider */
/* exported RemoteAboutNewTab */
"use strict";
let Ci = Components.interfaces;
let Cu = Components.utils;
const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
this.EXPORTED_SYMBOLS = ["RemoteAboutNewTab"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.importGlobalProperties(["URL"]);
XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
"resource://gre/modules/RemotePageManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabUtils",
"resource:///modules/RemoteNewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BackgroundPageThumbs",
"resource://gre/modules/BackgroundPageThumbs.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
"resource://gre/modules/PageThumbs.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteDirectoryLinksProvider",
"resource:///modules/RemoteDirectoryLinksProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabLocation",
"resource:///modules/RemoteNewTabLocation.jsm");
let RemoteAboutNewTab = {
pageListener: null,
/**
* Initialize the RemotePageManager and add all message listeners for this page
*/
init: function() {
this.pageListener = new RemotePages("about:remote-newtab");
this.pageListener.addMessageListener("NewTab:InitializeGrid", this.initializeGrid.bind(this));
this.pageListener.addMessageListener("NewTab:UpdateGrid", this.updateGrid.bind(this));
this.pageListener.addMessageListener("NewTab:CaptureBackgroundPageThumbs",
this.captureBackgroundPageThumb.bind(this));
this.pageListener.addMessageListener("NewTab:PageThumbs", this.createPageThumb.bind(this));
this.pageListener.addMessageListener("NewTabFrame:GetInit", this.initContentFrame.bind(this));
this._addObservers();
},
/**
* Initializes the grid for the first time when the page loads.
* Fetch all the links and send them down to the child to populate
* the grid with.
*
* @param {Object} message
* A RemotePageManager message.
*/
initializeGrid: function(message) {
RemoteNewTabUtils.links.populateCache(() => {
message.target.sendAsyncMessage("NewTab:InitializeLinks", {
links: RemoteNewTabUtils.links.getLinks(),
enhancedLinks: this.getEnhancedLinks(),
});
});
},
/**
* Inits the content iframe with the newtab location
*/
initContentFrame: function(message) {
message.target.sendAsyncMessage("NewTabFrame:Init", {
href: RemoteNewTabLocation.href,
origin: RemoteNewTabLocation.origin
});
},
/**
* Updates the grid by getting a new set of links.
*
* @param {Object} message
* A RemotePageManager message.
*/
updateGrid: function(message) {
message.target.sendAsyncMessage("NewTab:UpdateLinks", {
links: RemoteNewTabUtils.links.getLinks(),
enhancedLinks: this.getEnhancedLinks(),
});
},
/**
* Captures the site's thumbnail in the background, then attemps to show the thumbnail.
*
* @param {Object} message
* A RemotePageManager message with the following data:
*
* link (Object):
* A link object that contains:
*
* baseDomain (String)
* blockState (Boolean)
* frecency (Integer)
* lastVisiteDate (Integer)
* pinState (Boolean)
* title (String)
* type (String)
* url (String)
*/
captureBackgroundPageThumb: Task.async(function* (message) {
try {
yield BackgroundPageThumbs.captureIfMissing(message.data.link.url);
this.createPageThumb(message);
} catch (err) {
Cu.reportError("error: " + err);
}
}),
/**
* Creates the thumbnail to display for each site based on the unique URL
* of the site and it's type (regular or enhanced). If the thumbnail is of
* type "regular", we create a blob and send that down to the child. If the
* thumbnail is of type "enhanced", get the file path for the URL and create
* and enhanced URI that will be sent down to the child.
*
* @param {Object} message
* A RemotePageManager message with the following data:
*
* link (Object):
* A link object that contains:
*
* baseDomain (String)
* blockState (Boolean)
* frecency (Integer)
* lastVisiteDate (Integer)
* pinState (Boolean)
* title (String)
* type (String)
* url (String)
*/
createPageThumb: function(message) {
let imgSrc = PageThumbs.getThumbnailURL(message.data.link.url);
let doc = Services.appShell.hiddenDOMWindow.document;
let img = doc.createElementNS(XHTML_NAMESPACE, "img");
let canvas = doc.createElementNS(XHTML_NAMESPACE, "canvas");
let enhanced = Services.prefs.getBoolPref("browser.newtabpage.enhanced");
img.onload = function(e) { // jshint ignore:line
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0, this.naturalWidth, this.naturalHeight);
canvas.toBlob(function(blob) {
let host = new URL(message.data.link.url).host;
RemoteAboutNewTab.pageListener.sendAsyncMessage("NewTab:RegularThumbnailURI", {
thumbPath: "/pagethumbs/" + host,
enhanced,
url: message.data.link.url,
blob,
});
});
};
img.src = imgSrc;
},
/**
* Get the set of enhanced links (if any) from the Directory Links Provider.
*/
getEnhancedLinks: function() {
let enhancedLinks = [];
for (let link of RemoteNewTabUtils.links.getLinks()) {
if (link) {
enhancedLinks.push(RemoteDirectoryLinksProvider.getEnhancedLink(link));
}
}
return enhancedLinks;
},
/**
* Listens for a preference change or session purge for all pages and sends
* a message to update the pages that are open. If a session purge occured,
* also clear the links cache and update the set of links to display, as they
* may have changed, then proceed with the page update.
*/
observe: function(aSubject, aTopic, aData) { // jshint ignore:line
let extraData;
let refreshPage = false;
if (aTopic === "browser:purge-session-history") {
RemoteNewTabUtils.links.resetCache();
RemoteNewTabUtils.links.populateCache(() => {
this.pageListener.sendAsyncMessage("NewTab:UpdateLinks", {
links: RemoteNewTabUtils.links.getLinks(),
enhancedLinks: this.getEnhancedLinks(),
});
});
}
if (extraData !== undefined || aTopic === "page-thumbnail:create") {
if (aTopic !== "page-thumbnail:create") {
// Change the topic for enhanced and enabled observers.
aTopic = aData;
}
this.pageListener.sendAsyncMessage("NewTab:Observe", {topic: aTopic, data: extraData});
}
},
/**
* Add all observers that about:newtab page must listen for.
*/
_addObservers: function() {
Services.obs.addObserver(this, "page-thumbnail:create", true);
Services.obs.addObserver(this, "browser:purge-session-history", true);
},
/**
* Remove all observers on the page.
*/
_removeObservers: function() {
Services.obs.removeObserver(this, "page-thumbnail:create");
Services.obs.removeObserver(this, "browser:purge-session-history");
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
uninit: function() {
this._removeObservers();
this.pageListener.destroy();
this.pageListener = null;
},
};
This diff is collapsed.
/* globals Services */
"use strict";
this.EXPORTED_SYMBOLS = ["RemoteNewTabLocation"];
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.importGlobalProperties(["URL"]);
// TODO: will get dynamically set in bug 1210478
const DEFAULT_PAGE_LOCATION = "https://newtab.cdn.mozilla.net/v0/nightly/en-US/index.html";
this.RemoteNewTabLocation = {
_url: new URL(DEFAULT_PAGE_LOCATION),
_overridden: false,
get href() {
return this._url.href;
},
get origin() {
return this._url.origin;
},
get overridden() {
return this._overridden;
},
override: function(newURL) {
this._url = new URL(newURL);
this._overridden = true;
Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
this._url.href);
},
reset: function() {
this._url = new URL(DEFAULT_PAGE_LOCATION);
this._overridden = false;
Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
this._url.href);
}
};
This diff is collapsed.
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
XPCSHELL_TESTS_MANIFESTS += [
'tests/xpcshell/xpcshell.ini',
]
EXTRA_JS_MODULES += [
'NewTabURL.jsm',
'RemoteAboutNewTab.jsm',
'RemoteDirectoryLinksProvider.jsm',
'RemoteNewTabLocation.jsm',
'RemoteNewTabUtils.jsm',
]
[DEFAULT]
support-files =
dummy_page.html
[browser_remotenewtab_pageloads.js]
/* globals XPCOMUtils, Task, RemoteAboutNewTab, RemoteNewTabLocation, ok */
"use strict";
let Cu = Components.utils;
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabLocation",
"resource:///modules/RemoteNewTabLocation.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteAboutNewTab",
"resource:///modules/RemoteAboutNewTab.jsm");
const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/dummy_page.html";
const NEWTAB_URL = "about:remote-newtab";
let tests = [];
/*
* Tests that:
* 1. overriding the RemoteNewTabPageLocation url causes a remote newtab page
* to load with the new url.
* 2. Messages pass between remote page <--> newTab.js <--> RemoteAboutNewTab.js
*/
tests.push(Task.spawn(function* testMessage() {
yield new Promise(resolve => {
RemoteAboutNewTab.pageListener.addMessageListener("NewTab:testMessage", () => {
ok(true, "message received");
resolve();
});
});
}));
add_task(function* open_newtab() {
RemoteNewTabLocation.override(TEST_URL);
ok(RemoteNewTabLocation.href === TEST_URL, "RemoteNewTabLocation has been overridden");
let tabOptions = {
gBrowser,
url: NEWTAB_URL,
};
for (let test of tests) {
yield BrowserTestUtils.withNewTab(tabOptions, function* (browser) { // jshint ignore:line
yield test;
}); // jshint ignore:line
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>Dummy Page</p>
<script type="text/javascript;version=1.8">
document.addEventListener("NewTabCommandReady", function readyCmd() {
document.removeEventListener("NewTabCommandReady", readyCmd);
let event = new CustomEvent("NewTabCommand", {
detail: {
command: "NewTab:testMessage"
}
});
document.dispatchEvent(event);
});
</script>
</body>
</html>
......@@ -20,6 +20,11 @@ add_task(function* () {
yield notificationPromise;
Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
Assert.equal(NewTabURL.get(), "about:newtab", "Newtab URL should be the about:newtab");
// change newtab page to remote
Services.prefs.setBoolPref("browser.newtabpage.remote", true);
Assert.equal(NewTabURL.get(), "about:remote-newtab", "Newtab URL should be the about:remote-newtab");
Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
});
function promiseNewtabURLNotification(aNewURL) {
......
/* globals ok, equal, RemoteNewTabLocation, Services */
"use strict";
Components.utils.import("resource:///modules/RemoteNewTabLocation.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.importGlobalProperties(["URL"]);
add_task(function* () {
var notificationPromise;
let defaultHref = RemoteNewTabLocation.href;
ok(RemoteNewTabLocation.href, "Default location has an href");
ok(RemoteNewTabLocation.origin, "Default location has an origin");
ok(!RemoteNewTabLocation.overridden, "Default location is not overridden");
let testURL = new URL("https://example.com/");
notificationPromise = changeNotificationPromise(testURL.href);
RemoteNewTabLocation.override(testURL.href);
yield notificationPromise;
ok(RemoteNewTabLocation.overridden, "Remote location should be overridden");
equal(RemoteNewTabLocation.href, testURL.href, "Remote href should be the custom URL");
equal(RemoteNewTabLocation.origin, testURL.origin, "Remote origin should be the custom URL");
notificationPromise = changeNotificationPromise(defaultHref);
RemoteNewTabLocation.reset();
yield notificationPromise;
ok(!RemoteNewTabLocation.overridden, "Newtab URL should not be overridden");
equal(RemoteNewTabLocation.href, defaultHref, "Remote href should be reset");
});
function changeNotificationPromise(aNewURL) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint ignore:line
Services.obs.removeObserver(observer, aTopic);
equal(aData, aNewURL, "remote-new-tab-location-changed data should be new URL.");
resolve();
}, "remote-new-tab-location-changed", false);
});
}
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// See also browser/base/content/test/newtab/.
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource:///modules/RemoteNewTabUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function run_test() {
run_next_test();
}
add_task(function validCacheMidPopulation() {
let expectedLinks = makeLinks(0, 3, 1);
let provider = new TestProvider(done => done(expectedLinks));
provider.maxNumLinks = expectedLinks.length;
RemoteNewTabUtils.initWithoutProviders();
RemoteNewTabUtils.links.addProvider(provider);
let promise = new Promise(resolve => RemoteNewTabUtils.links.populateCache(resolve));
// isTopSiteGivenProvider() and getProviderLinks() should still return results
// even when cache is empty or being populated.
do_check_false(RemoteNewTabUtils.isTopSiteGivenProvider("example1.com", provider));
do_check_links(RemoteNewTabUtils.getProviderLinks(provider), []);
yield promise;
// Once the cache is populated, we get the expected results
do_check_true(RemoteNewTabUtils.isTopSiteGivenProvider("example1.com", provider));
do_check_links(RemoteNewTabUtils.getProviderLinks(provider), expectedLinks);
RemoteNewTabUtils.links.removeProvider(provider);
});
add_task(function notifyLinkDelete() {
let expectedLinks = makeLinks(0, 3, 1);
let provider = new TestProvider(done => done(expectedLinks));
provider.maxNumLinks = expectedLinks.length;
RemoteNewTabUtils.initWithoutProviders();
RemoteNewTabUtils.links.addProvider(provider);
yield new Promise(resolve => RemoteNewTabUtils.links.populateCache(resolve));
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks);
// Remove a link.
let removedLink = expectedLinks[2];
provider.notifyLinkChanged(removedLink, 2, true);
let links = RemoteNewTabUtils.links._providers.get(provider);
// Check that sortedLinks is correctly updated.
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks.slice(0, 2));
// Check that linkMap is accurately updated.
do_check_eq(links.linkMap.size, 2);
do_check_true(links.linkMap.get(expectedLinks[0].url));
do_check_true(links.linkMap.get(expectedLinks[1].url));
do_check_false(links.linkMap.get(removedLink.url));
// Check that siteMap is correctly updated.
do_check_eq(links.siteMap.size, 2);
do_check_true(links.siteMap.has(RemoteNewTabUtils.extractSite(expectedLinks[0].url)));
do_check_true(links.siteMap.has(RemoteNewTabUtils.extractSite(expectedLinks[1].url)));
do_check_false(links.siteMap.has(RemoteNewTabUtils.extractSite(removedLink.url)));
RemoteNewTabUtils.links.removeProvider(provider);
});
add_task(function populatePromise() {
let count = 0;
let expectedLinks = makeLinks(0, 10, 2);
let getLinksFcn = Task.async(function* (callback) {
//Should not be calling getLinksFcn twice
count++;
do_check_eq(count, 1);
yield Promise.resolve();
callback(expectedLinks);
});
let provider = new TestProvider(getLinksFcn);
RemoteNewTabUtils.initWithoutProviders();
RemoteNewTabUtils.links.addProvider(provider);
RemoteNewTabUtils.links.populateProviderCache(provider, () => {});
RemoteNewTabUtils.links.populateProviderCache(provider, () => {
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks);
RemoteNewTabUtils.links.removeProvider(provider);
});
});
add_task(function isTopSiteGivenProvider() {
let expectedLinks = makeLinks(0, 10, 2);
// The lowest 2 frecencies have the same base domain.
expectedLinks[expectedLinks.length - 2].url = expectedLinks[expectedLinks.length - 1].url + "Test";
let provider = new TestProvider(done => done(expectedLinks));
provider.maxNumLinks = expectedLinks.length;
RemoteNewTabUtils.initWithoutProviders();
RemoteNewTabUtils.links.addProvider(provider);
yield new Promise(resolve => RemoteNewTabUtils.links.populateCache(resolve));
do_check_eq(RemoteNewTabUtils.isTopSiteGivenProvider("example2.com", provider), true);
do_check_eq(RemoteNewTabUtils.isTopSiteGivenProvider("example1.com", provider), false);
// Push out frecency 2 because the maxNumLinks is reached when adding frecency 3
let newLink = makeLink(3);
provider.notifyLinkChanged(newLink);
// There is still a frecent url with example2 domain, so it's still frecent.
do_check_eq(RemoteNewTabUtils.isTopSiteGivenProvider("example3.com", provider), true);
do_check_eq(RemoteNewTabUtils.isTopSiteGivenProvider("example2.com", provider), true);
// Push out frecency 3
newLink = makeLink(5);
provider.notifyLinkChanged(newLink);
// Push out frecency 4
newLink = makeLink(9);
provider.notifyLinkChanged(newLink);
// Our count reached 0 for the example2.com domain so it's no longer a frecent site.
do_check_eq(RemoteNewTabUtils.isTopSiteGivenProvider("example5.com", provider), true);
do_check_eq(RemoteNewTabUtils.isTopSiteGivenProvider("example2.com", provider), false);
RemoteNewTabUtils.links.removeProvider(provider);
});
add_task(function multipleProviders() {
// Make each provider generate RemoteNewTabUtils.links.maxNumLinks links to check