Backed out 4 changesets (bug 1218996) for talos svg failures CLOSED TREE

Backed out changeset ba750628c4f3 (bug 1218996)
Backed out changeset 2205cce34824 (bug 1218996)
Backed out changeset 9410cbc0545e (bug 1218996)
Backed out changeset 7839222071ac (bug 1218996)

--HG--
extra : commitid : Ju4TBTVoAIr
This commit is contained in:
Wes Kocher 2015-12-23 11:09:05 -08:00
Родитель 12b752a241
Коммит 1f00d8d838
34 изменённых файлов: 2219 добавлений и 490 удалений

Просмотреть файл

@ -1348,8 +1348,10 @@ pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/
// endpoint to send newtab click and view pings
pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/");
// activates the remote-hosted newtab page
#ifndef RELEASE_BUILD
// if true, it activates the remote-hosted newtab page
pref("browser.newtabpage.remote", false);
#endif
// Enable the DOM fullscreen API.
pref("full-screen-api.enabled", true);
@ -1636,4 +1638,4 @@ pref("toolkit.pageThumbs.minHeight", 190);
#ifdef NIGHTLY_BUILD
// Enable speech synthesis, only Nightly for now
pref("media.webspeech.synth.enabled", true);
#endif
#endif

Просмотреть файл

@ -11,7 +11,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NotificationDB.jsm");
Cu.import("resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
@ -56,9 +55,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
"resource:///modules/Pocket.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gAboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
// Can't use XPCOMUtils for these because the scripts try to define the variables
// on window, and so the defineProperty inside defineLazyGetter fails.
@ -2365,11 +2361,8 @@ function URLBarSetURI(aURI) {
} catch (e) {}
// Replace initial page URIs with an empty string
// 1. only if there's no opener (bug 370555).
// 2. if remote newtab is enabled and it's the default remote newtab page
let defaultRemoteURL = gAboutNewTabService.remoteEnabled &&
uri.spec === gAboutNewTabService.newTabURL;
if (gInitialPages.includes(uri.spec) || defaultRemoteURL)
// only if there's no opener (bug 370555).
if (gInitialPages.indexOf(uri.spec) != -1)
value = gBrowser.selectedBrowser.hasContentOpener ? uri.spec : "";
else
value = losslessDecodeURI(uri);
@ -3566,9 +3559,8 @@ const BrowserSearch = {
if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
let url = gBrowser.currentURI.spec.toLowerCase();
let mm = gBrowser.selectedBrowser.messageManager;
let newTabRemoted = Services.prefs.getBoolPref("browser.newtabpage.remote");
let localNewTabEnabled = url === "about:newtab" && !newTabRemoted && NewTabUtils.allPages.enabled;
if (url === "about:home" || localNewTabEnabled) {
if (url === "about:home" ||
(url === "about:newtab" && NewTabUtils.allPages.enabled)) {
ContentSearch.focusInput(mm);
} else {
openUILinkIn("about:home", "current");

Просмотреть файл

@ -0,0 +1,23 @@
/* 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/. */
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
position: relative;
}
#remotedoc {
width: 100%;
height: 100%;
border: none;
position: absolute;
}

Просмотреть файл

@ -0,0 +1,126 @@
/* 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 XPCOMUtils, Components, sendAsyncMessage, addMessageListener, removeMessageListener,
Services, PrivateBrowsingUtils*/
"use strict";
const {utils: Cu, interfaces: Ci} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
(function() {
let remoteNewTabLocation;
let remoteIFrame;
/**
* Attempts to handle commands sent from the remote IFrame within this content frame.
* Expected commands below, with data types explained.
*
* @returns {Boolean} whether or not the command was handled
* @param {String} command
* The command passed from the remote IFrame
* @param {Object} data
* Parameters to the command
*/
function handleCommand(command, data) {
let commandHandled = true;
switch (command) {
case "NewTab:UpdateTelemetryProbe":
/**
* Update a given Telemetry histogram
*
* @param {String} data.probe
* Probe name to update
* @param {Number} data.value
* Value to update histogram by
*/
Services.telemetry.getHistogramById(data.probe).add(data.value);
break;
case "NewTab:Register":
registerEvent(data.type);
break;
case "NewTab:GetInitialState":
getInitialState();
break;
default:
commandHandled = false;
}
return commandHandled;
}
function initRemotePage(initData) {
// Messages that the iframe sends the browser will be passed onto
// the privileged parent process
remoteNewTabLocation = initData;
remoteIFrame = document.querySelector("#remotedoc");
let loadHandler = () => {
if (remoteIFrame.src !== remoteNewTabLocation.href) {
return;
}
remoteIFrame.removeEventListener("load", loadHandler);
remoteIFrame.contentDocument.addEventListener("NewTabCommand", (e) => {
// If the commands are not handled within this content frame, the command will be
// passed on to main process, in RemoteAboutNewTab.jsm
let handled = handleCommand(e.detail.command, e.detail.data);
if (!handled) {
sendAsyncMessage(e.detail.command, e.detail.data);
}
});
registerEvent("NewTab:Observe");
let ev = new CustomEvent("NewTabCommandReady");
remoteIFrame.contentDocument.dispatchEvent(ev);
};
remoteIFrame.src = remoteNewTabLocation.href;
remoteIFrame.addEventListener("load", loadHandler);
}
/**
* Allows the content IFrame to register a listener to an event sent by
* the privileged parent process, in RemoteAboutNewTab.jsm
*
* @param {String} eventName
* Event name to listen to
*/
function registerEvent(eventName) {
addMessageListener(eventName, (message) => {
remoteIFrame.contentWindow.postMessage(message, remoteNewTabLocation.origin);
});
}
/**
* Sends the initial data payload to a content IFrame so it can bootstrap
*/
function getInitialState() {
let prefs = Services.prefs;
let isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
let state = {
enabled: prefs.getBoolPref("browser.newtabpage.enabled"),
enhanced: prefs.getBoolPref("browser.newtabpage.enhanced"),
rows: prefs.getIntPref("browser.newtabpage.rows"),
columns: prefs.getIntPref("browser.newtabpage.columns"),
introShown: prefs.getBoolPref("browser.newtabpage.introShown"),
windowID: window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID,
privateBrowsingMode: isPrivate
};
remoteIFrame.contentWindow.postMessage({
name: "NewTab:State",
data: state
}, remoteNewTabLocation.origin);
}
addMessageListener("NewTabFrame:Init", function loadHandler(message) {
// Everything is loaded. Initialize the New Tab Page.
removeMessageListener("NewTabFrame:Init", loadHandler);
initRemotePage(message.data);
});
sendAsyncMessage("NewTabFrame:GetInit");
}());

Просмотреть файл

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
%newTabDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&newtab.pageTitle;</title>
<link rel="stylesheet" href="chrome://browser/content/remote-newtab/newTab.css"/>
</head>
<body>
<iframe id="remotedoc"/>
<script type="text/javascript;version=1.8"
src="chrome://browser/content/remote-newtab/newTab.js">
</script>
</body>
</html>

Просмотреть файл

@ -1,40 +1,36 @@
/* 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/. */
"use strict";
/* globals
waitForExplicitFinish, whenNewWindowLoaded, whenNewTabLoaded,
executeSoon, registerCleanupFunction, finish, is
*/
/* exported test */
// This test makes sure that opening a new tab in private browsing mode opens about:privatebrowsing
function test() {
// initialization
waitForExplicitFinish();
let aboutNewTabService = Components.classes["@mozilla.org/browser/aboutnewtab-service;1"]
.getService(Components.interfaces.nsIAboutNewTabService);
let windowsToClose = [];
let newTab;
let newTabURL;
let mode;
function doTest(aIsPrivateMode, aWindow, aCallback) {
whenNewTabLoaded(aWindow, function() {
whenNewTabLoaded(aWindow, function () {
if (aIsPrivateMode) {
mode = "per window private browsing";
newTabURL = "about:privatebrowsing";
} else {
mode = "normal";
newTabURL = "about:newtab";
newTabURL = aboutNewTabService.newTabURL;
}
is(aWindow.gBrowser.currentURI.spec, newTabURL,
"URL of NewTab should be " + newTabURL + " in " + mode + " mode");
aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
aCallback();
aCallback()
});
}
};
function testOnWindow(aOptions, aCallback) {
whenNewWindowLoaded(aOptions, function(aWin) {
@ -44,9 +40,9 @@ function test() {
// call whenNewWindowLoaded() instead of testOnWindow() on your test.
executeSoon(() => aCallback(aWin));
});
}
};
// this function is called after calling finish() on the test.
// this function is called after calling finish() on the test.
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();

Просмотреть файл

@ -1,30 +1,24 @@
/* 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/. */
"use strict";
/* globals waitForExplicitFinish, executeSoon, finish, whenNewWindowLoaded, ok */
/* globals is */
/* exported test */
function test() {
//initialization
waitForExplicitFinish();
let aboutNewTabService = Components.classes["@mozilla.org/browser/aboutnewtab-service;1"]
.getService(Components.interfaces.nsIAboutNewTabService);
let newTabURL;
let testURL = "http://example.com/";
let defaultURL = aboutNewTabService.newTabURL;
let mode;
function doTest(aIsPrivateMode, aWindow, aCallback) {
openNewTab(aWindow, function() {
openNewTab(aWindow, function () {
if (aIsPrivateMode) {
mode = "per window private browsing";
newTabURL = "about:privatebrowsing";
} else {
mode = "normal";
newTabURL = "about:newtab";
newTabURL = aboutNewTabService.newTabURL;
}
// Check the new tab opened while in normal/private mode
@ -35,18 +29,18 @@ function test() {
is(aboutNewTabService.newTabURL, testURL, "Custom newtab url is set");
// Open a newtab after setting the custom newtab url
openNewTab(aWindow, function() {
openNewTab(aWindow, function () {
is(aWindow.gBrowser.selectedBrowser.currentURI.spec, testURL,
"URL of NewTab should be the custom url");
// Clear the custom url.
aboutNewTabService.resetNewTabURL();
is(aboutNewTabService.newTabURL, defaultURL, "No custom newtab url is set");
is(aboutNewTabService.newTabURL, "about:newtab", "No custom newtab url is set");
aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
aWindow.close();
aCallback();
aCallback()
});
});
}
@ -78,7 +72,7 @@ function openNewTab(aWindow, aCallback) {
aWindow.BrowserOpenTab();
let browser = aWindow.gBrowser.selectedBrowser;
if (browser.contentDocument.readyState === "complete") {
if (browser.contentDocument.readyState == "complete") {
executeSoon(aCallback);
return;
}

Просмотреть файл

@ -9,19 +9,13 @@
* as the nodePrincipal match the URL in the URL bar.
*/
/* globals Cc, Ci, ok, is, content, TestRunner, addNewTabPageTab, gWindow, Services, info */
/* exported runTests */
"use strict";
const ABOUT_NEWTAB_URI = "about:newtab";
const PREF_URI = "http://example.com/browser/browser/base/content/test/newtab/external_newtab.html";
var browser = null;
var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
.getService(Ci.nsIAboutNewTabService);
const ABOUT_NEWTAB_URI = "about:newtab";
const PREF_URI = "http://example.com/browser/browser/base/content/test/newtab/external_newtab.html";
const DEFAULT_URI = aboutNewTabService.newTabURL;
function testPref() {
// set the pref for about:newtab to point to an exteranl resource
aboutNewTabService.newTabURL = PREF_URI;
@ -40,7 +34,7 @@ function testPref() {
// reset to about:newtab and perform sanity check
aboutNewTabService.resetNewTabURL();
is(aboutNewTabService.newTabURL, DEFAULT_URI,
is(aboutNewTabService.newTabURL, ABOUT_NEWTAB_URI,
"sanity check: resetting the URL to about:newtab should return about:newtab");
// remove the tab and move on

Просмотреть файл

@ -20,7 +20,7 @@ this.__defineGetter__("BROWSER_NEW_TAB_URL", () => {
!aboutNewTabService.overridden) {
return "about:privatebrowsing";
}
return "about:newtab";
return aboutNewTabService.newTabURL;
});
var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";

Просмотреть файл

@ -138,6 +138,9 @@ browser.jar:
* content/browser/newtab/newTab.js (content/newtab/newTab.js)
content/browser/newtab/newTab.css (content/newtab/newTab.css)
content/browser/newtab/newTab.inadjacent.json (content/newtab/newTab.inadjacent.json)
content/browser/remote-newtab/newTab.xhtml (content/remote-newtab/newTab.xhtml)
content/browser/remote-newtab/newTab.js (content/remote-newtab/newTab.js)
content/browser/remote-newtab/newTab.css (content/remote-newtab/newTab.css)
* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
content/browser/pageinfo/pageInfo.js (content/pageinfo/pageInfo.js)
content/browser/pageinfo/pageInfo.css (content/pageinfo/pageInfo.css)

Просмотреть файл

@ -46,3 +46,5 @@ component {d8903bf6-68d5-4e97-bcd1-e4d3012f721a} nsBrowserGlue.js
#ifndef MOZ_MULET
contract @mozilla.org/content-permission/prompt;1 {d8903bf6-68d5-4e97-bcd1-e4d3012f721a}
#endif
component {97eea4bb-db50-4ae0-9147-1e5ed55b4ed5} nsBrowserGlue.js
contract @mozilla.org/browser/aboutnewtab-service;1 {97eea4bb-db50-4ae0-9147-1e5ed55b4ed5}

Просмотреть файл

@ -86,9 +86,13 @@ static RedirEntry kRedirMap[] = {
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::ENABLE_INDEXED_DB },
// the newtab's actual URL will be determined when the channel is created
{ "newtab", "about:blank",
{ "newtab", "chrome://browser/content/newtab/newTab.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
#ifndef RELEASE_BUILD
{ "remote-newtab", "chrome://browser/content/remote-newtab/newTab.xhtml",
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
nsIAboutModule::ALLOW_SCRIPT },
#endif
{ "preferences", "chrome://browser/content/preferences/in-content/preferences.xul",
nsIAboutModule::ALLOW_SCRIPT },
{ "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
@ -173,12 +177,18 @@ AboutRedirector::NewChannel(nsIURI* aURI,
if (!strcmp(path.get(), kRedirMap[i].id)) {
nsAutoCString url;
// check if about:newtab got overridden
if (path.EqualsLiteral("newtab")) {
// let the aboutNewTabService decide where to redirect
nsCOMPtr<nsIAboutNewTabService> aboutNewTabService =
do_GetService("@mozilla.org/browser/aboutnewtab-service;1", &rv);
rv = aboutNewTabService->GetNewTabURL(url);
NS_ENSURE_SUCCESS(rv, rv);
bool overridden = false;
rv = aboutNewTabService->GetOverridden(&overridden);
NS_ENSURE_SUCCESS(rv, rv);
if (overridden) {
rv = aboutNewTabService->GetNewTabURL(url);
NS_ENSURE_SUCCESS(rv, rv);
}
}
// fall back to the specified url in the map
if (url.IsEmpty()) {

Просмотреть файл

@ -29,6 +29,7 @@ DIRS += [
DIRS += ['build']
XPIDL_SOURCES += [
'nsIAboutNewTabService.idl',
'nsIBrowserGlue.idl',
'nsIBrowserHandler.idl',
]

Просмотреть файл

@ -1,2 +0,0 @@
component {cef25b06-0ef6-4c50-a243-e69f943ef23d} aboutNewTabService.js
contract @mozilla.org/browser/aboutnewtab-service;1 {cef25b06-0ef6-4c50-a243-e69f943ef23d}

Просмотреть файл

@ -17,7 +17,6 @@ XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
// Supported prefs and data type
const gPrefsMap = new Map([
["browser.newtabpage.remote", "bool"],
["browser.newtabpage.enabled", "bool"],
["browser.newtabpage.enhanced", "bool"],
["browser.newtabpage.pinned", "str"],

Просмотреть файл

@ -0,0 +1,43 @@
/* 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/. */
"use strict";
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "NewTabURL" ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm");
const DepecationURL = "https://bugzilla.mozilla.org/show_bug.cgi?id=1204983#c89";
this.NewTabURL = {
get: function() {
Deprecated.warning("NewTabURL.get is deprecated, please query aboutNewTabService.newTabURL", DepecationURL);
return aboutNewTabService.newTabURL;
},
get overridden() {
Deprecated.warning("NewTabURL.overridden is deprecated, please query aboutNewTabService.overridden", DepecationURL);
return aboutNewTabService.overridden;
},
override: function(newURL) {
Deprecated.warning("NewTabURL.override is deprecated, please set aboutNewTabService.newTabURL", DepecationURL);
aboutNewTabService.newTabURL = newURL;
},
reset: function() {
Deprecated.warning("NewTabURL.reset is deprecated, please use aboutNewTabService.resetNewTabURL()", DepecationURL);
aboutNewTabService.resetNewTabURL();
}
};

Просмотреть файл

@ -0,0 +1,301 @@
/* 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, DirectoryLinksProvider, PlacesProvider, NewTabPrefsProvider */
/* 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, "DirectoryLinksProvider",
"resource:///modules/DirectoryLinksProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabLocation",
"resource:///modules/RemoteNewTabLocation.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesProvider",
"resource:///modules/PlacesProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
let RemoteAboutNewTab = {
pageListener: null,
/**
* Initialize the RemotePageManager and add all message listeners for this page
*/
init: function() {
RemoteNewTabLocation.init();
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:Customize", this.customize.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();
},
customize: function(message) {
if (message.data.enabled !== undefined) {
Services.prefs.setBoolPref("browser.newtabpage.enabled", message.data.enabled);
}
if (message.data.enhanced !== undefined) {
Services.prefs.setBoolPref("browser.newtabpage.enhanced", message.data.enhanced);
}
},
/**
* Notifies when history is cleared
*/
placesClearHistory: function() {
this.pageListener.sendAsyncMessage("NewTab:PlacesClearHistory");
},
/**
* Notifies when a link has changed
*/
placesLinkChanged: function(name, data) { // jshint ignore:line
this.pageListener.sendAsyncMessage("NewTab:PlacesLinkChanged", data);
},
/**
* Notifies when many links have changed
*/
placesManyLinksChanged: function() {
this.pageListener.sendAsyncMessage("NewTab:PlacesManyLinksChanged");
},
/**
* Notifies when one URL has been deleted
*/
placesDeleteURI: function(name, data) { // jshint ignore:line
this.pageListener.sendAsyncMessage("NewTab:PlacesDeleteURI", data.url);
},
/**
* 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(DirectoryLinksProvider.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;
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});
}
},
setEnabled: function(name, data) { // jshint ignore:line
this.pageListener.sendAsyncMessage("NewTab:setEnabled", data);
},
setEnhanced: function(name, data) { // jshint ignore:line
this.pageListener.sendAsyncMessage("NewTab:setEnhanced", data);
},
setPinned: function(name, data) { // jshint ignore:line
this.pageListener.sendAsyncMessage("NewTab:setPinnedLinks", data);
},
/**
* 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);
PlacesProvider.links.on("deleteURI", this.placesDeleteURI.bind(this));
PlacesProvider.links.on("clearHistory", this.placesClearHistory.bind(this));
PlacesProvider.links.on("linkChanged", this.placesLinkChanged.bind(this));
PlacesProvider.links.on("manyLinksChanged", this.placesManyLinksChanged.bind(this));
NewTabPrefsProvider.prefs.on("browser.newtabpage.enabled", this.setEnabled.bind(this));
NewTabPrefsProvider.prefs.on("browser.newtabpage.enhanced", this.setEnhanced.bind(this));
NewTabPrefsProvider.prefs.on("browser.newtabpage.pinned", this.setPinned.bind(this));
},
/**
* Remove all observers on the page.
*/
_removeObservers: function() {
Services.obs.removeObserver(this, "page-thumbnail:create");
Services.obs.removeObserver(this, "browser:purge-session-history");
PlacesProvider.links.off("deleteURI", this.placesDeleteURI);
PlacesProvider.links.off("clearHistory", this.placesClearHistory);
PlacesProvider.links.off("linkChanged", this.placesLinkChanged);
PlacesProvider.links.off("manyLinksChanged", this.placesManyLinksChanged);
NewTabPrefsProvider.prefs.off("browser.newtabpage.enabled", this.setEnabled.bind(this));
NewTabPrefsProvider.prefs.off("browser.newtabpage.enhanced", this.setEnhanced.bind(this));
NewTabPrefsProvider.prefs.off("browser.newtabpage.pinned", this.setPinned.bind(this));
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
uninit: function() {
RemoteNewTabLocation.uninit();
this._removeObservers();
this.pageListener.destroy();
this.pageListener = null;
},
};

Просмотреть файл

@ -0,0 +1,141 @@
/* globals Services, UpdateUtils, XPCOMUtils, URL, NewTabPrefsProvider, Locale */
/* exported RemoteNewTabLocation */
"use strict";
this.EXPORTED_SYMBOLS = ["RemoteNewTabLocation"];
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.importGlobalProperties(["URL"]);
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
"resource://gre/modules/Locale.jsm");
// The preference that tells whether to match the OS locale
const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
// The preference that tells what locale the user selected
const PREF_SELECTED_LOCALE = "general.useragent.locale";
const DEFAULT_PAGE_LOCATION = "https://newtab.cdn.mozilla.net/" +
"v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
const NEWTAB_VERSION = "0";
let RemoteNewTabLocation = {
/*
* Generate a default url based on locale and update channel
*/
_generateDefaultURL() {
let releaseName = this._releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
let uri = DEFAULT_PAGE_LOCATION
.replace("%VERSION%", this.version)
.replace("%LOCALE%", Locale.getLocale())
.replace("%CHANNEL%", releaseName);
return new URL(uri);
},
_url: null,
_overridden: false,
get href() {
return this._url.href;
},
get origin() {
return this._url.origin;
},
get overridden() {
return this._overridden;
},
get version() {
return NEWTAB_VERSION;
},
get channels() {
return VALID_CHANNELS;
},
/**
* Returns the release name from an Update Channel name
*
* @return {String} a release name based on the update channel. Defaults to nightly
*/
_releaseFromUpdateChannel(channel) {
let result = "nightly";
if (VALID_CHANNELS.has(channel)) {
result = channel;
}
return result;
},
/*
* Updates the location when the page is not overriden.
* Useful when there is a pref change
*/
_updateMaybe() {
if (!this.overridden) {
let url = this._generateDefaultURL();
if (url.href !== this._url.href) {
this._url = url;
Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
this._url.href);
}
}
},
/*
* Override the Remote newtab page location.
*/
override(newURL) {
let url = new URL(newURL);
if (url.href !== this._url.href) {
this._overridden = true;
this._url = url;
Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
this._url.href);
}
},
/*
* Reset the newtab page location to the default value
*/
reset() {
let url = this._generateDefaultURL();
if (url.href !== this._url.href) {
this._url = url;
this._overridden = false;
Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
this._url.href);
}
},
init() {
NewTabPrefsProvider.prefs.on(
PREF_SELECTED_LOCALE,
this._updateMaybe.bind(this));
NewTabPrefsProvider.prefs.on(
PREF_MATCH_OS_LOCALE,
this._updateMaybe.bind(this));
this._url = this._generateDefaultURL();
},
uninit() {
this._url = null;
this._overridden = false;
NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateMaybe);
NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateMaybe);
}
};

Просмотреть файл

@ -0,0 +1,766 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["RemoteNewTabUtils"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
"resource://gre/modules/PageThumbs.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BinarySearch",
"resource://gre/modules/BinarySearch.jsm");
XPCOMUtils.defineLazyGetter(this, "gPrincipal", function () {
let uri = Services.io.newURI("about:newtab", null, null);
return Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
});
// The maximum number of results PlacesProvider retrieves from history.
const HISTORY_RESULTS_LIMIT = 100;
// The maximum number of links Links.getLinks will return.
const LINKS_GET_LINKS_LIMIT = 100;
/**
* Singleton that serves as the default link provider for the grid. It queries
* the history to retrieve the most frequently visited sites.
*/
let PlacesProvider = {
/**
* A count of how many batch updates are under way (batches may be nested, so
* we keep a counter instead of a simple bool).
**/
_batchProcessingDepth: 0,
/**
* A flag that tracks whether onFrecencyChanged was notified while a batch
* operation was in progress, to tell us whether to take special action after
* the batch operation completes.
**/
_batchCalledFrecencyChanged: false,
/**
* Set this to change the maximum number of links the provider will provide.
*/
maxNumLinks: HISTORY_RESULTS_LIMIT,
/**
* Must be called before the provider is used.
*/
init: function PlacesProvider_init() {
PlacesUtils.history.addObserver(this, true);
},
/**
* Gets the current set of links delivered by this provider.
* @param aCallback The function that the array of links is passed to.
*/
getLinks: function PlacesProvider_getLinks(aCallback) {
let options = PlacesUtils.history.getNewQueryOptions();
options.maxResults = this.maxNumLinks;
// Sort by frecency, descending.
options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_FRECENCY_DESCENDING
let links = [];
let callback = {
handleResult: function (aResultSet) {
let row;
while ((row = aResultSet.getNextRow())) {
let url = row.getResultByIndex(1);
if (LinkChecker.checkLoadURI(url)) {
let title = row.getResultByIndex(2);
let frecency = row.getResultByIndex(12);
let lastVisitDate = row.getResultByIndex(5);
links.push({
url: url,
title: title,
frecency: frecency,
lastVisitDate: lastVisitDate,
type: "history",
});
}
}
},
handleError: function (aError) {
// Should we somehow handle this error?
aCallback([]);
},
handleCompletion: function (aReason) {
// The Places query breaks ties in frecency by place ID descending, but
// that's different from how Links.compareLinks breaks ties, because
// compareLinks doesn't have access to place IDs. It's very important
// that the initial list of links is sorted in the same order imposed by
// compareLinks, because Links uses compareLinks to perform binary
// searches on the list. So, ensure the list is so ordered.
let i = 1;
let outOfOrder = [];
while (i < links.length) {
if (Links.compareLinks(links[i - 1], links[i]) > 0)
outOfOrder.push(links.splice(i, 1)[0]);
else
i++;
}
for (let link of outOfOrder) {
i = BinarySearch.insertionIndexOf(Links.compareLinks, links, link);
links.splice(i, 0, link);
}
aCallback(links);
}
};
// Execute the query.
let query = PlacesUtils.history.getNewQuery();
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase);
db.asyncExecuteLegacyQueries([query], 1, options, callback);
},
/**
* Registers an object that will be notified when the provider's links change.
* @param aObserver An object with the following optional properties:
* * onLinkChanged: A function that's called when a single link
* changes. It's passed the provider and the link object. Only the
* link's `url` property is guaranteed to be present. If its `title`
* property is present, then its title has changed, and the
* property's value is the new title. If any sort properties are
* present, then its position within the provider's list of links may
* have changed, and the properties' values are the new sort-related
* values. Note that this link may not necessarily have been present
* in the lists returned from any previous calls to getLinks.
* * onManyLinksChanged: A function that's called when many links
* change at once. It's passed the provider. You should call
* getLinks to get the provider's new list of links.
*/
addObserver: function PlacesProvider_addObserver(aObserver) {
this._observers.push(aObserver);
},
_observers: [],
/**
* Called by the history service.
*/
onBeginUpdateBatch: function() {
this._batchProcessingDepth += 1;
},
onEndUpdateBatch: function() {
this._batchProcessingDepth -= 1;
if (this._batchProcessingDepth == 0 && this._batchCalledFrecencyChanged) {
this.onManyFrecenciesChanged();
this._batchCalledFrecencyChanged = false;
}
},
onDeleteURI: function PlacesProvider_onDeleteURI(aURI, aGUID, aReason) {
// let observers remove sensetive data associated with deleted visit
this._callObservers("onDeleteURI", {
url: aURI.spec,
});
},
onClearHistory: function() {
this._callObservers("onClearHistory")
},
/**
* Called by the history service.
*/
onFrecencyChanged: function PlacesProvider_onFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden, aLastVisitDate) {
// If something is doing a batch update of history entries we don't want
// to do lots of work for each record. So we just track the fact we need
// to call onManyFrecenciesChanged() once the batch is complete.
if (this._batchProcessingDepth > 0) {
this._batchCalledFrecencyChanged = true;
return;
}
// The implementation of the query in getLinks excludes hidden and
// unvisited pages, so it's important to exclude them here, too.
if (!aHidden && aLastVisitDate) {
this._callObservers("onLinkChanged", {
url: aURI.spec,
frecency: aNewFrecency,
lastVisitDate: aLastVisitDate,
type: "history",
});
}
},
/**
* Called by the history service.
*/
onManyFrecenciesChanged: function PlacesProvider_onManyFrecenciesChanged() {
this._callObservers("onManyLinksChanged");
},
/**
* Called by the history service.
*/
onTitleChanged: function PlacesProvider_onTitleChanged(aURI, aNewTitle, aGUID) {
this._callObservers("onLinkChanged", {
url: aURI.spec,
title: aNewTitle
});
},
_callObservers: function PlacesProvider__callObservers(aMethodName, aArg) {
for (let obs of this._observers) {
if (obs[aMethodName]) {
try {
obs[aMethodName](this, aArg);
} catch (err) {
Cu.reportError(err);
}
}
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
Ci.nsISupportsWeakReference]),
};
/**
* Singleton that provides access to all links contained in the grid (including
* the ones that don't fit on the grid). A link is a plain object that looks
* like this:
*
* {
* url: "http://www.mozilla.org/",
* title: "Mozilla",
* frecency: 1337,
* lastVisitDate: 1394678824766431,
* }
*/
let Links = {
/**
* The maximum number of links returned by getLinks.
*/
maxNumLinks: LINKS_GET_LINKS_LIMIT,
/**
* A mapping from each provider to an object { sortedLinks, siteMap, linkMap }.
* sortedLinks is the cached, sorted array of links for the provider.
* siteMap is a mapping from base domains to URL count associated with the domain.
* siteMap is used to look up a user's top sites that can be targeted
* with a suggested tile.
* linkMap is a Map from link URLs to link objects.
*/
_providers: new Map(),
/**
* The properties of link objects used to sort them.
*/
_sortProperties: [
"frecency",
"lastVisitDate",
"url",
],
/**
* List of callbacks waiting for the cache to be populated.
*/
_populateCallbacks: [],
/**
* A list of objects that are observing links updates.
*/
_observers: [],
/**
* Registers an object that will be notified when links updates.
*/
addObserver: function (aObserver) {
this._observers.push(aObserver);
},
/**
* Adds a link provider.
* @param aProvider The link provider.
*/
addProvider: function Links_addProvider(aProvider) {
this._providers.set(aProvider, null);
aProvider.addObserver(this);
},
/**
* Removes a link provider.
* @param aProvider The link provider.
*/
removeProvider: function Links_removeProvider(aProvider) {
if (!this._providers.delete(aProvider))
throw new Error("Unknown provider");
},
/**
* Populates the cache with fresh links from the providers.
* @param aCallback The callback to call when finished (optional).
* @param aForce When true, populates the cache even when it's already filled.
*/
populateCache: function Links_populateCache(aCallback, aForce) {
let callbacks = this._populateCallbacks;
// Enqueue the current callback.
callbacks.push(aCallback);
// There was a callback waiting already, thus the cache has not yet been
// populated.
if (callbacks.length > 1)
return;
function executeCallbacks() {
while (callbacks.length) {
let callback = callbacks.shift();
if (callback) {
try {
callback();
} catch (e) {
// We want to proceed even if a callback fails.
}
}
}
}
let numProvidersRemaining = this._providers.size;
for (let [provider, links] of this._providers) {
this._populateProviderCache(provider, () => {
if (--numProvidersRemaining == 0)
executeCallbacks();
}, aForce);
}
},
/**
* Gets the current set of links contained in the grid.
* @return The links in the grid.
*/
getLinks: function Links_getLinks() {
let links = this._getMergedProviderLinks();
let sites = new Set();
// Filter duplicate base domains.
links = links.filter(function (link) {
let site = RemoteNewTabUtils.extractSite(link.url);
link.baseDomain = site;
if (site == null || sites.has(site))
return false;
sites.add(site);
return true;
});
return links;
},
/**
* Resets the links cache.
*/
resetCache: function Links_resetCache() {
for (let provider of this._providers.keys()) {
this._providers.set(provider, null);
}
},
/**
* Compares two links.
* @param aLink1 The first link.
* @param aLink2 The second link.
* @return A negative number if aLink1 is ordered before aLink2, zero if
* aLink1 and aLink2 have the same ordering, or a positive number if
* aLink1 is ordered after aLink2.
*
* @note compareLinks's this object is bound to Links below.
*/
compareLinks: function Links_compareLinks(aLink1, aLink2) {
for (let prop of this._sortProperties) {
if (!(prop in aLink1) || !(prop in aLink2))
throw new Error("Comparable link missing required property: " + prop);
}
return aLink2.frecency - aLink1.frecency ||
aLink2.lastVisitDate - aLink1.lastVisitDate ||
aLink1.url.localeCompare(aLink2.url);
},
_incrementSiteMap: function(map, link) {
let site = RemoteNewTabUtils.extractSite(link.url);
map.set(site, (map.get(site) || 0) + 1);
},
_decrementSiteMap: function(map, link) {
let site = RemoteNewTabUtils.extractSite(link.url);
let previousURLCount = map.get(site);
if (previousURLCount === 1) {
map.delete(site);
} else {
map.set(site, previousURLCount - 1);
}
},
/**
* Update the siteMap cache based on the link given and whether we need
* to increment or decrement it. We do this by iterating over all stored providers
* to find which provider this link already exists in. For providers that
* have this link, we will adjust siteMap for them accordingly.
*
* @param aLink The link that will affect siteMap
* @param increment A boolean for whether to increment or decrement siteMap
*/
_adjustSiteMapAndNotify: function(aLink, increment=true) {
for (let [provider, cache] of this._providers) {
// We only update siteMap if aLink is already stored in linkMap.
if (cache.linkMap.get(aLink.url)) {
if (increment) {
this._incrementSiteMap(cache.siteMap, aLink);
continue;
}
this._decrementSiteMap(cache.siteMap, aLink);
}
}
this._callObservers("onLinkChanged", aLink);
},
populateProviderCache: function(provider, callback) {
if (!this._providers.has(provider)) {
throw new Error("Can only populate provider cache for existing provider.");
}
return this._populateProviderCache(provider, callback, false);
},
/**
* Calls getLinks on the given provider and populates our cache for it.
* @param aProvider The provider whose cache will be populated.
* @param aCallback The callback to call when finished.
* @param aForce When true, populates the provider's cache even when it's
* already filled.
*/
_populateProviderCache: function (aProvider, aCallback, aForce) {
let cache = this._providers.get(aProvider);
let createCache = !cache;
if (createCache) {
cache = {
// Start with a resolved promise.
populatePromise: new Promise(resolve => resolve()),
};
this._providers.set(aProvider, cache);
}
// Chain the populatePromise so that calls are effectively queued.
cache.populatePromise = cache.populatePromise.then(() => {
return new Promise(resolve => {
if (!createCache && !aForce) {
aCallback();
resolve();
return;
}
aProvider.getLinks(links => {
// Filter out null and undefined links so we don't have to deal with
// them in getLinks when merging links from providers.
links = links.filter((link) => !!link);
cache.sortedLinks = links;
cache.siteMap = links.reduce((map, link) => {
this._incrementSiteMap(map, link);
return map;
}, new Map());
cache.linkMap = links.reduce((map, link) => {
map.set(link.url, link);
return map;
}, new Map());
aCallback();
resolve();
});
});
});
},
/**
* Merges the cached lists of links from all providers whose lists are cached.
* @return The merged list.
*/
_getMergedProviderLinks: function Links__getMergedProviderLinks() {
// Build a list containing a copy of each provider's sortedLinks list.
let linkLists = [];
for (let provider of this._providers.keys()) {
let links = this._providers.get(provider);
if (links && links.sortedLinks) {
linkLists.push(links.sortedLinks.slice());
}
}
function getNextLink() {
let minLinks = null;
for (let links of linkLists) {
if (links.length &&
(!minLinks || Links.compareLinks(links[0], minLinks[0]) < 0))
minLinks = links;
}
return minLinks ? minLinks.shift() : null;
}
let finalLinks = [];
for (let nextLink = getNextLink();
nextLink && finalLinks.length < this.maxNumLinks;
nextLink = getNextLink()) {
finalLinks.push(nextLink);
}
return finalLinks;
},
/**
* Called by a provider to notify us when a single link changes.
* @param aProvider The provider whose link changed.
* @param aLink The link that changed. If the link is new, it must have all
* of the _sortProperties. Otherwise, it may have as few or as
* many as is convenient.
* @param aIndex The current index of the changed link in the sortedLinks
cache in _providers. Defaults to -1 if the provider doesn't know the index
* @param aDeleted Boolean indicating if the provider has deleted the link.
*/
onLinkChanged: function Links_onLinkChanged(aProvider, aLink, aIndex=-1, aDeleted=false) {
if (!("url" in aLink))
throw new Error("Changed links must have a url property");
let links = this._providers.get(aProvider);
if (!links)
// This is not an error, it just means that between the time the provider
// was added and the future time we call getLinks on it, it notified us of
// a change.
return;
let { sortedLinks, siteMap, linkMap } = links;
let existingLink = linkMap.get(aLink.url);
let insertionLink = null;
if (existingLink) {
// Update our copy's position in O(lg n) by first removing it from its
// list. It's important to do this before modifying its properties.
if (this._sortProperties.some(prop => prop in aLink)) {
let idx = aIndex;
if (idx < 0) {
idx = this._indexOf(sortedLinks, existingLink);
} else if (this.compareLinks(aLink, sortedLinks[idx]) != 0) {
throw new Error("aLink should be the same as sortedLinks[idx]");
}
if (idx < 0) {
throw new Error("Link should be in _sortedLinks if in _linkMap");
}
sortedLinks.splice(idx, 1);
if (aDeleted) {
linkMap.delete(existingLink.url);
this._decrementSiteMap(siteMap, existingLink);
} else {
// Update our copy's properties.
Object.assign(existingLink, aLink);
// Finally, reinsert our copy below.
insertionLink = existingLink;
}
}
// Update our copy's title in O(1).
if ("title" in aLink && aLink.title != existingLink.title) {
existingLink.title = aLink.title;
}
}
else if (this._sortProperties.every(prop => prop in aLink)) {
// Before doing the O(lg n) insertion below, do an O(1) check for the
// common case where the new link is too low-ranked to be in the list.
if (sortedLinks.length && sortedLinks.length == aProvider.maxNumLinks) {
let lastLink = sortedLinks[sortedLinks.length - 1];
if (this.compareLinks(lastLink, aLink) < 0) {
return;
}
}
// Copy the link object so that changes later made to it by the caller
// don't affect our copy.
insertionLink = {};
for (let prop in aLink) {
insertionLink[prop] = aLink[prop];
}
linkMap.set(aLink.url, insertionLink);
this._incrementSiteMap(siteMap, aLink);
}
if (insertionLink) {
let idx = this._insertionIndexOf(sortedLinks, insertionLink);
sortedLinks.splice(idx, 0, insertionLink);
if (sortedLinks.length > aProvider.maxNumLinks) {
let lastLink = sortedLinks.pop();
linkMap.delete(lastLink.url);
this._decrementSiteMap(siteMap, lastLink);
}
}
},
/**
* Called by a provider to notify us when many links change.
*/
onManyLinksChanged: function Links_onManyLinksChanged(aProvider) {
this._populateProviderCache(aProvider, () => {}, true);
},
_indexOf: function Links__indexOf(aArray, aLink) {
return this._binsearch(aArray, aLink, "indexOf");
},
_insertionIndexOf: function Links__insertionIndexOf(aArray, aLink) {
return this._binsearch(aArray, aLink, "insertionIndexOf");
},
_binsearch: function Links__binsearch(aArray, aLink, aMethod) {
return BinarySearch[aMethod](this.compareLinks, aArray, aLink);
},
_callObservers(methodName, ...args) {
for (let obs of this._observers) {
if (typeof(obs[methodName]) == "function") {
try {
obs[methodName](this, ...args);
} catch (err) {
Cu.reportError(err);
}
}
}
},
};
Links.compareLinks = Links.compareLinks.bind(Links);
/**
* Singleton that checks if a given link should be displayed on about:newtab
* or if we should rather not do it for security reasons. URIs that inherit
* their caller's principal will be filtered.
*/
let LinkChecker = {
_cache: {},
get flags() {
return Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL |
Ci.nsIScriptSecurityManager.DONT_REPORT_ERRORS;
},
checkLoadURI: function LinkChecker_checkLoadURI(aURI) {
if (!(aURI in this._cache))
this._cache[aURI] = this._doCheckLoadURI(aURI);
return this._cache[aURI];
},
_doCheckLoadURI: function Links_doCheckLoadURI(aURI) {
try {
Services.scriptSecurityManager.
checkLoadURIStrWithPrincipal(gPrincipal, aURI, this.flags);
return true;
} catch (e) {
// We got a weird URI or one that would inherit the caller's principal.
return false;
}
}
};
let ExpirationFilter = {
init: function ExpirationFilter_init() {
PageThumbs.addExpirationFilter(this);
},
filterForThumbnailExpiration:
function ExpirationFilter_filterForThumbnailExpiration(aCallback) {
Links.populateCache(function () {
let urls = [];
// Add all URLs to the list that we want to keep thumbnails for.
for (let link of Links.getLinks().slice(0, 25)) {
if (link && link.url)
urls.push(link.url);
}
aCallback(urls);
});
}
};
/**
* Singleton that provides the public API of this JSM.
*/
this.RemoteNewTabUtils = {
_initialized: false,
/**
* Extract a "site" from a url in a way that multiple urls of a "site" returns
* the same "site."
* @param aUrl Url spec string
* @return The "site" string or null
*/
extractSite: function Links_extractSite(url) {
let host;
try {
// Note that nsIURI.asciiHost throws NS_ERROR_FAILURE for some types of
// URIs, including jar and moz-icon URIs.
host = Services.io.newURI(url, null, null).asciiHost;
} catch (ex) {
return null;
}
// Strip off common subdomains of the same site (e.g., www, load balancer)
return host.replace(/^(m|mobile|www\d*)\./, "");
},
init: function RemoteNewTabUtils_init() {
if (this.initWithoutProviders()) {
PlacesProvider.init();
Links.addProvider(PlacesProvider);
}
},
initWithoutProviders: function RemoteNewTabUtils_initWithoutProviders() {
if (!this._initialized) {
this._initialized = true;
ExpirationFilter.init();
return true;
}
return false;
},
getProviderLinks: function(aProvider) {
let cache = Links._providers.get(aProvider);
if (cache && cache.sortedLinks) {
return cache.sortedLinks;
}
return [];
},
isTopSiteGivenProvider: function(aSite, aProvider) {
let cache = Links._providers.get(aProvider);
if (cache && cache.siteMap) {
return cache.siteMap.has(aSite);
}
return false;
},
isTopPlacesSite: function(aSite) {
return this.isTopSiteGivenProvider(aSite, PlacesProvider);
},
links: Links,
linkChecker: LinkChecker,
placesProvider: PlacesProvider
};

Просмотреть файл

@ -1,185 +0,0 @@
/*
* 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 XPCOMUtils, NewTabPrefsProvider, Services,
Locale, UpdateUtils
*/
"use strict";
const {utils: Cu, interfaces: Ci} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
"resource://gre/modules/Locale.jsm");
const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml";
const REMOTE_NEWTAB_URL = "https://newtab.cdn.mozilla.net/" +
"v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
// Pref that tells if remote newtab is enabled
const PREF_REMOTE_ENABLED = "browser.newtabpage.remote";
// The preference that tells whether to match the OS locale
const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
// The preference that tells what locale the user selected
const PREF_SELECTED_LOCALE = "general.useragent.locale";
const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
const REMOTE_NEWTAB_VERSION = "0";
function AboutNewTabService() {
NewTabPrefsProvider.prefs.on(PREF_REMOTE_ENABLED, this._handleToggleEvent.bind(this));
// trigger remote change if needed, according to pref
this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED));
}
AboutNewTabService.prototype = {
_newTabURL: LOCAL_NEWTAB_URL,
_remoteEnabled: false,
_overridden: false,
classID: Components.ID("{cef25b06-0ef6-4c50-a243-e69f943ef23d}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutNewTabService]),
_xpcom_categories: [{
service: true
}],
_handleToggleEvent(prefName, stateEnabled, forceState) { //jshint unused:false
this.toggleRemote(stateEnabled, forceState);
},
/**
* React to changes to the remote newtab pref. Only act
* if there is a change of state and if not overridden.
*
* @returns {Boolean} Returns if there has been a state change
*
* @param {Boolean} stateEnabled remote state to set to
* @param {Boolean} forceState force state change
*/
toggleRemote(stateEnabled, forceState) {
if (!forceState && (this._overriden || stateEnabled === this._remoteEnabled)) {
// exit there is no change of state
return false;
}
if (stateEnabled) {
this._newTabURL = this.generateRemoteURL();
NewTabPrefsProvider.prefs.on(
PREF_SELECTED_LOCALE,
this._updateRemoteMaybe.bind(this));
NewTabPrefsProvider.prefs.on(
PREF_MATCH_OS_LOCALE,
this._updateRemoteMaybe.bind(this));
this._remoteEnabled = true;
} else {
this._newTabURL = LOCAL_NEWTAB_URL;
NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateRemoteMaybe);
NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateRemoteMaybe);
this._remoteEnabled = false;
}
return true;
},
/*
* Generate a default url based on locale and update channel
*/
generateRemoteURL() {
let releaseName = this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
let url = REMOTE_NEWTAB_URL
.replace("%VERSION%", REMOTE_NEWTAB_VERSION)
.replace("%LOCALE%", Locale.getLocale())
.replace("%CHANNEL%", releaseName);
return url;
},
/*
* Updates the remote location when the page is not overriden.
*
* Useful when there is a dependent pref change
*/
_updateRemoteMaybe() {
if (!this._remoteEnabled || this._overridden) {
return;
}
let url = this.generateRemoteURL();
if (url !== this._newTabURL) {
this._newTabURL = url;
Services.obs.notifyObservers(null, "newtab-url-changed",
this._newTabURL);
}
},
/**
* Returns the release name from an Update Channel name
*
* @return {String} a release name based on the update channel. Defaults to nightly
*/
releaseFromUpdateChannel(channelName) {
return VALID_CHANNELS.has(channelName) ? channelName : "nightly";
},
get newTabURL() {
return this._newTabURL;
},
get remoteVersion() {
return REMOTE_NEWTAB_VERSION;
},
get remoteReleaseName() {
return this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
},
set newTabURL(aNewTabURL) {
let remoteURL = this.generateRemoteURL();
let prefRemoteEnabled = Services.prefs.getBoolPref(PREF_REMOTE_ENABLED);
let isResetLocal = !prefRemoteEnabled && aNewTabURL === LOCAL_NEWTAB_URL;
let isResetRemote = prefRemoteEnabled && aNewTabURL === remoteURL;
if (isResetLocal || isResetRemote) {
if (this._overriden) {
// only trigger a reset if previously overridden
this.resetNewTabURL();
}
return;
}
// turn off remote state if needed
this.toggleRemote(false);
this._newTabURL = aNewTabURL;
this._overridden = true;
Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
},
get overridden() {
return this._overridden;
},
get remoteEnabled() {
return this._remoteEnabled;
},
resetNewTabURL() {
this._overridden = false;
this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED), true);
Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutNewTabService]);

Просмотреть файл

@ -4,24 +4,18 @@
# 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']
if not CONFIG['RELEASE_BUILD']:
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
XPCSHELL_TESTS_MANIFESTS += [
'tests/xpcshell/xpcshell.ini',
]
XPCSHELL_TESTS_MANIFESTS += [
'tests/xpcshell/xpcshell.ini',
]
EXTRA_JS_MODULES += [
'NewTabPrefsProvider.jsm',
'PlacesProvider.jsm',
]
XPIDL_SOURCES += [
'nsIAboutNewTabService.idl',
]
XPIDL_MODULE = 'browser-newtab'
EXTRA_COMPONENTS += [
'aboutNewTabService.js',
'NewTabComponents.manifest',
]
EXTRA_JS_MODULES += [
'NewTabPrefsProvider.jsm',
'NewTabURL.jsm',
'PlacesProvider.jsm',
'RemoteAboutNewTab.jsm',
'RemoteNewTabLocation.jsm',
'RemoteNewTabUtils.jsm',
]

Просмотреть файл

@ -1,58 +0,0 @@
/* 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/. */
#include "nsISupports.idl"
/**
* Allows to override about:newtab to point to a different location
* than the one specified within AboutRedirector.cpp
*/
[scriptable, uuid(cef25b06-0ef6-4c50-a243-e69f943ef23d)]
interface nsIAboutNewTabService : nsISupports
{
/**
* Returns the url of the resource for the newtab page if not overridden,
* otherwise a string represenation of the new URL.
*/
attribute ACString newTabURL;
/**
* Returns true if the default resource got overridden.
*/
readonly attribute bool overridden;
/**
* Returns true if the default resource is remotely hosted and isn't
* overridden
*/
readonly attribute bool remoteEnabled;
/**
* Returns the version of the remote newtab page expected
*/
readonly attribute ACString remoteVersion;
/**
* Returns the expected channel for the remote the newtab page
*/
readonly attribute ACString remoteReleaseName;
/**
* Generates and returns the remote newtab page url
*/
ACString generateRemoteURL();
/**
* Returns a remote new tab release name given an update channel name
*/
ACString releaseFromUpdateChannel(in ACString channelName);
/**
* Resets to the default resource and also resets the
* overridden attribute to false.
*/
void resetNewTabURL();
};

Просмотреть файл

@ -1,57 +1,46 @@
/* globals XPCOMUtils, aboutNewTabService, Services */
/* 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");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemotePageManager",
"resource://gre/modules/RemotePageManager.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
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() {
let notificationPromise = nextChangeNotificationPromise(TEST_URL, "newtab page now points to test url");
aboutNewTabService.newTabURL = TEST_URL;
yield notificationPromise;
Assert.ok(aboutNewTabService.overridden, "url has been overridden");
RemoteNewTabLocation.override(TEST_URL);
ok(RemoteNewTabLocation.href === TEST_URL, "RemoteNewTabLocation has been overridden");
let tabOptions = {
gBrowser,
url: "about:newtab",
url: NEWTAB_URL,
};
yield BrowserTestUtils.withNewTab(tabOptions, function* (browser) {
Assert.equal(TEST_URL, browser.contentWindow.location, `New tab should open to ${TEST_URL}`);
});
for (let test of tests) {
yield BrowserTestUtils.withNewTab(tabOptions, function* (browser) { // jshint ignore:line
yield test;
}); // jshint ignore:line
}
});
add_task(function* emptyURL() {
let notificationPromise = nextChangeNotificationPromise("", "newtab service now points to empty url");
aboutNewTabService.newTabURL = "";
yield notificationPromise;
let tabOptions = {
gBrowser,
url: "about:newtab",
};
yield BrowserTestUtils.withNewTab(tabOptions, function* (browser) {
Assert.equal("about:blank", browser.contentWindow.location, `New tab should open to ${"about:blank"}`);
});
});
function nextChangeNotificationPromise(aNewURL, testMessage) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint unused:false
Services.obs.removeObserver(observer, aTopic);
Assert.equal(aData, aNewURL, testMessage);
resolve();
}, "newtab-url-changed", false);
});
}

Просмотреть файл

@ -6,5 +6,17 @@
</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>

Просмотреть файл

@ -1,144 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* globals Services, XPCOMUtils, NewTabPrefsProvider, Preferences, aboutNewTabService */
"use strict";
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
let aboutNewTabService = Components.classes["@mozilla.org/browser/aboutnewtab-service;1"]
.getService(Components.interfaces.nsIAboutNewTabService);
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
const DEFAULT_HREF = aboutNewTabService.generateRemoteURL();
/**
* Test the overriding of the default URL
*/
add_task(function* () {
NewTabPrefsProvider.prefs.init();
let notificationPromise;
Services.prefs.setBoolPref("browser.newtabpage.remote", false);
// tests default is the local newtab resource
Assert.equal(aboutNewTabService.newTabURL, "chrome://browser/content/newtab/newTab.xhtml",
"Default newtab URL should be chrome://browser/content/newtab/newTab.xhtml");
Assert.equal(aboutNewTabService.newTabURL, "about:newtab", "Default newtab URL should be about:newtab");
let url = "http://example.com/";
notificationPromise = nextChangeNotificationPromise(url);
let notificationPromise = promiseNewtabURLNotification(url);
aboutNewTabService.newTabURL = url;
yield notificationPromise;
Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
Assert.equal(aboutNewTabService.newTabURL, url, "Newtab URL should be the custom URL");
notificationPromise = nextChangeNotificationPromise("chrome://browser/content/newtab/newTab.xhtml");
notificationPromise = promiseNewtabURLNotification("about:newtab");
aboutNewTabService.resetNewTabURL();
yield notificationPromise;
Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
Assert.equal(aboutNewTabService.newTabURL, "chrome://browser/content/newtab/newTab.xhtml",
"Newtab URL should be the default");
Assert.equal(aboutNewTabService.newTabURL, "about:newtab", "Newtab URL should be the about:newtab");
// change newtab page to remote
Services.prefs.setBoolPref("browser.newtabpage.remote", true);
let remoteHref = aboutNewTabService.generateRemoteURL();
Assert.equal(aboutNewTabService.newTabURL, remoteHref, "Newtab URL should be the default remote URL");
Assert.equal(aboutNewTabService.newTabURL, "about:remote-newtab", "Newtab URL should be the about:remote-newtab");
Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
NewTabPrefsProvider.prefs.uninit();
});
/**
* Tests reponse to updates to prefs
*/
add_task(function* test_updates() {
Preferences.set("browser.newtabpage.remote", true);
let notificationPromise;
let expectedHref = "https://newtab.cdn.mozilla.net" +
`/v${aboutNewTabService.remoteVersion}` +
`/${aboutNewTabService.remoteReleaseName}` +
"/en-GB" +
"/index.html";
Preferences.set("intl.locale.matchOS", true);
Preferences.set("general.useragent.locale", "en-GB");
NewTabPrefsProvider.prefs.init();
// test update checks for prefs
notificationPromise = nextChangeNotificationPromise(
expectedHref, "Remote href should be updated");
Preferences.set("intl.locale.matchOS", false);
yield notificationPromise;
notificationPromise = nextChangeNotificationPromise(
DEFAULT_HREF, "Remote href changes back to default");
Preferences.set("general.useragent.locale", "en-US");
yield notificationPromise;
// test update fires on override and reset
let testURL = "https://example.com/";
notificationPromise = nextChangeNotificationPromise(
testURL, "a notification occurs on override");
aboutNewTabService.newTabURL = testURL;
yield notificationPromise;
// from overridden to default
notificationPromise = nextChangeNotificationPromise(
DEFAULT_HREF, "a notification occurs on reset");
aboutNewTabService.resetNewTabURL();
Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
yield notificationPromise;
// override to default URL from default URL
notificationPromise = nextChangeNotificationPromise(
testURL, "a notification only occurs for a change in overridden urls");
aboutNewTabService.newTabURL = aboutNewTabService.generateRemoteURL();
Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
aboutNewTabService.newTabURL = testURL;
Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
yield notificationPromise;
// reset twice, only one notification for default URL
notificationPromise = nextChangeNotificationPromise(
DEFAULT_HREF, "reset occurs");
aboutNewTabService.resetNewTabURL();
yield notificationPromise;
NewTabPrefsProvider.prefs.uninit();
});
/**
* Verifies that releaseFromUpdateChannel
* Returns the correct release names
*/
add_task(function* test_release_names() {
let valid_channels = ["esr", "release", "beta", "aurora", "nightly"];
let invalid_channels = new Set(["default", "invalid"]);
for (let channel of valid_channels) {
Assert.equal(channel, aboutNewTabService.releaseFromUpdateChannel(channel),
"release == channel name when valid");
}
for (let channel of invalid_channels) {
Assert.equal("nightly", aboutNewTabService.releaseFromUpdateChannel(channel),
"release == nightly when invalid");
}
});
function nextChangeNotificationPromise(aNewURL, testMessage) {
function promiseNewtabURLNotification(aNewURL) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint unused:false
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
Services.obs.removeObserver(observer, aTopic);
Assert.equal(aData, aNewURL, testMessage);
Assert.equal(aData, aNewURL, "Data for newtab-url-changed notification should be new URL.");
resolve();
}, "newtab-url-changed", false);
});

Просмотреть файл

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
Components.utils.import("resource:///modules/NewTabURL.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
add_task(function* () {
Assert.equal(NewTabURL.get(), "about:newtab", "Default newtab URL should be about:newtab");
let url = "http://example.com/";
let notificationPromise = promiseNewtabURLNotification(url);
NewTabURL.override(url);
yield notificationPromise;
Assert.ok(NewTabURL.overridden, "Newtab URL should be overridden");
Assert.equal(NewTabURL.get(), url, "Newtab URL should be the custom URL");
notificationPromise = promiseNewtabURLNotification("about:newtab");
NewTabURL.reset();
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) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
Services.obs.removeObserver(observer, aTopic);
Assert.equal(aData, aNewURL, "Data for newtab-url-changed notification should be new URL.");
resolve();
}, "newtab-url-changed", false);
});
}

Просмотреть файл

@ -0,0 +1,148 @@
/* globals ok, equal, RemoteNewTabLocation, NewTabPrefsProvider, Services, Preferences, XPCOMUtils, UpdateUtils */
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
"use strict";
const {utils: Cu} = Components;
Cu.import("resource:///modules/RemoteNewTabLocation.jsm");
Cu.import("resource:///modules/NewTabPrefsProvider.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.importGlobalProperties(["URL"]);
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
RemoteNewTabLocation.init();
const DEFAULT_HREF = RemoteNewTabLocation.href;
RemoteNewTabLocation.uninit();
add_task(function* test_defaults() {
RemoteNewTabLocation.init();
ok(RemoteNewTabLocation.href, "Default location has an href");
ok(RemoteNewTabLocation.origin, "Default location has an origin");
ok(!RemoteNewTabLocation.overridden, "Default location is not overridden");
RemoteNewTabLocation.uninit();
});
/**
* Tests the overriding of the default URL
*/
add_task(function* test_overrides() {
RemoteNewTabLocation.init();
let testURL = new URL("https://example.com/");
let notificationPromise;
notificationPromise = nextChangeNotificationPromise(
testURL.href, "Remote Location should change");
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 = nextChangeNotificationPromise(
DEFAULT_HREF, "Remote href should be reset");
RemoteNewTabLocation.reset();
yield notificationPromise;
ok(!RemoteNewTabLocation.overridden, "Newtab URL should not be overridden");
RemoteNewTabLocation.uninit();
});
/**
* Tests how RemoteNewTabLocation responds to updates to prefs
*/
add_task(function* test_updates() {
RemoteNewTabLocation.init();
let notificationPromise;
let release = RemoteNewTabLocation._releaseFromUpdateChannel(
UpdateUtils.UpdateChannel);
let expectedHref = "https://newtab.cdn.mozilla.net" +
`/v${RemoteNewTabLocation.version}` +
`/${release}` +
"/en-GB" +
"/index.html";
Preferences.set("intl.locale.matchOS", true);
Preferences.set("general.useragent.locale", "en-GB");
NewTabPrefsProvider.prefs.init();
// test update checks for prefs
notificationPromise = nextChangeNotificationPromise(
expectedHref, "Remote href should be updated");
Preferences.set("intl.locale.matchOS", false);
yield notificationPromise;
notificationPromise = nextChangeNotificationPromise(
DEFAULT_HREF, "Remote href changes back to default");
Preferences.set("general.useragent.locale", "en-US");
yield notificationPromise;
// test update fires on override and reset
let testURL = new URL("https://example.com/");
notificationPromise = nextChangeNotificationPromise(
testURL.href, "a notification occurs on override");
RemoteNewTabLocation.override(testURL.href);
yield notificationPromise;
// from overridden to default
notificationPromise = nextChangeNotificationPromise(
DEFAULT_HREF, "a notification occurs on reset");
RemoteNewTabLocation.reset();
yield notificationPromise;
// override to default URL from default URL
notificationPromise = nextChangeNotificationPromise(
testURL.href, "a notification only occurs for a change in overridden urls");
RemoteNewTabLocation.override(DEFAULT_HREF);
RemoteNewTabLocation.override(testURL.href);
yield notificationPromise;
// reset twice, only one notification for default URL
notificationPromise = nextChangeNotificationPromise(
DEFAULT_HREF, "reset occurs");
RemoteNewTabLocation.reset();
yield notificationPromise;
notificationPromise = nextChangeNotificationPromise(
testURL.href, "a notification only occurs for a change in reset urls");
RemoteNewTabLocation.reset();
RemoteNewTabLocation.override(testURL.href);
yield notificationPromise;
NewTabPrefsProvider.prefs.uninit();
RemoteNewTabLocation.uninit();
});
/**
* Verifies that RemoteNewTabLocation's _releaseFromUpdateChannel
* Returns the correct release names
*/
add_task(function* test_release_names() {
RemoteNewTabLocation.init();
let valid_channels = RemoteNewTabLocation.channels;
let invalid_channels = new Set(["default", "invalid"]);
for (let channel of valid_channels) {
equal(channel, RemoteNewTabLocation._releaseFromUpdateChannel(channel),
"release == channel name when valid");
}
for (let channel of invalid_channels) {
equal("nightly", RemoteNewTabLocation._releaseFromUpdateChannel(channel),
"release == nightly when invalid");
}
RemoteNewTabLocation.uninit();
});
function nextChangeNotificationPromise(aNewURL, testMessage) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint ignore:line
Services.obs.removeObserver(observer, aTopic);
equal(aData, aNewURL, testMessage);
resolve();
}, "remote-new-tab-location-changed", false);
});
}

Просмотреть файл

@ -0,0 +1,375 @@
/* 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
// that no more than maxNumLinks are actually returned in the merged list.
let evenLinks = makeLinks(0, 2 * RemoteNewTabUtils.links.maxNumLinks, 2);
let evenProvider = new TestProvider(done => done(evenLinks));
let oddLinks = makeLinks(0, 2 * RemoteNewTabUtils.links.maxNumLinks - 1, 2);
let oddProvider = new TestProvider(done => done(oddLinks));
RemoteNewTabUtils.initWithoutProviders();
RemoteNewTabUtils.links.addProvider(evenProvider);
RemoteNewTabUtils.links.addProvider(oddProvider);
yield new Promise(resolve => RemoteNewTabUtils.links.populateCache(resolve));
let links = RemoteNewTabUtils.links.getLinks();
let expectedLinks = makeLinks(RemoteNewTabUtils.links.maxNumLinks,
2 * RemoteNewTabUtils.links.maxNumLinks,
1);
do_check_eq(links.length, RemoteNewTabUtils.links.maxNumLinks);
do_check_links(links, expectedLinks);
RemoteNewTabUtils.links.removeProvider(evenProvider);
RemoteNewTabUtils.links.removeProvider(oddProvider);
});
add_task(function* changeLinks() {
let expectedLinks = makeLinks(0, 20, 2);
let provider = new TestProvider(done => done(expectedLinks));
RemoteNewTabUtils.initWithoutProviders();
RemoteNewTabUtils.links.addProvider(provider);
yield new Promise(resolve => RemoteNewTabUtils.links.populateCache(resolve));
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks);
// Notify of a new link.
let newLink = makeLink(19);
expectedLinks.splice(1, 0, newLink);
provider.notifyLinkChanged(newLink);
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks);
// Notify of a link that's changed sort criteria.
newLink.frecency = 17;
expectedLinks.splice(1, 1);
expectedLinks.splice(2, 0, newLink);
provider.notifyLinkChanged({
url: newLink.url,
frecency: 17,
});
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks);
// Notify of a link that's changed title.
newLink.title = "My frecency is now 17";
provider.notifyLinkChanged({
url: newLink.url,
title: newLink.title,
});
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks);
// Notify of a new link again, but this time make it overflow maxNumLinks.
provider.maxNumLinks = expectedLinks.length;
newLink = makeLink(21);
expectedLinks.unshift(newLink);
expectedLinks.pop();
do_check_eq(expectedLinks.length, provider.maxNumLinks); // Sanity check.
provider.notifyLinkChanged(newLink);
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks);
// Notify of many links changed.
expectedLinks = makeLinks(0, 3, 1);
provider.notifyManyLinksChanged();
// Since _populateProviderCache() is async, we must wait until the provider's
// populate promise has been resolved.
yield RemoteNewTabUtils.links._providers.get(provider).populatePromise;
// RemoteNewTabUtils.links will now repopulate its cache
do_check_links(RemoteNewTabUtils.links.getLinks(), expectedLinks);
RemoteNewTabUtils.links.removeProvider(provider);
});
add_task(function* oneProviderAlreadyCached() {
let links1 = makeLinks(0, 10, 1);
let provider1 = new TestProvider(done => done(links1));
RemoteNewTabUtils.initWithoutProviders();
RemoteNewTabUtils.links.addProvider(provider1);
yield new Promise(resolve => RemoteNewTabUtils.links.populateCache(resolve));
do_check_links(RemoteNewTabUtils.links.getLinks(), links1);
let links2 = makeLinks(10, 20, 1);
let provider2 = new TestProvider(done => done(links2));
RemoteNewTabUtils.links.addProvider(provider2);
yield new Promise(resolve => RemoteNewTabUtils.links.populateCache(resolve));
do_check_links(RemoteNewTabUtils.links.getLinks(), links2.concat(links1));
RemoteNewTabUtils.links.removeProvider(provider1);
RemoteNewTabUtils.links.removeProvider(provider2);
});
add_task(function* newLowRankedLink() {
// Init a provider with 10 links and make its maximum number also 10.
let links = makeLinks(0, 10, 1);
let provider = new TestProvider(done => done(links));
provider.maxNumLinks = links.length;
RemoteNewTabUtils.initWithoutProviders();
RemoteNewTabUtils.links.addProvider(provider);
yield new Promise(resolve => RemoteNewTabUtils.links.populateCache(resolve));
do_check_links(RemoteNewTabUtils.links.getLinks(), links);
// Notify of a new link that's low-ranked enough not to make the list.
let newLink = makeLink(0);
provider.notifyLinkChanged(newLink);
do_check_links(RemoteNewTabUtils.links.getLinks(), links);
// Notify about the new link's title change.
provider.notifyLinkChanged({
url: newLink.url,
title: "a new title",
});
do_check_links(RemoteNewTabUtils.links.getLinks(), links);
RemoteNewTabUtils.links.removeProvider(provider);
});
add_task(function extractSite() {
// All these should extract to the same site
[ "mozilla.org",
"m.mozilla.org",
"mobile.mozilla.org",
"www.mozilla.org",
"www3.mozilla.org",
].forEach(host => {
let url = "http://" + host;
do_check_eq(RemoteNewTabUtils.extractSite(url), "mozilla.org", "extracted same " + host);
});
// All these should extract to the same subdomain
[ "bugzilla.mozilla.org",
"www.bugzilla.mozilla.org",
].forEach(host => {
let url = "http://" + host;
do_check_eq(RemoteNewTabUtils.extractSite(url), "bugzilla.mozilla.org", "extracted eTLD+2 " + host);
});
// All these should not extract to the same site
[ "bugzilla.mozilla.org",
"bug123.bugzilla.mozilla.org",
"too.many.levels.bugzilla.mozilla.org",
"m2.mozilla.org",
"mobile30.mozilla.org",
"ww.mozilla.org",
"ww2.mozilla.org",
"wwwww.mozilla.org",
"wwwww50.mozilla.org",
"wwws.mozilla.org",
"secure.mozilla.org",
"secure10.mozilla.org",
"many.levels.deep.mozilla.org",
"just.check.in",
"192.168.0.1",
"localhost",
].forEach(host => {
let url = "http://" + host;
do_check_neq(RemoteNewTabUtils.extractSite(url), "mozilla.org", "extracted diff " + host);
});
// All these should not extract to the same site
[ "about:blank",
"file:///Users/user/file",
"chrome://browser/something",
"ftp://ftp.mozilla.org/",
].forEach(url => {
do_check_neq(RemoteNewTabUtils.extractSite(url), "mozilla.org", "extracted diff url " + url);
});
});
function TestProvider(getLinksFn) {
this.getLinks = getLinksFn;
this._observers = new Set();
}
TestProvider.prototype = {
addObserver: function (observer) {
this._observers.add(observer);
},
notifyLinkChanged: function (link, index=-1, deleted=false) {
this._notifyObservers("onLinkChanged", link, index, deleted);
},
notifyManyLinksChanged: function () {
this._notifyObservers("onManyLinksChanged");
},
_notifyObservers: function () {
let observerMethodName = arguments[0];
let args = Array.prototype.slice.call(arguments, 1);
args.unshift(this);
for (let obs of this._observers) {
if (obs[observerMethodName])
obs[observerMethodName].apply(RemoteNewTabUtils.links, args);
}
},
};
function do_check_links(actualLinks, expectedLinks) {
do_check_true(Array.isArray(actualLinks));
do_check_eq(actualLinks.length, expectedLinks.length);
for (let i = 0; i < expectedLinks.length; i++) {
let expected = expectedLinks[i];
let actual = actualLinks[i];
do_check_eq(actual.url, expected.url);
do_check_eq(actual.title, expected.title);
do_check_eq(actual.frecency, expected.frecency);
do_check_eq(actual.lastVisitDate, expected.lastVisitDate);
}
}
function makeLinks(frecRangeStart, frecRangeEnd, step) {
let links = [];
// Remember, links are ordered by frecency descending.
for (let i = frecRangeEnd; i > frecRangeStart; i -= step) {
links.push(makeLink(i));
}
return links;
}
function makeLink(frecency) {
return {
url: "http://example" + frecency + ".com/",
title: "My frecency is " + frecency,
frecency: frecency,
lastVisitDate: 0,
};
}

Просмотреть файл

@ -6,4 +6,7 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_AboutNewTabService.js]
[test_NewTabPrefsProvider.js]
[test_NewTabURL.js]
[test_PlacesProvider.js]
[test_RemoteNewTabLocation.js]
[test_RemoteNewTabUtils.js]

Просмотреть файл

@ -14,6 +14,9 @@ const POLARIS_ENABLED = "browser.polaris.enabled";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
"resource:///modules/AboutHome.jsm");
@ -26,8 +29,16 @@ XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
if(!AppConstants.RELEASE_BUILD) {
XPCOMUtils.defineLazyModuleGetter(this, "RemoteAboutNewTab",
"resource:///modules/RemoteAboutNewTab.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabUtils",
"resource:///modules/RemoteNewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
}
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
"resource:///modules/UITour.jsm");
@ -178,15 +189,14 @@ XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
"@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
XPCOMUtils.defineLazyServiceGetter(this, "AlertsService",
"@mozilla.org/alerts-service;1", "nsIAlertsService");
const ABOUT_NEWTAB = "about:newtab";
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
@ -821,7 +831,12 @@ BrowserGlue.prototype = {
NewTabUtils.links.addProvider(DirectoryLinksProvider);
AboutNewTab.init();
NewTabPrefsProvider.prefs.init();
if(!AppConstants.RELEASE_BUILD) {
RemoteNewTabUtils.init();
RemoteNewTabUtils.links.addProvider(DirectoryLinksProvider);
RemoteAboutNewTab.init();
NewTabPrefsProvider.prefs.init();
}
SessionStore.init();
BrowserUITelemetry.init();
@ -1142,7 +1157,10 @@ BrowserGlue.prototype = {
CustomizationTabPreloader.uninit();
WebappManager.uninit();
NewTabPrefsProvider.prefs.uninit();
if (!AppConstants.RELEASE_BUILD) {
RemoteAboutNewTab.uninit();
NewTabPrefsProvider.prefs.uninit();
}
AboutNewTab.uninit();
#ifdef NIGHTLY_BUILD
if (Services.prefs.getBoolPref("dom.identity.enabled")) {
@ -2512,6 +2530,50 @@ BrowserGlue.prototype = {
_xpcom_factory: BrowserGlueServiceFactory,
}
// ------------------------------------
// nsIAboutNewTabService implementation
//-------------------------------------
function AboutNewTabService()
{
this._newTabURL = ABOUT_NEWTAB;
this._overridden = false;
}
AboutNewTabService.prototype = {
classID: Components.ID("{97eea4bb-db50-4ae0-9147-1e5ed55b4ed5}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutNewTabService]),
get newTabURL() {
if (!AppConstants.RELEASE_BUILD && Services.prefs.getBoolPref("browser.newtabpage.remote")) {
return "about:remote-newtab";
}
return this._newTabURL;
},
set newTabURL(aNewTabURL) {
if (aNewTabURL === ABOUT_NEWTAB) {
this.resetNewTabURL();
return;
}
this._newTabURL = aNewTabURL;
this._overridden = true;
Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
},
get overridden() {
return this._overridden;
},
resetNewTabURL: function() {
this._newTabURL = ABOUT_NEWTAB;
this._overridden = false;
Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
}
};
function ContentPermissionPrompt() {}
ContentPermissionPrompt.prototype = {
@ -3326,7 +3388,7 @@ var E10SAccessibilityCheck = {
},
};
var components = [BrowserGlue, ContentPermissionPrompt];
var components = [BrowserGlue, ContentPermissionPrompt, AboutNewTabService];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);

Просмотреть файл

@ -0,0 +1,32 @@
/* 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/. */
#include "nsISupports.idl"
/**
* Allows to override about:newtab to point to a different location
* than the one specified within AboutRedirector.cpp
*/
[scriptable, uuid(6c66f022-beb1-46ea-8af6-c6c6dd937ea9)]
interface nsIAboutNewTabService : nsISupports
{
/**
* Returns "about:newtab" if not overridden, otherwise
* a string represenation of the new URL.
*/
attribute ACString newTabURL;
/**
* Returns true if the default of "about:newtab" got
* overridden.
*/
readonly attribute bool overridden;
/**
* Resets "about:newtab" to the default and also resets the
* overridden attribute to false.
*/
void resetNewTabURL();
};

Просмотреть файл

@ -388,9 +388,6 @@
@RESPATH@/browser/components/webideComponents.manifest
@RESPATH@/browser/components/Experiments.manifest
@RESPATH@/browser/components/ExperimentsService.js
@RESPATH@/browser/components/browser-newtab.xpt
@RESPATH@/browser/components/aboutNewTabService.js
@RESPATH@/browser/components/NewTabComponents.manifest
@RESPATH@/components/Downloads.manifest
@RESPATH@/components/DownloadLegacy.js
@RESPATH@/components/BrowserPageThumbs.manifest

Просмотреть файл

@ -17,6 +17,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm")
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
@ -31,6 +32,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
XPCOMUtils.defineLazyServiceGetter(this, "eTLD",
"@mozilla.org/network/effective-tld-service;1",
"nsIEffectiveTLDService");
// ensure remote new tab doesn't go beyond aurora
if (!AppConstants.RELEASE_BUILD) {
XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabUtils",
"resource:///modules/RemoteNewTabUtils.jsm");
}
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
return new TextDecoder();
});
@ -759,7 +767,13 @@ var DirectoryLinksProvider = {
NewTabUtils.placesProvider.addObserver(this);
NewTabUtils.links.addObserver(this);
return Task.spawn(function() {
// ensure remote new tab doesn't go beyond aurora
if (!AppConstants.RELEASE_BUILD) {
RemoteNewTabUtils.placesProvider.addObserver(this);
RemoteNewTabUtils.links.addObserver(this);
}
return Task.spawn(function*() {
// get the last modified time of the links file if it exists
let doesFileExists = yield OS.File.exists(this._directoryFilePath);
if (doesFileExists) {

Просмотреть файл

@ -120,7 +120,6 @@
@BINPATH@/components/browsercompsbase.xpt
@BINPATH@/components/browser-element.xpt
@BINPATH@/components/browser-feeds.xpt
@BINPATH@/components/browser-newtab.xpt
@BINPATH@/components/caps.xpt
@BINPATH@/components/chardet.xpt
@BINPATH@/components/chrome.xpt