merge mozilla-inbound to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-01-13 11:57:15 +01:00
commit 0506fad16e
520 changed files with 10157 additions and 7165 deletions

View File

@ -18,6 +18,7 @@ caps/**
chrome/**
config/**
db/**
devtools/**
docshell/**
dom/**
editor/**

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1209344 - Remove debug button from about:addons. r=mossop
Bug 1133073 - Use PR_DuplicateEnvironment from NSPR and remove interim mozglue wrappers.

View File

@ -77,7 +77,7 @@ this.EventManager.prototype = {
Logger.debug('EventManager.stop');
AccessibilityEventObserver.removeListener(this);
try {
this._preDialogPosition.clear();
this._preDialogPosition = new WeakMap();
this.webProgress.removeProgressListener(this);
this.removeEventListener('wheel', this, true);
this.removeEventListener('scroll', this, true);
@ -618,7 +618,7 @@ const AccessibilityEventObserver = {
}
Services.obs.removeObserver(this, 'accessible-event');
// Clean up all registered event managers.
this.eventManagers.clear();
this.eventManagers = new WeakMap();
this.listenerCount = 0;
this.started = false;
},

View File

@ -13,16 +13,21 @@ const { Cu } = require("chrome");
function makeGetterFor(Type) {
let cache = new WeakMap();
return function getFor(target) {
if (!cache.has(target))
cache.set(target, new Type());
return {
getFor(target) {
if (!cache.has(target))
cache.set(target, new Type());
return cache.get(target);
return cache.get(target);
},
clearFor(target) {
return cache.delete(target)
}
}
}
var getLookupFor = makeGetterFor(WeakMap);
var getRefsFor = makeGetterFor(Set);
var {getFor: getLookupFor, clearFor: clearLookupFor} = makeGetterFor(WeakMap);
var {getFor: getRefsFor, clearFor: clearRefsFor} = makeGetterFor(Set);
function add(target, value) {
if (has(target, value))
@ -44,8 +49,8 @@ function has(target, value) {
exports.has = has;
function clear(target) {
getLookupFor(target).clear();
getRefsFor(target).clear();
clearLookupFor(target);
clearRefsFor(target);
}
exports.clear = clear;

View File

@ -71,11 +71,12 @@ FilePicker.prototype = {
/* readonly attribute nsISimpleEnumerator files - not implemented; */
/* readonly attribute nsIURI fileURL - not implemented; */
get domfiles() {
get domFileOrDirectoryEnumerator() {
return this.mFilesEnumerator;
},
get domfile() {
// We don't support directory selection yet.
get domFileOrDirectory() {
return this.mFilesEnumerator ? this.mFilesEnumerator.mFiles[0] : null;
},

View File

@ -1349,10 +1349,8 @@ 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/");
#ifndef RELEASE_BUILD
// if true, it activates the remote-hosted newtab page
// activates the remote-hosted newtab page
pref("browser.newtabpage.remote", false);
#endif
// Enable the DOM fullscreen API.
pref("full-screen-api.enabled", true);

View File

@ -65,7 +65,6 @@ var FullZoom = {
}
// This should be nulled after initialization.
this._initialLocations.clear();
this._initialLocations = null;
},

View File

@ -11,6 +11,7 @@ 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",
@ -53,7 +54,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
"@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gAboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings.createBundle('chrome://browser/locale/browser.properties');
});
@ -239,10 +242,6 @@ XPCOMUtils.defineLazyGetter(this, "Win7Features", function () {
if (AppConstants.platform != "win")
return null;
// Bug 666808 - AeroPeek support for e10s
if (gMultiProcessBrowser)
return null;
const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
if (WINTASKBAR_CONTRACTID in Cc &&
Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
@ -2316,8 +2315,11 @@ function URLBarSetURI(aURI) {
} catch (e) {}
// Replace initial page URIs with an empty string
// only if there's no opener (bug 370555).
if (gInitialPages.indexOf(uri.spec) != -1)
// 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)
value = gBrowser.selectedBrowser.hasContentOpener ? uri.spec : "";
else
value = losslessDecodeURI(uri);
@ -3524,8 +3526,9 @@ const BrowserSearch = {
if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
let url = gBrowser.currentURI.spec.toLowerCase();
let mm = gBrowser.selectedBrowser.messageManager;
if (url === "about:home" ||
(url === "about:newtab" && NewTabUtils.allPages.enabled)) {
let newTabRemoted = Services.prefs.getBoolPref("browser.newtabpage.remote");
let localNewTabEnabled = url === "about:newtab" && !newTabRemoted && NewTabUtils.allPages.enabled;
if (url === "about:home" || localNewTabEnabled) {
ContentSearch.focusInput(mm);
} else {
openUILinkIn("about:home", "current");

View File

@ -1,23 +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/. */
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;
}

View File

@ -1,126 +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, 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");
}());

View File

@ -1,24 +0,0 @@
<?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>

View File

@ -616,6 +616,9 @@ var DOMFullscreenHandler = {
},
get _windowUtils() {
if (!content) {
return null;
}
return content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
},
@ -633,7 +636,9 @@ var DOMFullscreenHandler = {
break;
}
case "DOMFullscreen:CleanUp": {
this._windowUtils.exitFullscreen();
if (this._windowUtils) {
this._windowUtils.exitFullscreen();
}
this._fullscreenDoc = null;
break;
}

View File

@ -1,36 +1,40 @@
/* 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 = aboutNewTabService.newTabURL;
newTabURL = "about:newtab";
}
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) {
@ -40,9 +44,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();

View File

@ -1,24 +1,30 @@
/* 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 = aboutNewTabService.newTabURL;
newTabURL = "about:newtab";
}
// Check the new tab opened while in normal/private mode
@ -29,18 +35,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, "about:newtab", "No custom newtab url is set");
is(aboutNewTabService.newTabURL, defaultURL, "No custom newtab url is set");
aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
aWindow.close();
aCallback()
aCallback();
});
});
}
@ -72,7 +78,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;
}

View File

@ -9,13 +9,19 @@
* as the nodePrincipal match the URL in the URL bar.
*/
const ABOUT_NEWTAB_URI = "about:newtab";
const PREF_URI = "http://example.com/browser/browser/base/content/test/newtab/external_newtab.html";
/* globals Cc, Ci, ok, is, content, TestRunner, addNewTabPageTab, gWindow, Services, info */
/* exported runTests */
"use strict";
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;
@ -34,7 +40,7 @@ function testPref() {
// reset to about:newtab and perform sanity check
aboutNewTabService.resetNewTabURL();
is(aboutNewTabService.newTabURL, ABOUT_NEWTAB_URI,
is(aboutNewTabService.newTabURL, DEFAULT_URI,
"sanity check: resetting the URL to about:newtab should return about:newtab");
// remove the tab and move on

View File

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

View File

@ -136,9 +136,6 @@ 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)

View File

@ -46,5 +46,3 @@ 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}

View File

@ -86,13 +86,9 @@ static RedirEntry kRedirMap[] = {
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::ENABLE_INDEXED_DB },
{ "newtab", "chrome://browser/content/newtab/newTab.xhtml",
// the newtab's actual URL will be determined when the channel is created
{ "newtab", "about:blank",
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",
@ -162,18 +158,12 @@ 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()) {

View File

@ -1693,7 +1693,7 @@ CustomizeMode.prototype = {
let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
let originArea = this._getCustomizableParent(draggedWrapper);
if (this._dragSizeMap) {
this._dragSizeMap.clear();
this._dragSizeMap = new WeakMap();
}
// Do nothing if the target area or origin area are not customizable.
if (!targetArea || !originArea) {

View File

@ -410,7 +410,7 @@ var DragPositionManager = {
},
stop: function() {
gManagers.clear();
gManagers = new WeakMap();
},
getManagerForArea: function(aArea) {

View File

@ -51,7 +51,7 @@ add_task(function* () {
windowId: activeWindow,
active: true,
pinned: false,
url: "about:newtab",
url: "chrome://browser/content/newtab/newTab.xhtml",
};
let tests = [
@ -65,7 +65,7 @@ add_task(function* () {
},
{
create: {},
result: { url: "about:newtab" },
result: { url: "chrome://browser/content/newtab/newTab.xhtml" },
},
{
create: { active: false },

View File

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

View File

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

View File

@ -17,6 +17,7 @@ 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"],

View File

@ -2,13 +2,14 @@
* 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, Deprecated, aboutNewTabService*/
/* exported NewTabURL */
"use strict";
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
const {utils: Cu} = Components;
this.EXPORTED_SYMBOLS = [ "NewTabURL" ];
this.EXPORTED_SYMBOLS = ["NewTabURL"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",

View File

@ -1,301 +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 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;
},
};

View File

@ -1,141 +0,0 @@
/* 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);
}
};

View File

@ -1,766 +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/. */
"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
};

View File

@ -0,0 +1,190 @@
/*
* 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) {
if (aNewTabURL === "about:newtab") {
// avoid infinite redirects in case one sets the URL to about:newtab
this.resetNewTabURL();
return;
}
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]);

View File

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

View File

@ -0,0 +1,58 @@
/* 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();
};

View File

@ -1,46 +1,57 @@
/* globals XPCOMUtils, Task, RemoteAboutNewTab, RemoteNewTabLocation, ok */
/* globals XPCOMUtils, aboutNewTabService, Services */
"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, "RemoteNewTabLocation",
"resource:///modules/RemoteNewTabLocation.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteAboutNewTab",
"resource:///modules/RemoteAboutNewTab.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemotePageManager",
"resource://gre/modules/RemotePageManager.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/dummy_page.html";
const NEWTAB_URL = "about:remote-newtab";
let tests = [];
/*
* Tests that:
* 1. overriding the RemoteNewTabPageLocation url causes a remote newtab page
* to load with the new url.
* 2. Messages pass between remote page <--> newTab.js <--> RemoteAboutNewTab.js
*/
tests.push(Task.spawn(function* testMessage() {
yield new Promise(resolve => {
RemoteAboutNewTab.pageListener.addMessageListener("NewTab:testMessage", () => {
ok(true, "message received");
resolve();
});
});
}));
add_task(function* open_newtab() {
RemoteNewTabLocation.override(TEST_URL);
ok(RemoteNewTabLocation.href === TEST_URL, "RemoteNewTabLocation has been overridden");
let 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");
let tabOptions = {
gBrowser,
url: NEWTAB_URL,
url: "about:newtab",
};
for (let test of tests) {
yield BrowserTestUtils.withNewTab(tabOptions, function* (browser) { // jshint ignore:line
yield test;
}); // jshint ignore:line
}
yield BrowserTestUtils.withNewTab(tabOptions, function* (browser) {
Assert.equal(TEST_URL, browser.contentWindow.location, `New tab should open to ${TEST_URL}`);
});
});
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);
});
}

View File

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

View File

@ -1,38 +1,150 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* globals Services, XPCOMUtils, NewTabPrefsProvider, Preferences, aboutNewTabService */
"use strict";
Components.utils.import("resource://gre/modules/Services.jsm");
let aboutNewTabService = Components.classes["@mozilla.org/browser/aboutnewtab-service;1"]
.getService(Components.interfaces.nsIAboutNewTabService);
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");
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* () {
Assert.equal(aboutNewTabService.newTabURL, "about:newtab", "Default newtab URL should be about:newtab");
NewTabPrefsProvider.prefs.init();
let notificationPromise;
Services.prefs.setBoolPref("browser.newtabpage.remote", false);
let localChromeURL = "chrome://browser/content/newtab/newTab.xhtml";
// tests default is the local newtab resource
Assert.equal(aboutNewTabService.newTabURL, localChromeURL,
`Default newtab URL should be ${localChromeURL}`);
// test the newtab service does not go in a circular redirect
aboutNewTabService.newTabURL = "about:newtab";
Assert.equal(aboutNewTabService.newTabURL, localChromeURL,
"Newtab URL avoids a circular redirect by setting to the default URL");
let url = "http://example.com/";
let notificationPromise = promiseNewtabURLNotification(url);
notificationPromise = nextChangeNotificationPromise(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 = promiseNewtabURLNotification("about:newtab");
notificationPromise = nextChangeNotificationPromise("chrome://browser/content/newtab/newTab.xhtml");
aboutNewTabService.resetNewTabURL();
yield notificationPromise;
Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
Assert.equal(aboutNewTabService.newTabURL, "about:newtab", "Newtab URL should be the about:newtab");
Assert.equal(aboutNewTabService.newTabURL, "chrome://browser/content/newtab/newTab.xhtml",
"Newtab URL should be the default");
// change newtab page to remote
Services.prefs.setBoolPref("browser.newtabpage.remote", true);
Assert.equal(aboutNewTabService.newTabURL, "about:remote-newtab", "Newtab URL should be the about:remote-newtab");
let remoteHref = aboutNewTabService.generateRemoteURL();
Assert.equal(aboutNewTabService.newTabURL, remoteHref, "Newtab URL should be the default remote URL");
Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
NewTabPrefsProvider.prefs.uninit();
});
function promiseNewtabURLNotification(aNewURL) {
/**
* 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) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint unused:false
Services.obs.removeObserver(observer, aTopic);
Assert.equal(aData, aNewURL, "Data for newtab-url-changed notification should be new URL.");
Assert.equal(aData, aNewURL, testMessage);
resolve();
}, "newtab-url-changed", false);
});

View File

@ -1,13 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* globals Services, NewTabURL, XPCOMUtils, aboutNewTabService */
"use strict";
Components.utils.import("resource:///modules/NewTabURL.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/NewTabURL.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
add_task(function* () {
Assert.equal(NewTabURL.get(), "about:newtab", "Default newtab URL should be about:newtab");
let defaultURL = aboutNewTabService.newTabURL;
Services.prefs.setBoolPref("browser.newtabpage.remote", false);
Assert.equal(NewTabURL.get(), defaultURL, `Default newtab URL should be ${defaultURL}`);
let url = "http://example.com/";
let notificationPromise = promiseNewtabURLNotification(url);
NewTabURL.override(url);
@ -15,21 +27,24 @@ add_task(function* () {
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");
notificationPromise = promiseNewtabURLNotification(defaultURL);
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");
Assert.equal(NewTabURL.get(), defaultURL, "Newtab URL should be the default");
// change newtab page to remote
NewTabPrefsProvider.prefs.init();
Services.prefs.setBoolPref("browser.newtabpage.remote", true);
Assert.equal(NewTabURL.get(), "about:remote-newtab", "Newtab URL should be the about:remote-newtab");
let remoteURL = aboutNewTabService.generateRemoteURL();
Assert.equal(NewTabURL.get(), remoteURL, `Newtab URL should be ${remoteURL}`);
Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
NewTabPrefsProvider.prefs.uninit();
});
function promiseNewtabURLNotification(aNewURL) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint ignore:line
Services.obs.removeObserver(observer, aTopic);
Assert.equal(aData, aNewURL, "Data for newtab-url-changed notification should be new URL.");
resolve();

View File

@ -1,148 +0,0 @@
/* 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);
});
}

View File

@ -1,375 +0,0 @@
/* 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,
};
}

View File

@ -8,5 +8,3 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_NewTabPrefsProvider.js]
[test_NewTabURL.js]
[test_PlacesProvider.js]
[test_RemoteNewTabLocation.js]
[test_RemoteNewTabUtils.js]

View File

@ -13,9 +13,6 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
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");
@ -28,16 +25,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.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, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
"resource:///modules/UITour.jsm");
@ -188,14 +177,15 @@ 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";
@ -778,12 +768,7 @@ BrowserGlue.prototype = {
NewTabUtils.links.addProvider(DirectoryLinksProvider);
AboutNewTab.init();
if(!AppConstants.RELEASE_BUILD) {
RemoteNewTabUtils.init();
RemoteNewTabUtils.links.addProvider(DirectoryLinksProvider);
RemoteAboutNewTab.init();
NewTabPrefsProvider.prefs.init();
}
NewTabPrefsProvider.prefs.init();
SessionStore.init();
BrowserUITelemetry.init();
@ -1100,10 +1085,7 @@ BrowserGlue.prototype = {
CustomizationTabPreloader.uninit();
WebappManager.uninit();
if (!AppConstants.RELEASE_BUILD) {
RemoteAboutNewTab.uninit();
NewTabPrefsProvider.prefs.uninit();
}
NewTabPrefsProvider.prefs.uninit();
AboutNewTab.uninit();
#ifdef NIGHTLY_BUILD
if (Services.prefs.getBoolPref("dom.identity.enabled")) {
@ -2480,50 +2462,6 @@ 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 = {
@ -3338,7 +3276,7 @@ var E10SAccessibilityCheck = {
},
};
var components = [BrowserGlue, ContentPermissionPrompt, AboutNewTabService];
var components = [BrowserGlue, ContentPermissionPrompt];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);

View File

@ -1,32 +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(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();
};

View File

@ -230,7 +230,7 @@ FrameTreeInternal.prototype = {
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
// Clear the list of frames until we can recollect it.
this._frames.clear();
this._frames = new WeakMap();
// Notify observers that the frame tree has been reset.
this.notifyObservers("onFrameTreeReset");

View File

@ -18,9 +18,14 @@ const TAB_STATE_NEEDS_RESTORE = 1;
const TAB_STATE_RESTORING = 2;
const TAB_STATE_WILL_RESTORE = 3;
// A new window has just been restored. At this stage, tabs are generally
// not restored.
const NOTIFY_SINGLE_WINDOW_RESTORED = "sessionstore-single-window-restored";
const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
const NOTIFY_LAST_SESSION_CLEARED = "sessionstore-last-session-cleared";
const NOTIFY_RESTORING_ON_STARTUP = "sessionstore-restoring-on-startup";
const NOTIFY_INITIATING_MANUAL_RESTORE = "sessionstore-initiating-manual-restore";
const NOTIFY_TAB_RESTORED = "sessionstore-debug-tab-restored"; // WARNING: debug-only
@ -1179,6 +1184,9 @@ var SessionStoreInternal = {
let initialState = this.initSession();
this._sessionInitialized = true;
if (initialState) {
Services.obs.notifyObservers(null, NOTIFY_RESTORING_ON_STARTUP, "");
}
TelemetryStopwatch.start("FX_SESSION_RESTORE_STARTUP_ONLOAD_INITIAL_WINDOW_MS");
this.initializeWindow(aWindow, initialState);
TelemetryStopwatch.finish("FX_SESSION_RESTORE_STARTUP_ONLOAD_INITIAL_WINDOW_MS");
@ -1567,7 +1575,7 @@ var SessionStoreInternal = {
}
this._clearRestoringWindows();
this._saveableClosedWindowData.clear();
this._saveableClosedWindowData = new WeakSet();
},
/**
@ -2316,6 +2324,8 @@ var SessionStoreInternal = {
throw Components.Exception("Last session can not be restored");
}
Services.obs.notifyObservers(null, NOTIFY_INITIATING_MANUAL_RESTORE, "");
// First collect each window with its id...
let windows = {};
this._forEachBrowserWindow(function(aWindow) {
@ -3017,6 +3027,9 @@ var SessionStoreInternal = {
TelemetryStopwatch.finish("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
this._setWindowStateReady(aWindow);
Services.obs.notifyObservers(aWindow, NOTIFY_SINGLE_WINDOW_RESTORED, "");
this._sendRestoreCompletedNotifications();
},
@ -4317,7 +4330,7 @@ var DirtyWindows = {
},
clear: function (window) {
this._data.clear();
this._data = new WeakMap();
}
};

View File

@ -0,0 +1,200 @@
/* 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 = ["StartupPerformance"];
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
"resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
const COLLECT_RESULTS_AFTER_MS = 10000;
const TOPICS = ["sessionstore-restoring-on-startup", "sessionstore-initiating-manual-restore"];
this.StartupPerformance = {
// Instant at which we have started restoration (notification "sessionstore-restoring-on-startup")
_startTimeStamp: null,
// Latest instant at which we have finished restoring a tab (DOM event "SSTabRestored")
_latestRestoredTimeStamp: null,
// A promise resolved once we have finished restoring all the startup tabs.
_promiseFinished: null,
// Function `resolve()` for `_promiseFinished`.
_resolveFinished: null,
// A timer
_deadlineTimer: null,
// `true` once the timer has fired
_hasFired: false,
// Statistics on the session we need to restore.
_totalNumberOfEagerTabs: 0,
_totalNumberOfTabs: 0,
_totalNumberOfWindows: 0,
init: function() {
for (let topic of TOPICS) {
Services.obs.addObserver(this, topic, false);
}
},
// Called when restoration starts.
// Record the start timestamp, setup the timer and `this._promiseFinished`.
// Behavior is unspecified if there was already an ongoing measure.
_onRestorationStarts: function(isAutoRestore) {
this._startTimeStamp = Date.now();
this._totalNumberOfEagerTabs = 0;
this._totalNumberOfTabs = 0;
this._totalNumberOfWindows = 0;
// While we may restore several sessions in a single run of the browser,
// that's a very unusual case, and not really worth measuring, so let's
// stop listening for further restorations.
for (let topic of TOPICS) {
Services.obs.removeObserver(this, topic);
}
Services.obs.addObserver(this, "sessionstore-single-window-restored", false);
this._promiseFinished = new Promise(resolve => {
this._resolveFinished = resolve;
});
this._promiseFinished.then(() => {
try {
if (!this._latestRestoredTimeStamp) {
// Apparently, we haven't restored any tab.
return;
}
// Once we are done restoring tabs, update Telemetry.
let histogramName = isAutoRestore ?
"FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS" :
"FX_SESSION_RESTORE_MANUAL_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS";
let histogram = Services.telemetry.getHistogramById(histogramName);
let delta = this._latestRestoredTimeStamp - this._startTimeStamp;
histogram.add(delta);
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_EAGER_TABS_RESTORED").add(this._totalNumberOfEagerTabs);
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_TABS_RESTORED").add(this._totalNumberOfTabs);
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_WINDOWS_RESTORED").add(this._totalNumberOfWindows);
// Reset
this._startTimeStamp = null;
} catch (ex) {
console.error("StartupPerformance: error after resolving promise", ex);
}
});
},
_startTimer: function() {
if (this._hasFired) {
return;
}
if (this._deadlineTimer) {
clearTimeout(this._deadlineTimer);
}
this._deadlineTimer = setTimeout(() => {
try {
this._resolveFinished();
} catch (ex) {
console.error("StartupPerformance: Error in timeout handler", ex);
} finally {
// Clean up.
this._deadlineTimer = null;
this._hasFired = true;
this._resolveFinished = null;
Services.obs.removeObserver(this, "sessionstore-single-window-restored");
}
}, COLLECT_RESULTS_AFTER_MS);
},
observe: function(subject, topic, details) {
try {
switch (topic) {
case "sessionstore-restoring-on-startup":
this._onRestorationStarts(true);
break;
case "sessionstore-initiating-manual-restore":
this._onRestorationStarts(false);
break;
case "sessionstore-single-window-restored": {
// Session Restore has just opened a window with (initially empty) tabs.
// Some of these tabs will be restored eagerly, while others will be
// restored on demand. The process becomes usable only when all windows
// have finished restored their eager tabs.
//
// While it would be possible to track the restoration of each tab
// from within SessionRestore to determine exactly when the process
// becomes usable, experience shows that this is too invasive. Rather,
// we employ the following heuristic:
// - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect
// will be triggered only once all tabs have been restored;
// - whenever we restore a new window (hence a bunch of eager tabs),
// we postpone the timer to ensure that the new eager tabs have
// `COLLECT_RESULTS_AFTER_MS` to be restored;
// - whenever a tab is restored, we update
// `this._latestRestoredTimeStamp`;
// - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version
// of `this._latestRestoredTimeStamp`, and use it to determine the
// entire duration of the collection.
//
// Note that this heuristic may be inaccurate if a user clicks
// immediately on a restore-on-demand tab before the end of
// `COLLECT_RESULTS_AFTER_MS`. We assume that this will not
// affect too much the results.
//
// Reset the delay, to give the tabs a little (more) time to restore.
this._startTimer();
this._totalNumberOfWindows += 1;
// Observe the restoration of all tabs. We assume that all tabs of this
// window will have been restored before `COLLECT_RESULTS_AFTER_MS`.
// The last call to `observer` will let us determine how long it took
// to reach that point.
let win = subject;
let observer = () => {
this._latestRestoredTimeStamp = Date.now();
this._totalNumberOfEagerTabs += 1;
};
win.gBrowser.tabContainer.addEventListener("SSTabRestored", observer);
this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;
// Once we have finished collecting the results, clean up the observers.
this._promiseFinished.then(() => {
if (!win.gBrowser.tabContainer) {
// May be undefined during shutdown and/or some tests.
return;
}
win.gBrowser.tabContainer.removeEventListener("SSTabRestored", observer);
});
}
break;
default:
throw new Error(`Unexpected topic ${topic}`);
}
} catch (ex) {
console.error("StartupPerformance error", ex, ex.stack);
throw ex;
}
}
};

View File

@ -112,6 +112,7 @@ function updateTabListVisibility() {
}
function restoreSession() {
Services.obs.notifyObservers(null, "sessionstore-initiating-manual-restore", "");
document.getElementById("errorTryAgain").disabled = true;
if (isTreeViewVisible()) {

View File

@ -41,6 +41,7 @@ EXTRA_JS_MODULES.sessionstore = [
'SessionStore.jsm',
'SessionWorker.js',
'SessionWorker.jsm',
'StartupPerformance.jsm',
'TabAttributes.jsm',
'TabState.jsm',
'TabStateCache.jsm',

View File

@ -45,6 +45,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "StartupPerformance",
"resource:///modules/sessionstore/StartupPerformance.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
"resource://gre/modules/CrashMonitor.jsm");
@ -96,6 +98,7 @@ SessionStartup.prototype = {
*/
init: function sss_init() {
Services.obs.notifyObservers(null, "sessionstore-init-started", null);
StartupPerformance.init();
// do not need to initialize anything in auto-started private browsing sessions
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {

View File

@ -93,6 +93,9 @@ this.UITour = {
urlbarCapture: new WeakMap(),
appMenuOpenForAnnotation: new Set(),
availableTargetsCache: new WeakMap(),
clearAvailableTargetsCache() {
this.availableTargetsCache = new WeakMap();
},
_annotationPanelMutationObservers: new WeakMap(),
@ -285,7 +288,7 @@ this.UITour = {
"onAreaReset",
];
CustomizableUI.addListener(listenerMethods.reduce((listener, method) => {
listener[method] = () => this.availableTargetsCache.clear();
listener[method] = () => this.clearAvailableTargetsCache();
return listener;
}, {}));
},
@ -1582,7 +1585,7 @@ this.UITour = {
popup.addEventListener("popuphidden", this.onPanelHidden);
popup.setAttribute("noautohide", true);
this.availableTargetsCache.clear();
this.clearAvailableTargetsCache();
if (popup.state == "open") {
if (aOpenCallback) {
@ -1611,7 +1614,7 @@ this.UITour = {
panel.setAttribute("noautohide", true);
if (panel.state != "open") {
this.recreatePopup(panel);
this.availableTargetsCache.clear();
this.clearAvailableTargetsCache();
}
// An event object is expected but we don't want to toggle the panel with a click if the panel
@ -1731,7 +1734,7 @@ this.UITour = {
onPanelHidden: function(aEvent) {
aEvent.target.removeAttribute("noautohide");
UITour.recreatePopup(aEvent.target);
UITour.availableTargetsCache.clear();
UITour.clearAvailableTargetsCache();
},
recreatePopup: function(aPanel) {

View File

@ -940,7 +940,7 @@ var MozLoopServiceInternal = {
let window = chatbox.contentWindow;
function socialFrameChanged(eventName) {
UITour.availableTargetsCache.clear();
UITour.clearAvailableTargetsCache();
UITour.notify(eventName);
if (eventName == "Loop:ChatWindowDetached" || eventName == "Loop:ChatWindowAttached") {

View File

@ -919,7 +919,7 @@ var START_TIME = performance.now();
"undefined" !== typeof ShumwayCom && ShumwayCom.getWeakMapKeys ? (this._map = new WeakMap, this._id = 0, this._newAdditions = []) : this._list = [];
}
e.prototype.clear = function() {
this._map ? this._map.clear() : this._list.length = 0;
this._map ? this._map = new WeakMap() : this._list.length = 0;
};
e.prototype.push = function(g) {
this._map ? (this._map.set(g, this._id++), this._newAdditions.forEach(function(d) {

View File

@ -920,7 +920,7 @@ var START_TIME = performance.now();
"undefined" !== typeof ShumwayCom && ShumwayCom.getWeakMapKeys ? (this._map = new WeakMap, this._id = 0, this._newAdditions = []) : this._list = [];
}
a.prototype.clear = function() {
this._map ? this._map.clear() : this._list.length = 0;
this._map ? this._map = new WeakMap() : this._list.length = 0;
};
a.prototype.push = function(a) {
this._map ? (this._map.set(a, this._id++), this._newAdditions.forEach(function(c) {

View File

@ -387,6 +387,9 @@
@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

View File

@ -138,7 +138,8 @@ function handleGUMRequest(aSubject, aTopic, aData) {
// and allow the user to plug in a device, instead of immediately failing.
denyGUMRequest({callID: aSubject.callID}, error);
},
aSubject.innerWindowID);
aSubject.innerWindowID,
aSubject.callID);
}
function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSecure) {

View File

@ -17,7 +17,6 @@ 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");
@ -32,13 +31,6 @@ 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();
});
@ -767,12 +759,6 @@ var DirectoryLinksProvider = {
NewTabUtils.placesProvider.addObserver(this);
NewTabUtils.links.addObserver(this);
// 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);

View File

@ -50,6 +50,8 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PageThumbs.jsm");
// Pref to enable/disable preview-per-tab
const TOGGLE_PREF_NAME = "browser.taskbar.previews.enable";
@ -75,9 +77,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "faviconSvc",
// nsIURI -> imgIContainer
function _imageFromURI(doc, uri, privateMode, callback) {
let channel = ioSvc.newChannelFromURI2(uri,
doc,
null, // aLoadingPrincipal
null, // aTriggeringPrincipal
null,
Services.scriptSecurityManager.getSystemPrincipal(),
null,
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE);
try {
@ -128,12 +130,14 @@ function snapRectAtScale(r, scale) {
//// PreviewController
/*
* This class manages the behavior of the preview.
*
* To give greater performance when drawing, the dirty areas of the content
* window are tracked and drawn on demand into a canvas of the same size.
* This provides a great increase in responsiveness when drawing a preview
* for unchanged (or even only slightly changed) tabs.
* This class manages the behavior of thumbnails and previews. It has the following
* responsibilities:
* 1) responding to requests from Windows taskbar for a thumbnail or window
* preview.
* 2) listens for dom events that result in a thumbnail or window preview needing
* to be refresh, and communicates this to the taskbar.
* 3) Handles querying and returning to the taskbar new thumbnail or window
* preview images through PageThumbs.
*
* @param win
* The TabWindow (see below) that owns the preview that this controls
@ -146,148 +150,92 @@ function PreviewController(win, tab) {
this.linkedBrowser = tab.linkedBrowser;
this.preview = this.win.createTabPreview(this);
this.linkedBrowser.addEventListener("MozAfterPaint", this, false);
this.linkedBrowser.addEventListener("resize", this, false);
this.tab.addEventListener("TabAttrModified", this, false);
XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
let canvas = this.win.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let canvas = PageThumbs.createCanvas();
canvas.mozOpaque = true;
return canvas;
});
XPCOMUtils.defineLazyGetter(this, "dirtyRegion",
function () {
let dirtyRegion = Cc["@mozilla.org/gfx/region;1"]
.createInstance(Ci.nsIScriptableRegion);
dirtyRegion.init();
return dirtyRegion;
});
XPCOMUtils.defineLazyGetter(this, "winutils",
function () {
let win = tab.linkedBrowser.contentWindow;
return win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
});
}
PreviewController.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
Ci.nsIDOMEventListener]),
destroy: function () {
this.tab.removeEventListener("TabAttrModified", this, false);
this.linkedBrowser.removeEventListener("resize", this, false);
this.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
// Break cycles, otherwise we end up leaking the window with everything
// attached to it.
delete this.win;
delete this.preview;
delete this.dirtyRegion;
},
get wrappedJSObject() {
return this;
},
get dirtyRects() {
let rectstream = this.dirtyRegion.getRects();
if (!rectstream)
return [];
let rects = [];
for (let i = 0; i < rectstream.length; i+= 4) {
let r = {x: rectstream[i],
y: rectstream[i+1],
width: rectstream[i+2],
height: rectstream[i+3]};
rects.push(r);
}
return rects;
},
// Resizes the canvasPreview to 0x0, essentially freeing its memory.
// updateCanvasPreview() will detect the size mismatch as a resize event
// the next time it is called.
resetCanvasPreview: function () {
this.resizeCanvasPreview(0, 0);
this.canvasPreview.width = 0;
this.canvasPreview.height = 0;
},
resizeCanvasPreview: function (width, height) {
this.canvasPreview.width = width;
this.canvasPreview.height = height;
/**
* Set the canvas dimensions.
*/
resizeCanvasPreview: function (aRequestedWidth, aRequestedHeight) {
this.canvasPreview.width = aRequestedWidth;
this.canvasPreview.height = aRequestedHeight;
},
get wasResizedSinceLastPreview () {
let bx = this.linkedBrowser.boxObject;
return bx.width != this.canvasPreview.width ||
bx.height != this.canvasPreview.height;
},
get zoom() {
// Note that winutils.fullZoom accounts for "quantization" of the zoom factor
// from nsIContentViewer due to conversion through appUnits.
// We do -not- want screenPixelsPerCSSPixel here, because that would -also-
// incorporate any scaling that is applied due to hi-dpi resolution options.
return this.winutils.fullZoom;
return this.tab.linkedBrowser.fullZoom;
},
// Updates the controller's canvas with the parts of the <browser> that need
// to be redrawn.
updateCanvasPreview: function () {
let win = this.linkedBrowser.contentWindow;
let bx = this.linkedBrowser.boxObject;
// If we resized then we need to flush layout so that the previews are up
// to date. Layout flushing for resizes is deferred for background tabs so
// we may need to force it. (bug 526620)
let flushLayout = this.wasResizedSinceLastPreview;
// Check for resize
if (flushLayout) {
// Invalidate the entire area and repaint
this.onTabPaint({left:0, top:0, right:win.innerWidth, bottom:win.innerHeight});
this.resizeCanvasPreview(bx.width, bx.height);
}
get screenPixelsPerCSSPixel() {
let chromeWin = this.tab.ownerGlobal;
let windowUtils = chromeWin.getInterface(Ci.nsIDOMWindowUtils);
return windowUtils.screenPixelsPerCSSPixel;
},
// Draw dirty regions
let ctx = this.canvasPreview.getContext("2d");
let scale = this.zoom;
get browserDims() {
return this.tab.linkedBrowser.getBoundingClientRect();
},
let flags = this.canvasPreviewFlags;
if (flushLayout)
flags &= ~Ci.nsIDOMCanvasRenderingContext2D.DRAWWINDOW_DO_NOT_FLUSH;
cacheBrowserDims: function () {
let dims = this.browserDims;
this._cachedWidth = dims.width;
this._cachedHeight = dims.height;
},
// The dirty region may include parts that are offscreen so we clip to the
// canvas area.
this.dirtyRegion.intersectRect(0, 0, win.innerWidth, win.innerHeight);
this.dirtyRects.forEach(function (r) {
// We need to snap the rectangle to be pixel aligned in the destination
// coordinate space. Otherwise natively themed widgets might not draw.
snapRectAtScale(r, scale);
let x = r.x;
let y = r.y;
let width = r.width;
let height = r.height;
ctx.save();
ctx.scale(scale, scale);
ctx.translate(x, y);
ctx.drawWindow(win, x, y, width, height, "white", flags);
ctx.restore();
});
this.dirtyRegion.setToRect(0,0,0,0);
testCacheBrowserDims: function () {
let dims = this.browserDims;
return this._cachedWidth == dims.width &&
this._cachedHeight == dims.height;
},
/**
* Capture a new thumbnail image for this preview. Called by the controller
* in response to a request for a new thumbnail image.
*/
updateCanvasPreview: function (aFullScale, aCallback) {
// Update our cached browser dims so that delayed resize
// events don't trigger another invalidation if this tab becomes active.
this.cacheBrowserDims();
PageThumbs.captureToCanvas(this.linkedBrowser, this.canvasPreview,
aCallback, { fullScale: aFullScale });
// If we're updating the canvas, then we're in the middle of a peek so
// don't discard the cache of previews.
AeroPeek.resetCacheTimer();
},
onTabPaint: function (rect) {
let x = Math.floor(rect.left),
y = Math.floor(rect.top),
width = Math.ceil(rect.right) - x,
height = Math.ceil(rect.bottom) - y;
this.dirtyRegion.unionRect(x, y, width, height);
},
updateTitleAndTooltip: function () {
let title = this.win.tabbrowser.getWindowTitleForBrowser(this.linkedBrowser);
this.preview.title = title;
@ -295,72 +243,90 @@ PreviewController.prototype = {
},
//////////////////////////////////////////////////////////////////////////////
//// nsITaskbarPreviewController
//// nsITaskbarPreviewController
// window width and height, not browser
get width() {
return this.win.width;
},
// window width and height, not browser
get height() {
return this.win.height;
},
get thumbnailAspectRatio() {
let boxObject = this.tab.linkedBrowser.boxObject;
let browserDims = this.browserDims;
// Avoid returning 0
let tabWidth = boxObject.width || 1;
let tabWidth = browserDims.width || 1;
// Avoid divide by 0
let tabHeight = boxObject.height || 1;
let tabHeight = browserDims.height || 1;
return tabWidth / tabHeight;
},
drawPreview: function (ctx) {
this.win.tabbrowser.previewTab(this.tab, () => this.previewTabCallback(ctx));
/**
* Responds to taskbar requests for window previews. Returns the results asynchronously
* through updateCanvasPreview.
*
* @param aTaskbarCallback nsITaskbarPreviewCallback results callback
*/
requestPreview: function (aTaskbarCallback) {
// Grab a high res content preview
this.resetCanvasPreview();
this.updateCanvasPreview(true, (aPreviewCanvas) => {
let winWidth = this.win.width;
let winHeight = this.win.height;
// We must avoid having the frame drawn around the window. See bug 520807
return false;
let composite = PageThumbs.createCanvas();
// Use transparency, Aero glass is drawn black without it.
composite.mozOpaque = false;
let ctx = composite.getContext('2d');
let scale = this.screenPixelsPerCSSPixel / this.zoom;
composite.width = winWidth * scale;
composite.height = winHeight * scale;
ctx.save();
ctx.scale(scale, scale);
// Draw chrome. Note we currently do not get scrollbars for remote frames
// in the image above.
ctx.drawWindow(this.win.win, 0, 0, winWidth, winHeight, "rgba(0,0,0,0)");
// Draw the content are into the composite canvas at the right location.
ctx.drawImage(aPreviewCanvas, this.browserDims.x, this.browserDims.y,
aPreviewCanvas.width, aPreviewCanvas.height);
ctx.restore();
// Deliver the resulting composite canvas to Windows
this.win.tabbrowser.previewTab(this.tab, function () {
aTaskbarCallback.done(composite, false);
});
});
},
previewTabCallback: function (ctx) {
// This will extract the resolution-scale component of the scaling we need,
// which should be applied to both chrome and content;
// the page zoom component is applied (to content only) within updateCanvasPreview.
let scale = this.winutils.screenPixelsPerCSSPixel / this.winutils.fullZoom;
ctx.save();
ctx.scale(scale, scale);
let width = this.win.width;
let height = this.win.height;
// Draw our toplevel window
ctx.drawWindow(this.win.win, 0, 0, width, height, "transparent");
// XXX (jfkthame): Pending tabs don't seem to draw with the proper scaling
// unless we use this block of code; but doing this for "normal" (loaded) tabs
// results in blurry rendering on hidpi systems, so we avoid it if possible.
// I don't understand why pending and loaded tabs behave differently here...
// (see bug 857061).
if (this.tab.hasAttribute("pending")) {
// Compositor, where art thou?
// Draw the tab content on top of the toplevel window
this.updateCanvasPreview();
let boxObject = this.linkedBrowser.boxObject;
ctx.translate(boxObject.x, boxObject.y);
ctx.drawImage(this.canvasPreview, 0, 0);
}
ctx.restore();
/**
* Responds to taskbar requests for tab thumbnails. Returns the results asynchronously
* through updateCanvasPreview.
*
* Note Windows requests a specific width and height here, if the resulting thumbnail
* does not match these dimensions thumbnail display will fail.
*
* @param aTaskbarCallback nsITaskbarPreviewCallback results callback
* @param aRequestedWidth width of the requested thumbnail
* @param aRequestedHeight height of the requested thumbnail
*/
requestThumbnail: function (aTaskbarCallback, aRequestedWidth, aRequestedHeight) {
this.resizeCanvasPreview(aRequestedWidth, aRequestedHeight);
this.updateCanvasPreview(false, (aThumbnailCanvas) => {
aTaskbarCallback.done(aThumbnailCanvas, false);
});
},
drawThumbnail: function (ctx, width, height) {
this.updateCanvasPreview();
let scale = width/this.linkedBrowser.boxObject.width;
ctx.scale(scale, scale);
ctx.drawImage(this.canvasPreview, 0, 0);
// Don't draw a frame around the thumbnail
return false;
},
//////////////////////////////////////////////////////////////////////////////
//// Event handling
onClose: function () {
this.win.tabbrowser.removeTab(this.tab);
@ -377,33 +343,9 @@ PreviewController.prototype = {
//// nsIDOMEventListener
handleEvent: function (evt) {
switch (evt.type) {
case "MozAfterPaint":
if (evt.originalTarget === this.linkedBrowser.contentWindow) {
let clientRects = evt.clientRects;
let length = clientRects.length;
for (let i = 0; i < length; i++) {
let r = clientRects.item(i);
this.onTabPaint(r);
}
}
this.preview.invalidate();
break;
case "TabAttrModified":
this.updateTitleAndTooltip();
break;
case "resize":
// We need to invalidate our window's other tabs' previews since layout
// due to resizing is delayed for background tabs. Note that this
// resize may not be the first after the main window has been resized -
// the user may be switching to our tab which forces the resize.
this.win.previews.forEach(function (p) {
let controller = p.controller.wrappedJSObject;
if (controller.wasResizedSinceLastPreview) {
controller.resetCanvasPreview();
p.invalidate();
}
});
break;
}
}
};
@ -423,16 +365,22 @@ XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
* This class monitors a browser window for changes to its tabs
*
* @param win
* The nsIDOMWindow browser window
* The nsIDOMWindow browser window
*/
function TabWindow(win) {
this.win = win;
this.tabbrowser = win.gBrowser;
this.cacheDims();
this.previews = new Map();
for (let i = 0; i < this.tabEvents.length; i++)
this.tabbrowser.tabContainer.addEventListener(this.tabEvents[i], this, false);
for (let i = 0; i < this.winEvents.length; i++)
this.win.addEventListener(this.winEvents[i], this, false);
this.tabbrowser.addTabsProgressListener(this);
AeroPeek.windows.push(this);
@ -447,6 +395,7 @@ function TabWindow(win) {
TabWindow.prototype = {
_enabled: false,
tabEvents: ["TabOpen", "TabClose", "TabSelect", "TabMove"],
winEvents: ["resize"],
destroy: function () {
this._destroying = true;
@ -454,6 +403,10 @@ TabWindow.prototype = {
let tabs = this.tabbrowser.tabs;
this.tabbrowser.removeTabsProgressListener(this);
for (let i = 0; i < this.winEvents.length; i++)
this.win.removeEventListener(this.winEvents[i], this, false);
for (let i = 0; i < this.tabEvents.length; i++)
this.tabbrowser.tabContainer.removeEventListener(this.tabEvents[i], this, false);
@ -472,6 +425,15 @@ TabWindow.prototype = {
return this.win.innerHeight;
},
cacheDims: function () {
this._cachedWidth = this.width;
this._cachedHeight = this.height;
},
testCacheDims: function () {
return this._cachedWidth == this.width && this._cachedHeight == this.height;
},
// Invoked when the given tab is added to this window
newTab: function (tab) {
let controller = new PreviewController(this, tab);
@ -493,7 +455,7 @@ TabWindow.prototype = {
preview.active = this.tabbrowser.selectedTab == controller.tab;
// Grab the default favicon
getFaviconAsImage(
controller.linkedBrowser.contentWindow.document,
null,
null,
PrivateBrowsingUtils.isWindowPrivate(this.win),
function (img) {
@ -577,15 +539,83 @@ TabWindow.prototype = {
case "TabMove":
this.updateTabOrdering();
break;
case "resize":
if (!AeroPeek._prefenabled)
return;
this.onResize();
break;
}
},
// Set or reset a timer that will invalidate visible thumbnails soon.
setInvalidationTimer: function () {
if (!this.invalidateTimer) {
this.invalidateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
}
this.invalidateTimer.cancel();
// delay 1 second before invalidating
this.invalidateTimer.initWithCallback(() => {
// invalidate every preview. note the internal implementation of
// invalidate ignores thumbnails that aren't visible.
this.previews.forEach(function (aPreview) {
let controller = aPreview.controller.wrappedJSObject;
if (!controller.testCacheBrowserDims()) {
controller.cacheBrowserDims();
aPreview.invalidate();
}
});
}, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
},
onResize: function () {
// Specific to a window.
// Call invalidate on each tab thumbnail so that Windows will request an
// updated image. However don't do this repeatedly across multiple resize
// events triggered during window border drags.
if (this.testCacheDims()) {
return;
}
// update the window dims on our TabWindow object.
this.cacheDims();
// invalidate soon
this.setInvalidationTimer();
},
invalidateTabPreview: function(aBrowser) {
for (let [tab, preview] of this.previews) {
if (aBrowser == tab.linkedBrowser) {
preview.invalidate();
break;
}
}
},
//// Browser progress listener
onLocationChange: function (aBrowser) {
// I'm not sure we need this, onStateChange does a really good job
// of picking up page changes.
//this.invalidateTabPreview(aBrowser);
},
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
this.invalidateTabPreview(aBrowser);
}
},
onLinkIconAvailable: function (aBrowser, aIconURL) {
let self = this;
getFaviconAsImage(
aBrowser.contentWindow.document,
aIconURL,PrivateBrowsingUtils.isWindowPrivate(this.win),
null,
aIconURL,
PrivateBrowsingUtils.isWindowPrivate(this.win),
function (img) {
let index = self.tabbrowser.browsers.indexOf(aBrowser);
// Only add it if we've found the index. The tab could have closed!

View File

@ -21,4 +21,4 @@ support-files =
[browser_SignInToWebsite.js]
skip-if = e10s # Bug 941426 - SignIntoWebsite.jsm not e10s friendly
[browser_taskbar_preview.js]
skip-if = os != win || e10s # Bug 666808 - AeroPeek support for e10s
skip-if = os != "win"

View File

@ -50,19 +50,6 @@ function test() {
ok(checkSelectedTab(), "Current tab is correctly selected");
});
let currentSelectedTab = gBrowser.selectedTab;
for (let idx of [1,2,3,4]) {
let preview = getPreviewForTab(gBrowser.tabs[0]);
let canvas = createThumbnailSurface(preview);
let ctx = canvas.getContext("2d");
preview.controller.drawThumbnail(ctx, canvas.width, canvas.height);
ok(currentSelectedTab.selected, "Drawing thumbnail does not change selection");
canvas = getCanvas(preview.controller.width, preview.controller.height);
ctx = canvas.getContext("2d");
preview.controller.drawPreview(ctx);
ok(currentSelectedTab.selected, "Drawing preview does not change selection");
}
// Close #4
getPreviewForTab(gBrowser.selectedTab).controller.onClose();
checkPreviews(3, "Expected number of previews after closing selected tab via controller");
@ -117,26 +104,4 @@ function test() {
function isTabSelected(idx) {
return gBrowser.tabs[idx].selected;
}
function createThumbnailSurface(p) {
let thumbnailWidth = 200,
thumbnailHeight = 120;
let ratio = p.controller.thumbnailAspectRatio;
if (thumbnailWidth/thumbnailHeight > ratio)
thumbnailWidth = thumbnailHeight * ratio;
else
thumbnailHeight = thumbnailWidth / ratio;
return getCanvas(thumbnailWidth, thumbnailHeight);
}
function getCanvas(width, height) {
let win = window.QueryInterface(Ci.nsIDOMWindow);
let doc = win.document;
let canvas = doc.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = width;
canvas.height = height;
return canvas;
}
}

View File

@ -19,6 +19,7 @@ ALL_HARNESSES = [
'mozbase',
'web-platform',
'talos',
'gtest',
]
PACKAGE_SPECIFIED_HARNESSES = [
@ -30,6 +31,11 @@ PACKAGE_SPECIFIED_HARNESSES = [
'talos',
]
# These packages are not present for every build configuration.
OPTIONAL_PACKAGES = [
'gtest',
]
def parse_args():
parser = ArgumentParser(description='Generate a test_packages.json file to tell automation which harnesses require which test packages.')
@ -46,6 +52,10 @@ def parse_args():
parser.add_argument("--%s" % harness, required=True,
action="store", dest=harness,
help="Name of the %s zip." % harness)
for harness in OPTIONAL_PACKAGES:
parser.add_argument("--%s" % harness, required=False,
action="store", dest=harness,
help="Name of the %s zip." % harness)
parser.add_argument("--dest-file", required=True,
action="store", dest="destfile",
help="Path to the output file to be written.")
@ -66,8 +76,10 @@ def generate_package_data(args):
harness_requirements = dict([(k, [tests_common]) for k in ALL_HARNESSES])
harness_requirements['jittest'].append(jsshell)
for harness in PACKAGE_SPECIFIED_HARNESSES:
pkg_name = getattr(args, harness)
for harness in PACKAGE_SPECIFIED_HARNESSES + OPTIONAL_PACKAGES:
pkg_name = getattr(args, harness, None)
if pkg_name is None:
continue
if args.use_short_names:
pkg_name = 'target.%s.tests.zip' % harness
harness_requirements[harness].append(pkg_name)

View File

@ -212,11 +212,14 @@ ifdef WRAP_LDFLAGS
NSS_EXTRA_LDFLAGS += $(WRAP_LDFLAGS)
endif
ifdef MOZ_GLUE_WRAP_LDFLAGS
# The SHARED_LIBS part is needed unconditionally on Android. It's not
# clear why this is the case, but see bug 1133073 (starting around
# comment #8) for context.
ifneq (,$(or $(MOZ_GLUE_WRAP_LDFLAGS), $(filter Android, $(OS_TARGET))))
NSS_EXTRA_LDFLAGS += $(SHARED_LIBS:$(DEPTH)%=$(MOZ_BUILD_ROOT)%) $(MOZ_GLUE_WRAP_LDFLAGS)
endif
ifneq (,$(WRAP_LDFLAGS)$(MOZ_GLUE_WRAP_LDFLAGS))
ifneq (,$(NSS_EXTRA_LDFLAGS))
DEFAULT_GMAKE_FLAGS += \
LDFLAGS='$(LDFLAGS) $(NSS_EXTRA_LDFLAGS)' \
DSO_LDOPTS='$(DSO_LDOPTS) $(LDFLAGS) $(NSS_EXTRA_LDFLAGS)' \

View File

@ -7271,7 +7271,6 @@ fi
dnl We need to wrap dlopen and related functions on Android because we use
dnl our own linker.
if test "$OS_TARGET" = Android; then
MOZ_GLUE_WRAP_LDFLAGS="${MOZ_GLUE_WRAP_LDFLAGS} -Wl,--wrap=PR_GetEnv,--wrap=PR_SetEnv"
if test "$MOZ_WIDGET_TOOLKIT" = gonk -a -n "$MOZ_NUWA_PROCESS"; then
MOZ_GLUE_WRAP_LDFLAGS="${MOZ_GLUE_WRAP_LDFLAGS} -Wl,--wrap=pthread_create,--wrap=epoll_wait,--wrap=poll,--wrap=pthread_cond_timedwait,--wrap=pthread_cond_wait,--wrap=epoll_create,--wrap=epoll_ctl,--wrap=close,--wrap=pthread_key_create,--wrap=pthread_key_delete,--wrap=socketpair,--wrap=pthread_self,--wrap=pthread_mutex_lock,--wrap=pthread_mutex_trylock,--wrap=pthread_join,--wrap=pipe,--wrap=pipe2"
fi

View File

@ -114,7 +114,7 @@ var SourceUtils = {
clearCache: function() {
this._labelsCache.clear();
this._groupsCache.clear();
this._minifiedCache.clear();
this._minifiedCache = new WeakMap();
},
/**

View File

@ -1509,7 +1509,6 @@ EventTooltip.prototype = {
editor.destroy();
}
this._tooltip.eventEditors.clear();
this._tooltip.eventEditors = null;
}

View File

@ -155,7 +155,7 @@ VariablesView.prototype = {
}
this._store.length = 0;
this._itemsByElement.clear();
this._itemsByElement = new WeakMap();
this._prevHierarchy = this._currHierarchy;
this._currHierarchy = new Map(); // Don't clear, this is just simple swapping.

View File

@ -28,6 +28,11 @@ class SerializedLoadContext
{
public:
SerializedLoadContext()
: mIsNotNull(false)
, mIsPrivateBitValid(false)
, mIsContent(false)
, mUsePrivateBrowsing(false)
, mUseRemoteTabs(false)
{
Init(nullptr);
}

View File

@ -233,6 +233,7 @@ nsSHistory::nsSHistory()
: mIndex(-1)
, mLength(0)
, mRequestedIndex(-1)
, mRootDocShell(nullptr)
{
// Add this new SHistory object to the list
PR_APPEND_LINK(this, &gSHistoryList);
@ -1004,6 +1005,7 @@ class TransactionAndDistance
public:
TransactionAndDistance(nsISHTransaction* aTrans, uint32_t aDist)
: mTransaction(aTrans)
, mLastTouched(0)
, mDistance(aDist)
{
mViewer = GetContentViewerForTransaction(aTrans);
@ -1017,7 +1019,6 @@ public:
shentryInternal->GetLastTouched(&mLastTouched);
} else {
NS_WARNING("Can't cast to nsISHEntryInternal?");
mLastTouched = 0;
}
}

View File

@ -33,6 +33,10 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSIALARMHALSERVICE
AlarmHalService()
: mAlarmEnabled(false)
{}
void Init();
static already_AddRefed<AlarmHalService> GetInstance();

View File

@ -11,8 +11,6 @@
#include "mozilla/AutoRestore.h"
#include "mozilla/AsyncEventDispatcher.h" // For AsyncEventDispatcher
#include "mozilla/Maybe.h" // For Maybe
#include "AnimationCommon.h" // For AnimationCollection,
// CommonAnimationManager
#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
#include "nsIDocument.h" // For nsIDocument
#include "nsIPresShell.h" // For nsIPresShell
@ -682,19 +680,12 @@ Animation::HasLowerCompositeOrderThan(const Animation& aOther) const
void
Animation::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
nsCSSPropertySet& aSetProperties,
bool& aStyleChanging)
nsCSSPropertySet& aSetProperties)
{
if (!mEffect) {
return;
}
AnimationPlayState playState = PlayState();
if (playState == AnimationPlayState::Running ||
playState == AnimationPlayState::Pending) {
aStyleChanging = true;
}
if (!IsInEffect()) {
return;
}
@ -734,6 +725,7 @@ Animation::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
// afterwards. This is purely to prevent visual flicker. Other behavior
// such as dispatching events continues to rely on the regular timeline time.
bool updatedHoldTime = false;
AnimationPlayState playState = PlayState();
{
AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
@ -1046,10 +1038,23 @@ Animation::FlushStyle() const
void
Animation::PostUpdate()
{
AnimationCollection* collection = GetCollection();
if (collection) {
collection->RequestRestyle(AnimationCollection::RestyleType::Layer);
nsPresContext* presContext = GetPresContext();
if (!presContext) {
return;
}
Element* targetElement;
nsCSSPseudoElements::Type targetPseudoType;
mEffect->GetTarget(targetElement, targetPseudoType);
if (!targetElement) {
return;
}
presContext->EffectCompositor()
->RequestRestyle(targetElement,
targetPseudoType,
EffectCompositor::RestyleType::Layer,
CascadeLevel());
}
void
@ -1160,27 +1165,6 @@ Animation::GetPresContext() const
return mEffect->GetPresContext();
}
AnimationCollection*
Animation::GetCollection() const
{
CommonAnimationManager* manager = GetAnimationManager();
if (!manager) {
return nullptr;
}
MOZ_ASSERT(mEffect,
"An animation with an animation manager must have an effect");
Element* targetElement;
nsCSSPseudoElements::Type targetPseudoType;
mEffect->GetTarget(targetElement, targetPseudoType);
MOZ_ASSERT(targetElement,
"An animation with an animation manager must have a target");
return manager->GetAnimationCollection(targetElement,
targetPseudoType,
false /* aCreateIfNeeded */);
}
void
Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
{

View File

@ -39,9 +39,7 @@ class nsPresContext;
namespace mozilla {
struct AnimationCollection;
class AnimValuesStyleRule;
class CommonAnimationManager;
namespace dom {
@ -307,18 +305,12 @@ public:
* if any.
* Any properties already contained in |aSetProperties| are not changed. Any
* properties that are changed are added to |aSetProperties|.
* |aStyleChanging| will be set to true if this animation expects to update
* the style rule on the next refresh driver tick as well (because it
* is running and has an effect to sample).
*/
void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
nsCSSPropertySet& aSetProperties,
bool& aStyleChanging);
nsCSSPropertySet& aSetProperties);
void NotifyEffectTimingUpdated();
AnimationCollection* GetCollection() const;
protected:
void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
void SilentlySetPlaybackRate(double aPlaybackRate);
@ -377,7 +369,6 @@ protected:
nsIDocument* GetRenderedDocument() const;
nsPresContext* GetPresContext() const;
virtual CommonAnimationManager* GetAnimationManager() const = 0;
RefPtr<AnimationTimeline> mTimeline;
RefPtr<KeyframeEffectReadOnly> mEffect;

View File

@ -11,9 +11,8 @@
#include "mozilla/dom/KeyframeEffect.h" // For KeyframeEffectReadOnly
#include "mozilla/AnimationUtils.h"
#include "mozilla/EffectSet.h"
#include "mozilla/InitializerList.h"
#include "mozilla/LayerAnimationInfo.h"
#include "AnimationCommon.h" // For AnimationCollection
#include "nsAnimationManager.h"
#include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetPresShellForContent
#include "nsCSSPropertySet.h"
#include "nsCSSProps.h"
@ -21,7 +20,7 @@
#include "nsLayoutUtils.h"
#include "nsRuleNode.h" // For nsRuleNode::ComputePropertiesOverridingAnimation
#include "nsTArray.h"
#include "nsTransitionManager.h"
#include "RestyleManager.h"
using mozilla::dom::Animation;
using mozilla::dom::Element;
@ -29,6 +28,27 @@ using mozilla::dom::KeyframeEffectReadOnly;
namespace mozilla {
NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor)
for (auto& elementSet : tmp->mElementsToRestyle) {
elementSet.Clear();
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EffectCompositor)
for (auto& elementSet : tmp->mElementsToRestyle) {
for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
CycleCollectionNoteChild(cb, iter.Key().mElement,
"EffectCompositor::mElementsToRestyle[]",
cb.Flags());
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(EffectCompositor, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(EffectCompositor, Release)
// Helper function to factor out the common logic from
// GetAnimationsForCompositor and HasAnimationsForCompositor.
//
@ -108,6 +128,232 @@ FindAnimationsForCompositor(const nsIFrame* aFrame,
return foundSome;
}
void
EffectCompositor::RequestRestyle(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
RestyleType aRestyleType,
CascadeLevel aCascadeLevel)
{
if (!mPresContext) {
// Pres context will be null after the effect compositor is disconnected.
return;
}
auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel];
PseudoElementHashKey key = { aElement, aPseudoType };
if (aRestyleType == RestyleType::Throttled &&
!elementsToRestyle.Contains(key)) {
elementsToRestyle.Put(key, false);
mPresContext->Document()->SetNeedStyleFlush();
} else {
// Get() returns 0 if the element is not found. It will also return
// false if the element is found but does not have a pending restyle.
bool hasPendingRestyle = elementsToRestyle.Get(key);
if (!hasPendingRestyle) {
PostRestyleForAnimation(aElement, aPseudoType, aCascadeLevel);
}
elementsToRestyle.Put(key, true);
}
if (aRestyleType == RestyleType::Layer) {
// Prompt layers to re-sync their animations.
mPresContext->RestyleManager()->IncrementAnimationGeneration();
EffectSet* effectSet =
EffectSet::GetEffectSet(aElement, aPseudoType);
if (effectSet) {
effectSet->UpdateAnimationGeneration(mPresContext);
}
}
}
void
EffectCompositor::PostRestyleForAnimation(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
CascadeLevel aCascadeLevel)
{
if (!mPresContext) {
return;
}
dom::Element* element = GetElementToRestyle(aElement, aPseudoType);
if (!element) {
return;
}
nsRestyleHint hint = aCascadeLevel == CascadeLevel::Transitions ?
eRestyle_CSSTransitions :
eRestyle_CSSAnimations;
mPresContext->PresShell()->RestyleForAnimation(element, hint);
}
void
EffectCompositor::PostRestyleForThrottledAnimations()
{
for (size_t i = 0; i < kCascadeLevelCount; i++) {
CascadeLevel cascadeLevel = CascadeLevel(i);
auto& elementSet = mElementsToRestyle[cascadeLevel];
for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
bool& postedRestyle = iter.Data();
if (postedRestyle) {
continue;
}
PostRestyleForAnimation(iter.Key().mElement,
iter.Key().mPseudoType,
cascadeLevel);
postedRestyle = true;
}
}
}
void
EffectCompositor::MaybeUpdateAnimationRule(dom::Element* aElement,
nsCSSPseudoElements::Type
aPseudoType,
CascadeLevel aCascadeLevel)
{
// First update cascade results since that may cause some elements to
// be marked as needing a restyle.
MaybeUpdateCascadeResults(aElement, aPseudoType);
auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel];
PseudoElementHashKey key = { aElement, aPseudoType };
if (!mPresContext || !elementsToRestyle.Contains(key)) {
return;
}
ComposeAnimationRule(aElement, aPseudoType, aCascadeLevel,
mPresContext->RefreshDriver()->MostRecentRefresh());
elementsToRestyle.Remove(key);
}
nsIStyleRule*
EffectCompositor::GetAnimationRule(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
CascadeLevel aCascadeLevel)
{
if (!mPresContext || !mPresContext->IsDynamic()) {
// For print or print preview, ignore animations.
return nullptr;
}
EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
if (!effectSet) {
return nullptr;
}
if (mPresContext->RestyleManager()->SkipAnimationRules()) {
return nullptr;
}
MaybeUpdateAnimationRule(aElement, aPseudoType, aCascadeLevel);
return effectSet->AnimationRule(aCascadeLevel);
}
/* static */ dom::Element*
EffectCompositor::GetElementToRestyle(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType)
{
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
return aElement;
}
nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
if (!primaryFrame) {
return nullptr;
}
nsIFrame* pseudoFrame;
if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
pseudoFrame = nsLayoutUtils::GetBeforeFrame(primaryFrame);
} else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
pseudoFrame = nsLayoutUtils::GetAfterFrame(primaryFrame);
} else {
NS_NOTREACHED("Should not try to get the element to restyle for a pseudo "
"other that :before or :after");
return nullptr;
}
if (!pseudoFrame) {
return nullptr;
}
return pseudoFrame->GetContent()->AsElement();
}
bool
EffectCompositor::HasPendingStyleUpdates() const
{
for (auto& elementSet : mElementsToRestyle) {
if (elementSet.Count()) {
return true;
}
}
return false;
}
bool
EffectCompositor::HasThrottledStyleUpdates() const
{
for (auto& elementSet : mElementsToRestyle) {
for (auto iter = elementSet.ConstIter(); !iter.Done(); iter.Next()) {
if (!iter.Data()) {
return true;
}
}
}
return false;
}
void
EffectCompositor::AddStyleUpdatesTo(RestyleTracker& aTracker)
{
if (!mPresContext) {
return;
}
for (size_t i = 0; i < kCascadeLevelCount; i++) {
CascadeLevel cascadeLevel = CascadeLevel(i);
auto& elementSet = mElementsToRestyle[cascadeLevel];
// Copy the list of elements to restyle to a separate array that we can
// iterate over. This is because we need to call MaybeUpdateCascadeResults
// on each element, but doing that can mutate elementSet. In this case
// it will only mutate the bool value associated with each element in the
// set but even doing that will cause assertions in PLDHashTable to fail
// if we are iterating over the hashtable at the same time.
nsTArray<PseudoElementHashKey> elementsToRestyle(elementSet.Count());
for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
elementsToRestyle.AppendElement(iter.Key());
}
for (auto& pseudoElem : elementsToRestyle) {
MaybeUpdateCascadeResults(pseudoElem.mElement, pseudoElem.mPseudoType);
ComposeAnimationRule(pseudoElem.mElement,
pseudoElem.mPseudoType,
cascadeLevel,
mPresContext->RefreshDriver()->MostRecentRefresh());
dom::Element* elementToRestyle =
GetElementToRestyle(pseudoElem.mElement, pseudoElem.mPseudoType);
if (elementToRestyle) {
nsRestyleHint rshint = cascadeLevel == CascadeLevel::Transitions ?
eRestyle_CSSTransitions :
eRestyle_CSSAnimations;
aTracker.AddPendingRestyle(elementToRestyle, rshint, nsChangeHint(0));
}
}
elementSet.Clear();
// Note: mElement pointers in elementsToRestyle might now dangle
}
}
/* static */ bool
EffectCompositor::HasAnimationsForCompositor(const nsIFrame* aFrame,
nsCSSProperty aProperty)
@ -131,6 +377,20 @@ EffectCompositor::GetAnimationsForCompositor(const nsIFrame* aFrame,
return result;
}
/* static */ void
EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame *aFrame,
nsCSSProperty aProperty)
{
EffectSet* effects = EffectSet::GetEffectSet(aFrame);
if (!effects) {
return;
}
for (KeyframeEffectReadOnly* effect : *effects) {
effect->SetIsRunningOnCompositor(aProperty, false);
}
}
/* static */ void
EffectCompositor::MaybeUpdateCascadeResults(Element* aElement,
nsCSSPseudoElements::Type
@ -147,6 +407,25 @@ EffectCompositor::MaybeUpdateCascadeResults(Element* aElement,
MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state");
}
/* static */ void
EffectCompositor::MaybeUpdateCascadeResults(Element* aElement,
nsCSSPseudoElements::Type
aPseudoType)
{
nsStyleContext* styleContext = nullptr;
{
dom::Element* elementToRestyle = GetElementToRestyle(aElement, aPseudoType);
if (elementToRestyle) {
nsIFrame* frame = elementToRestyle->GetPrimaryFrame();
if (frame) {
styleContext = frame->StyleContext();
}
}
}
MaybeUpdateCascadeResults(aElement, aPseudoType, styleContext);
}
namespace {
class EffectCompositeOrderComparator {
public:
@ -228,7 +507,7 @@ EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame)
EffectCompositor::ComposeAnimationRule(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
CascadeLevel aCascadeLevel,
bool& aStyleChanging)
TimeStamp aRefreshTime)
{
EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
if (!effects) {
@ -253,9 +532,6 @@ EffectCompositor::ComposeAnimationRule(dom::Element* aElement,
effects->AnimationRule(aCascadeLevel);
animationRule = nullptr;
// We'll set aStyleChanging to true below if necessary.
aStyleChanging = false;
// If multiple animations specify behavior for the same property the
// animation with the *highest* composite order wins.
// As a result, we iterate from last animation to first and, if a
@ -263,9 +539,10 @@ EffectCompositor::ComposeAnimationRule(dom::Element* aElement,
nsCSSPropertySet properties;
for (KeyframeEffectReadOnly* effect : Reversed(sortedEffectList)) {
effect->GetAnimation()->ComposeStyle(animationRule, properties,
aStyleChanging);
effect->GetAnimation()->ComposeStyle(animationRule, properties);
}
effects->UpdateAnimationRuleRefreshTime(aCascadeLevel, aRefreshTime);
}
/* static */ void
@ -380,26 +657,14 @@ EffectCompositor::UpdateCascadeResults(EffectSet& aEffectSet,
// layers with the winning animations.
nsPresContext* presContext = GetPresContext(aElement);
if (changed && presContext) {
// We currently unconditionally update both animations and transitions
// even if we could, for example, get away with only updating animations.
// This is a temporary measure until we unify all animation style updating
// under EffectCompositor.
AnimationCollection* animations =
presContext->AnimationManager()->GetAnimationCollection(aElement,
aPseudoType,
false);
/* don't create */
if (animations) {
animations->RequestRestyle(AnimationCollection::RestyleType::Layer);
}
AnimationCollection* transitions =
presContext->TransitionManager()->GetAnimationCollection(aElement,
aPseudoType,
false);
/* don't create */
if (transitions) {
transitions->RequestRestyle(AnimationCollection::RestyleType::Layer);
// Update both transitions and animations. We could detect *which* levels
// actually changed and only update them, but that's probably unnecessary.
for (auto level : { CascadeLevel::Animations,
CascadeLevel::Transitions }) {
presContext->EffectCompositor()->RequestRestyle(aElement,
aPseudoType,
RestyleType::Layer,
level);
}
}
}

View File

@ -7,21 +7,27 @@
#ifndef mozilla_EffectCompositor_h
#define mozilla_EffectCompositor_h
#include "mozilla/EnumeratedArray.h"
#include "mozilla/Maybe.h"
#include "mozilla/Pair.h"
#include "mozilla/PseudoElementHashEntry.h"
#include "mozilla/RefPtr.h"
#include "nsCSSProperty.h"
#include "nsCSSPseudoElements.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDataHashtable.h"
#include "nsTArray.h"
class nsCSSPropertySet;
class nsIFrame;
class nsIStyleRule;
class nsPresContext;
class nsStyleContext;
namespace mozilla {
class EffectSet;
class RestyleTracker;
namespace dom {
class Animation;
@ -31,6 +37,17 @@ class Element;
class EffectCompositor
{
public:
explicit EffectCompositor(nsPresContext* aPresContext)
: mPresContext(aPresContext)
{ }
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(EffectCompositor)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(EffectCompositor)
void Disconnect() {
mPresContext = nullptr;
}
// Animations can be applied at two different levels in the CSS cascade:
enum class CascadeLevel {
// The animations sheet (CSS animations, script-generated animations,
@ -44,6 +61,68 @@ public:
static const size_t kCascadeLevelCount =
static_cast<size_t>(CascadeLevel::Transitions) + 1;
// NOTE: This can return null after Disconnect().
nsPresContext* PresContext() const { return mPresContext; }
enum class RestyleType {
// Animation style has changed but the compositor is applying the same
// change so we might be able to defer updating the main thread until it
// becomes necessary.
Throttled,
// Animation style has changed and needs to be updated on the main thread.
Standard,
// Animation style has changed and needs to be updated on the main thread
// as well as forcing animations on layers to be updated.
// This is needed in cases such as when an animation becomes paused or has
// its playback rate changed. In such cases, although the computed style
// and refresh driver time might not change, we still need to ensure the
// corresponding animations on layers are updated to reflect the new
// configuration of the animation.
Layer
};
// Notifies the compositor that the animation rule for the specified
// (pseudo-)element at the specified cascade level needs to be updated.
// The specified steps taken to update the animation rule depend on
// |aRestyleType| whose values are described above.
void RequestRestyle(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
RestyleType aRestyleType,
CascadeLevel aCascadeLevel);
// Schedule an animation restyle. This is called automatically by
// RequestRestyle when necessary. However, it is exposed here since we also
// need to perform this step when triggering transitions *without* also
// invalidating the animation style rule (which RequestRestyle would do).
void PostRestyleForAnimation(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
CascadeLevel aCascadeLevel);
// Posts an animation restyle for any elements whose animation style rule
// is out of date but for which an animation restyle has not yet been
// posted because updates on the main thread are throttled.
void PostRestyleForThrottledAnimations();
// Updates the animation rule stored on the EffectSet for the
// specified (pseudo-)element for cascade level |aLevel|.
// If the animation rule is not marked as needing an update,
// no work is done.
void MaybeUpdateAnimationRule(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
CascadeLevel aCascadeLevel);
nsIStyleRule* GetAnimationRule(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
CascadeLevel aCascadeLevel);
bool HasPendingStyleUpdates() const;
bool HasThrottledStyleUpdates() const;
// Tell the restyle tracker about all the animated styles that have
// pending updates so that it can update the animation rule for these
// elements.
void AddStyleUpdatesTo(RestyleTracker& aTracker);
static bool HasAnimationsForCompositor(const nsIFrame* aFrame,
nsCSSProperty aProperty);
@ -51,6 +130,8 @@ public:
GetAnimationsForCompositor(const nsIFrame* aFrame,
nsCSSProperty aProperty);
static void ClearIsRunningOnCompositor(const nsIFrame* aFrame,
nsCSSProperty aProperty);
// Update animation cascade results for the specified (pseudo-)element
// but only if we have marked the cascade as needing an update due a
@ -64,6 +145,12 @@ public:
nsCSSPseudoElements::Type aPseudoType,
nsStyleContext* aStyleContext);
// An overload of MaybeUpdateCascadeResults that uses the style context
// of the primary frame of the specified (pseudo-)element, when available.
static void
MaybeUpdateCascadeResults(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType);
// Update the mWinsInCascade member for each property in effects targetting
// the specified (pseudo-)element.
//
@ -87,14 +174,20 @@ public:
static Maybe<Pair<dom::Element*, nsCSSPseudoElements::Type>>
GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame);
private:
~EffectCompositor() = default;
// Rebuilds the animation rule corresponding to |aCascadeLevel| on the
// EffectSet associated with the specified (pseudo-)element.
static void ComposeAnimationRule(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType,
CascadeLevel aCascadeLevel,
bool& aStyleChanging);
TimeStamp aRefreshTime);
static dom::Element* GetElementToRestyle(dom::Element* aElement,
nsCSSPseudoElements::Type
aPseudoType);
private:
// Get the properties in |aEffectSet| that we are able to animate on the
// compositor but which are also specified at a higher level in the cascade
// than the animations level in |aStyleContext|.
@ -110,6 +203,17 @@ private:
nsStyleContext* aStyleContext);
static nsPresContext* GetPresContext(dom::Element* aElement);
nsPresContext* mPresContext;
// Elements with a pending animation restyle. The associated bool value is
// true if a pending animation restyle has also been dispatched. For
// animations that can be throttled, we will add an entry to the hashtable to
// indicate that the style rule on the element is out of date but without
// posting a restyle to update it.
EnumeratedArray<CascadeLevel, CascadeLevel(kCascadeLevelCount),
nsDataHashtable<PseudoElementHashEntry, bool>>
mElementsToRestyle;
};
} // namespace mozilla

View File

@ -10,6 +10,7 @@
#include "mozilla/AnimValuesStyleRule.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EnumeratedArray.h"
#include "mozilla/TimeStamp.h"
#include "nsCSSPseudoElements.h" // For nsCSSPseudoElements::Type
#include "nsHashKeys.h" // For nsPtrHashKey
#include "nsTHashtable.h" // For nsTHashtable
@ -133,6 +134,18 @@ public:
return mAnimationRule[aCascadeLevel];
}
const TimeStamp& AnimationRuleRefreshTime(EffectCompositor::CascadeLevel
aCascadeLevel) const
{
return mAnimationRuleRefreshTime[aCascadeLevel];
}
void UpdateAnimationRuleRefreshTime(EffectCompositor::CascadeLevel
aCascadeLevel,
const TimeStamp& aRefreshTime)
{
mAnimationRuleRefreshTime[aCascadeLevel] = aRefreshTime;
}
bool CascadeNeedsUpdate() const { return mCascadeNeedsUpdate; }
void MarkCascadeNeedsUpdate() { mCascadeNeedsUpdate = true; }
void MarkCascadeUpdated() { mCascadeNeedsUpdate = false; }
@ -158,6 +171,15 @@ private:
EffectCompositor::kCascadeLevelCount),
RefPtr<AnimValuesStyleRule>> mAnimationRule;
// A parallel array to mAnimationRule that records the refresh driver
// timestamp when the rule was last updated. This is used for certain
// animations which are updated only periodically (e.g. transform animations
// running on the compositor that affect the scrollable overflow region).
EnumeratedArray<EffectCompositor::CascadeLevel,
EffectCompositor::CascadeLevel(
EffectCompositor::kCascadeLevelCount),
TimeStamp> mAnimationRuleRefreshTime;
// Dirty flag to represent when the mWinsInCascade flag on effects in
// this set might need to be updated.
//

View File

@ -10,10 +10,10 @@
#include "mozilla/dom/KeyframeEffectBinding.h"
#include "mozilla/dom/PropertyIndexedKeyframesBinding.h"
#include "mozilla/AnimationUtils.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt
#include "mozilla/StyleAnimationValue.h"
#include "AnimationCommon.h"
#include "Layers.h" // For Layer
#include "nsCSSParser.h"
#include "nsCSSPropertySet.h"
@ -163,8 +163,7 @@ KeyframeEffectReadOnly::NotifyAnimationTimingUpdated()
}
// Request restyle if necessary.
ComputedTiming computedTiming = GetComputedTiming();
AnimationCollection* collection = GetCollection();
//
// Bug 1235002: We should skip requesting a restyle when mProperties is empty.
// However, currently we don't properly encapsulate mProperties so we can't
// detect when it changes. As a result, if we skip requesting restyles when
@ -173,13 +172,20 @@ KeyframeEffectReadOnly::NotifyAnimationTimingUpdated()
// request a restyle at all. Since animations without properties are rare, we
// currently just request the restyle regardless of whether mProperties is
// empty or not.
if (collection &&
// Bug 1216843: When we implement iteration composite modes, we need to
// also detect if the current iteration has changed.
computedTiming.mProgress != mProgressOnLastCompose) {
collection->RequestRestyle(CanThrottle() ?
AnimationCollection::RestyleType::Throttled :
AnimationCollection::RestyleType::Standard);
//
// Bug 1216843: When we implement iteration composite modes, we need to
// also detect if the current iteration has changed.
if (mAnimation && GetComputedTiming().mProgress != mProgressOnLastCompose) {
EffectCompositor::RestyleType restyleType =
CanThrottle() ?
EffectCompositor::RestyleType::Throttled :
EffectCompositor::RestyleType::Standard;
nsPresContext* presContext = GetPresContext();
if (presContext) {
presContext->EffectCompositor()->
RequestRestyle(mTarget, mPseudoType, restyleType,
mAnimation->CascadeLevel());
}
}
}
@ -422,6 +428,31 @@ KeyframeEffectReadOnly::HasAnimationOfProperties(
return false;
}
void
KeyframeEffectReadOnly::CopyPropertiesFrom(const KeyframeEffectReadOnly& aOther)
{
nsCSSPropertySet winningInCascadeProperties;
nsCSSPropertySet runningOnCompositorProperties;
for (const AnimationProperty& property : mProperties) {
if (property.mWinsInCascade) {
winningInCascadeProperties.AddProperty(property.mProperty);
}
if (property.mIsRunningOnCompositor) {
runningOnCompositorProperties.AddProperty(property.mProperty);
}
}
mProperties = aOther.mProperties;
for (AnimationProperty& property : mProperties) {
property.mWinsInCascade =
winningInCascadeProperties.HasProperty(property.mProperty);
property.mIsRunningOnCompositor =
runningOnCompositorProperties.HasProperty(property.mProperty);
}
}
void
KeyframeEffectReadOnly::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
nsCSSPropertySet& aSetProperties)
@ -450,7 +481,7 @@ KeyframeEffectReadOnly::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
"incorrect last to key");
if (aSetProperties.HasProperty(prop.mProperty)) {
// Animations are composed by AnimationCollection by iterating
// Animations are composed by EffectCompositor by iterating
// from the last animation to first. For animations targetting the
// same property, the later one wins. So if this property is already set,
// we should not override it.
@ -1375,7 +1406,6 @@ BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
MOZ_ASSERT(aEntries[i].mOffset == 0.0f);
animationProperty = aResult.AppendElement();
animationProperty->mProperty = aEntries[i].mProperty;
animationProperty->mWinsInCascade = true;
lastProperty = aEntries[i].mProperty;
}
@ -1577,7 +1607,6 @@ BuildAnimationPropertyListFromPropertyIndexedKeyframes(
animationPropertyIndexes[i] = aResult.Length();
AnimationProperty* animationProperty = aResult.AppendElement();
animationProperty->mProperty = p;
animationProperty->mWinsInCascade = true;
properties.AddProperty(p);
}
}
@ -1907,13 +1936,16 @@ KeyframeEffectReadOnly::CanThrottleTransformChanges(nsIFrame& aFrame) const
TimeStamp now =
presContext->RefreshDriver()->MostRecentRefresh();
AnimationCollection* collection = GetCollection();
MOZ_ASSERT(collection,
"CanThrottleTransformChanges should be involved with animation collection");
TimeStamp styleRuleRefreshTime = collection->mStyleRuleRefreshTime;
EffectSet* effectSet = EffectSet::GetEffectSet(mTarget, mPseudoType);
MOZ_ASSERT(effectSet, "CanThrottleTransformChanges is expected to be called"
" on an effect in an effect set");
MOZ_ASSERT(mAnimation, "CanThrottleTransformChanges is expected to be called"
" on an effect with a parent animation");
TimeStamp animationRuleRefreshTime =
effectSet->AnimationRuleRefreshTime(mAnimation->CascadeLevel());
// If this animation can cause overflow, we can throttle some of the ticks.
if (!styleRuleRefreshTime.IsNull() &&
(now - styleRuleRefreshTime) < OverflowRegionRefreshInterval()) {
if (!animationRuleRefreshTime.IsNull() &&
(now - animationRuleRefreshTime) < OverflowRegionRefreshInterval()) {
return true;
}
@ -1985,12 +2017,6 @@ KeyframeEffectReadOnly::GetPresContext() const
return shell->GetPresContext();
}
AnimationCollection *
KeyframeEffectReadOnly::GetCollection() const
{
return mAnimation ? mAnimation->GetCollection() : nullptr;
}
/* static */ bool
KeyframeEffectReadOnly::IsGeometricProperty(
const nsCSSProperty aProperty)

View File

@ -131,9 +131,10 @@ struct AnimationProperty
// For CSS Animations, which are overridden by !important rules in the
// cascade, we actually determine this from the CSS cascade
// computations, and then use it for OMTA.
//
// **NOTE**: This member is not included when comparing AnimationProperty
// objects for equality.
bool mWinsInCascade = true;
bool mWinsInCascade = false;
// If true, the propery is currently being animated on the compositor.
//
@ -141,6 +142,9 @@ struct AnimationProperty
// between calling RequestRestyle on its AnimationCollection and when the
// restyle is performed, this member may temporarily become false even if
// the animation remains on the layer after the restyle.
//
// **NOTE**: This member is not included when comparing AnimationProperty
// objects for equality.
bool mIsRunningOnCompositor = false;
InfallibleTArray<AnimationPropertySegment> mSegments;
@ -279,6 +283,10 @@ public:
InfallibleTArray<AnimationProperty>& Properties() {
return mProperties;
}
// Copies the properties from another keyframe effect whilst preserving
// the mWinsInCascade and mIsRunningOnCompositor state of matching
// properties.
void CopyPropertiesFrom(const KeyframeEffectReadOnly& aOther);
// Updates |aStyleRule| with the animation values produced by this
// AnimationEffect for the current time except any properties already

View File

@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_PseudoElementHashEntry_h
#define mozilla_PseudoElementHashEntry_h
#include "mozilla/dom/Element.h"
#include "mozilla/HashFunctions.h"
#include "nsCSSPseudoElements.h"
#include "PLDHashTable.h"
namespace mozilla {
struct PseudoElementHashKey
{
dom::Element* mElement;
nsCSSPseudoElements::Type mPseudoType;
};
// A hash entry that uses a RefPtr<dom::Element>, nsCSSPseudoElements::Type pair
class PseudoElementHashEntry : public PLDHashEntryHdr
{
public:
typedef PseudoElementHashKey KeyType;
typedef const PseudoElementHashKey* KeyTypePointer;
explicit PseudoElementHashEntry(KeyTypePointer aKey)
: mElement(aKey->mElement)
, mPseudoType(aKey->mPseudoType) { }
explicit PseudoElementHashEntry(const PseudoElementHashEntry& aCopy)=default;
~PseudoElementHashEntry() = default;
KeyType GetKey() const { return {mElement, mPseudoType}; }
bool KeyEquals(KeyTypePointer aKey) const
{
return mElement == aKey->mElement &&
mPseudoType == aKey->mPseudoType;
}
static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
static PLDHashNumber HashKey(KeyTypePointer aKey)
{
if (!aKey)
return 0;
return mozilla::HashGeneric(aKey->mElement, aKey->mPseudoType);
}
enum { ALLOW_MEMMOVE = true };
RefPtr<dom::Element> mElement;
nsCSSPseudoElements::Type mPseudoType;
};
} // namespace mozilla
#endif // mozilla_PseudoElementHashEntry_h

View File

@ -23,6 +23,7 @@ EXPORTS.mozilla += [
'EffectCompositor.h',
'EffectSet.h',
'PendingAnimationTracker.h',
'PseudoElementHashEntry.h',
]
UNIFIED_SOURCES += [

View File

@ -151,7 +151,11 @@ var steps = [
function() {
SpecialPowers.pushPrefEnv(
{"set": [["dom.mozPermissionSettings.enabled", true],
["dom.webapps.useCurrentProfile", true]]},
["dom.webapps.useCurrentProfile", true],
// This test resets a conection to simulate network error,
// so we need to set the following pref to 0 that we do not
// retry it in necko.
["network.http.request.max-attempts", 0]]},
PackagedTestHelper.next);
}
);

View File

@ -1394,6 +1394,7 @@ Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
uint64_t aInnerWindowID,
const nsAString& aCallID,
ErrorResult& aRv)
{
CallbackObjectHolder<MozGetUserMediaDevicesSuccessCallback,
@ -1413,7 +1414,7 @@ Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
MediaManager* manager = MediaManager::Get();
aRv = manager->GetUserMediaDevices(mWindow, aConstraints, onsuccess, onerror,
aInnerWindowID);
aInnerWindowID, aCallID);
}
#endif

View File

@ -297,6 +297,7 @@ public:
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
uint64_t aInnerWindowID,
const nsAString& aCallID,
ErrorResult& aRv);
#endif // MOZ_MEDIA_NAVIGATOR

View File

@ -1030,6 +1030,24 @@ nsDOMWindowUtils::SendNativeMouseEvent(int32_t aScreenX,
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::SendNativeMouseMove(int32_t aScreenX,
int32_t aScreenY,
nsIDOMElement* aElement,
nsIObserver* aObserver)
{
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
if (!widget)
return NS_ERROR_FAILURE;
NS_DispatchToMainThread(NS_NewRunnableMethodWithArgs
<LayoutDeviceIntPoint, nsIObserver*>
(widget, &nsIWidget::SynthesizeNativeMouseMove,
LayoutDeviceIntPoint(aScreenX, aScreenY), aObserver));
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::SendNativeMouseScrollEvent(int32_t aScreenX,
int32_t aScreenY,

View File

@ -690,8 +690,6 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #bug 901343, specialpowers.wr
[test_bug814576.html]
[test_bug819051.html]
[test_bug820909.html]
[test_bug827160.html]
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #needs plugin support # b2g(needs plugin support) b2g-debug(debug-only failure) b2g-desktop(needs plugin support)
[test_bug840098.html]
[test_bug864595.html]
[test_bug868999.html]
@ -777,8 +775,6 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e1
[test_mutationobservers.html]
skip-if = buildapp == 'b2g' # b2g(bug 901385, showmodaldialog) b2g-debug(bug 901385, showmodaldialog) b2g-desktop(bug 901385, showmodaldialog)
[test_nodelist_holes.html]
[test_object.html]
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(needs plugin support) b2g-debug(needs plugin support) b2g-desktop(needs plugin support)
[test_plugin_freezing.html]
skip-if = buildapp == 'b2g' || toolkit == 'android' #CLICK_TO_PLAY
[test_processing_instruction_update_stylesheet.xhtml]

View File

@ -311,7 +311,7 @@ class ProtoAndIfaceCache
void Trace(JSTracer* aTracer) {
for (size_t i = 0; i < ArrayLength(*this); ++i) {
JS::TraceNullableEdge(aTracer, &(*this)[i], "protoAndIfaceCache[i]");
JS::TraceEdge(aTracer, &(*this)[i], "protoAndIfaceCache[i]");
}
}
@ -370,7 +370,7 @@ class ProtoAndIfaceCache
Page* p = mPages[i];
if (p) {
for (size_t j = 0; j < ArrayLength(*p); ++j) {
JS::TraceNullableEdge(trc, &(*p)[j], "protoAndIfaceCache[i]");
JS::TraceEdge(trc, &(*p)[j], "protoAndIfaceCache[i]");
}
}
}

View File

@ -44,12 +44,8 @@ protected:
public:
inline void TraceSelf(JSTracer* trc)
{
if (mTypedObj) {
JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mTypedObj");
}
if (mWrappedObj) {
JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mWrappedObj");
}
JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mTypedObj");
JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mWrappedObj");
}
private:

View File

@ -8,6 +8,7 @@
#define mozilla_dom_bluetooth_bluetoothpbaprequesthandle_h
#include "nsCOMPtr.h"
#include "mozilla/dom/bluetooth/BluetoothCommon.h"
#include "mozilla/dom/DOMRequest.h"
#include "mozilla/dom/BlobSet.h"

View File

@ -27,7 +27,8 @@ namespace dom {
using namespace workers;
BroadcastChannelChild::BroadcastChannelChild(const nsACString& aOrigin)
: mActorDestroyed(false)
: mBC(nullptr)
, mActorDestroyed(false)
{
CopyUTF8toUTF16(aOrigin, mOrigin);
}

View File

@ -907,6 +907,7 @@ protected:
public:
ContextState() : textAlign(TextAlign::START),
textBaseline(TextBaseline::ALPHABETIC),
shadowColor(0),
lineWidth(1.0f),
miterLimit(10.0f),
globalAlpha(1.0f),

View File

@ -101,6 +101,10 @@ WebGLContextOptions::WebGLContextOptions()
WebGLContext::WebGLContext()
: WebGLContextUnchecked(nullptr)
, mBufferFetchingIsVerified(false)
, mBufferFetchingHasPerVertex(false)
, mMaxFetchedVertices(0)
, mMaxFetchedInstances(0)
, mBypassShaderValidation(false)
, mGLMaxSamples(1)
, mNeedsFakeNoAlpha(false)
@ -113,6 +117,12 @@ WebGLContext::WebGLContext()
mShouldPresent = true;
mResetLayer = true;
mOptionsFrozen = false;
mMinCapability = false;
mDisableExtensions = false;
mIsMesa = false;
mEmitContextLostErrorOnce = false;
mWebGLError = 0;
mUnderlyingGLError = 0;
mActiveTexture = 0;
@ -128,6 +138,17 @@ WebGLContext::WebGLContext()
mFakeVertexAttrib0BufferObject = 0;
mFakeVertexAttrib0BufferStatus = WebGLVertexAttrib0Status::Default;
mStencilRefFront = 0;
mStencilRefBack = 0;
mStencilValueMaskFront = 0;
mStencilValueMaskBack = 0;
mStencilWriteMaskFront = 0;
mStencilWriteMaskBack = 0;
mDepthWriteMask = 0;
mStencilClearValue = 0;
mDepthClearValue = 0;
mContextLostErrorSet = false;
mViewportX = 0;
mViewportY = 0;
mViewportWidth = 0;

View File

@ -20,6 +20,8 @@ WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPo
: mFB(fb)
, mAttachmentPoint(attachmentPoint)
, mTexImageTarget(LOCAL_GL_NONE)
, mTexImageLayer(0)
, mTexImageLevel(0)
{ }
WebGLFBAttachPoint::~WebGLFBAttachPoint()

View File

@ -79,6 +79,8 @@ public:
const nsAString& aManifestURL,
bool aReadOnly,
bool aEnabled)
: mReadOnly(true)
, mEnabled(false)
{
Init(aName, aOriginURL, aManifestURL, aReadOnly, aEnabled);
}

View File

@ -55,6 +55,7 @@ public:
AsyncEventDispatcher(dom::EventTarget* aTarget, nsIDOMEvent* aEvent)
: mTarget(aTarget)
, mEvent(aEvent)
, mBubbles(false)
, mOnlyChromeDispatch(false)
{
}

View File

@ -37,16 +37,19 @@ public:
}
explicit TypedEventHandler(dom::EventHandlerNonNull* aHandler)
: mBits(0)
{
Assign(aHandler, eNormal);
}
explicit TypedEventHandler(dom::OnErrorEventHandlerNonNull* aHandler)
: mBits(0)
{
Assign(aHandler, eOnError);
}
explicit TypedEventHandler(dom::OnBeforeUnloadEventHandlerNonNull* aHandler)
: mBits(0)
{
Assign(aHandler, eOnBeforeUnload);
}

View File

@ -256,7 +256,20 @@ private:
bool mAllowControlCharacters;
// Hide the default constructor and copy constructor.
TextComposition() {}
TextComposition()
: mPresContext(nullptr)
, mNativeContext(nullptr)
, mCompositionStartOffset(0)
, mCompositionTargetOffset(0)
, mIsSynthesizedForTests(false)
, mIsComposing(false)
, mIsEditorHandlingEvent(false)
, mIsRequestingCommit(false)
, mIsRequestingCancel(false)
, mRequestedToCommitOrCancel(false)
, mWasNativeCompositionEndEventDiscarded(false)
, mAllowControlCharacters(false)
{}
TextComposition(const TextComposition& aOther);
/**
@ -384,7 +397,7 @@ private:
EventMessage mEventMessage;
bool mIsSynthesizedEvent;
CompositionEventDispatcher() {};
CompositionEventDispatcher() : mIsSynthesizedEvent(false) {};
};
/**

View File

@ -86,7 +86,6 @@ InternalRequest::InternalRequest(const InternalRequest& aOther)
, mForceOriginHeader(aOther.mForceOriginHeader)
, mPreserveContentCodings(aOther.mPreserveContentCodings)
, mSameOriginDataURL(aOther.mSameOriginDataURL)
, mSandboxedStorageAreaURLs(aOther.mSandboxedStorageAreaURLs)
, mSkipServiceWorker(aOther.mSkipServiceWorker)
, mSynchronous(aOther.mSynchronous)
, mUnsafeRequest(aOther.mUnsafeRequest)

View File

@ -91,6 +91,7 @@ public:
explicit InternalRequest()
: mMethod("GET")
, mHeaders(new InternalHeaders(HeadersGuardEnum::None))
, mContentPolicyType(nsIContentPolicy::TYPE_FETCH)
, mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR))
, mMode(RequestMode::No_cors)
, mCredentialsMode(RequestCredentials::Omit)
@ -413,7 +414,6 @@ private:
bool mForceOriginHeader;
bool mPreserveContentCodings;
bool mSameOriginDataURL;
bool mSandboxedStorageAreaURLs;
bool mSkipServiceWorker;
bool mSynchronous;
bool mUnsafeRequest;

View File

@ -131,6 +131,16 @@ InternalResponse::GetTainting() const
}
}
already_AddRefed<InternalResponse>
InternalResponse::Unfiltered()
{
RefPtr<InternalResponse> ref = mWrappedResponse;
if (!ref) {
ref = this;
}
return ref.forget();
}
already_AddRefed<InternalResponse>
InternalResponse::OpaqueResponse()
{

View File

@ -221,6 +221,9 @@ public:
LoadTainting
GetTainting() const;
already_AddRefed<InternalResponse>
Unfiltered();
private:
~InternalResponse();

View File

@ -238,6 +238,20 @@ Response::Clone(ErrorResult& aRv) const
return response.forget();
}
already_AddRefed<Response>
Response::CloneUnfiltered(ErrorResult& aRv) const
{
if (BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
RefPtr<InternalResponse> clone = mInternalResponse->Clone();
RefPtr<InternalResponse> ir = clone->Unfiltered();
RefPtr<Response> ref = new Response(mOwner, ir);
return ref.forget();
}
void
Response::SetBody(nsIInputStream* aBody)
{

View File

@ -124,6 +124,9 @@ public:
already_AddRefed<Response>
Clone(ErrorResult& aRv) const;
already_AddRefed<Response>
CloneUnfiltered(ErrorResult& aRv) const;
void
SetBody(nsIInputStream* aBody);

View File

@ -46,7 +46,8 @@ Gamepad::Gamepad(nsISupports* aParent,
mMapping(aMapping),
mConnected(true),
mButtons(aNumButtons),
mAxes(aNumAxes)
mAxes(aNumAxes),
mTimestamp(0)
{
for (unsigned i = 0; i < aNumButtons; i++) {
mButtons.InsertElementAt(i, new GamepadButton(mParent));

View File

@ -102,7 +102,14 @@ public:
inline const nsString& GetOrigin() const { return mOrigin; }
private:
GeolocationSetting() {} // can't default construct
GeolocationSetting() :
#ifdef MOZ_APPROX_LOCATION
mDistance(0),
#endif
mLatitude(0),
mLongitude(0)
{} // can't default construct
GeolocationFuzzMethod mFuzzMethod;
#ifdef MOZ_APPROX_LOCATION
int32_t mDistance;

View File

@ -386,7 +386,8 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
nsTArray<RefPtr<File>> newFiles;
if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
nsCOMPtr<nsISimpleEnumerator> iter;
nsresult rv = mFilePicker->GetDomfiles(getter_AddRefs(iter));
nsresult rv =
mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
NS_ENSURE_SUCCESS(rv, rv);
if (!iter) {
@ -409,7 +410,7 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen) ||
mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder));
nsCOMPtr<nsISupports> tmp;
nsresult rv = mFilePicker->GetDomfile(getter_AddRefs(tmp));
nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(tmp);

Some files were not shown because too many files have changed in this diff Show More