mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
commit
0506fad16e
@ -18,6 +18,7 @@ caps/**
|
||||
chrome/**
|
||||
config/**
|
||||
db/**
|
||||
devtools/**
|
||||
docshell/**
|
||||
dom/**
|
||||
editor/**
|
||||
|
2
CLOBBER
2
CLOBBER
@ -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.
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
|
@ -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);
|
||||
|
@ -65,7 +65,6 @@ var FullZoom = {
|
||||
}
|
||||
|
||||
// This should be nulled after initialization.
|
||||
this._initialLocations.clear();
|
||||
this._initialLocations = null;
|
||||
},
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
@ -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");
|
||||
}());
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
@ -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)
|
||||
|
@ -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}
|
||||
|
@ -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()) {
|
||||
|
@ -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) {
|
||||
|
@ -410,7 +410,7 @@ var DragPositionManager = {
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
gManagers.clear();
|
||||
gManagers = new WeakMap();
|
||||
},
|
||||
|
||||
getManagerForArea: function(aArea) {
|
||||
|
@ -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 },
|
||||
|
@ -28,7 +28,6 @@ DIRS += [
|
||||
DIRS += ['build']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIAboutNewTabService.idl',
|
||||
'nsIBrowserGlue.idl',
|
||||
'nsIBrowserHandler.idl',
|
||||
]
|
||||
|
2
browser/components/newtab/NewTabComponents.manifest
Normal file
2
browser/components/newtab/NewTabComponents.manifest
Normal 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}
|
@ -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"],
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
},
|
||||
};
|
@ -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);
|
||||
}
|
||||
};
|
@ -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
|
||||
};
|
190
browser/components/newtab/aboutNewTabService.js
Normal file
190
browser/components/newtab/aboutNewTabService.js
Normal 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]);
|
@ -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',
|
||||
]
|
||||
|
58
browser/components/newtab/nsIAboutNewTabService.idl
Normal file
58
browser/components/newtab/nsIAboutNewTabService.idl
Normal 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();
|
||||
};
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
@ -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]
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
@ -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();
|
||||
};
|
@ -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");
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
200
browser/components/sessionstore/StartupPerformance.jsm
Normal file
200
browser/components/sessionstore/StartupPerformance.jsm
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
@ -112,6 +112,7 @@ function updateTabListVisibility() {
|
||||
}
|
||||
|
||||
function restoreSession() {
|
||||
Services.obs.notifyObservers(null, "sessionstore-initiating-manual-restore", "");
|
||||
document.getElementById("errorTryAgain").disabled = true;
|
||||
|
||||
if (isTreeViewVisible()) {
|
||||
|
@ -41,6 +41,7 @@ EXTRA_JS_MODULES.sessionstore = [
|
||||
'SessionStore.jsm',
|
||||
'SessionWorker.js',
|
||||
'SessionWorker.jsm',
|
||||
'StartupPerformance.jsm',
|
||||
'TabAttributes.jsm',
|
||||
'TabState.jsm',
|
||||
'TabStateCache.jsm',
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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") {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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!
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
7
config/external/nss/Makefile.in
vendored
7
config/external/nss/Makefile.in
vendored
@ -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)' \
|
||||
|
@ -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
|
||||
|
@ -114,7 +114,7 @@ var SourceUtils = {
|
||||
clearCache: function() {
|
||||
this._labelsCache.clear();
|
||||
this._groupsCache.clear();
|
||||
this._minifiedCache.clear();
|
||||
this._minifiedCache = new WeakMap();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1509,7 +1509,6 @@ EventTooltip.prototype = {
|
||||
editor.destroy();
|
||||
}
|
||||
|
||||
this._tooltip.eventEditors.clear();
|
||||
this._tooltip.eventEditors = null;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -28,6 +28,11 @@ class SerializedLoadContext
|
||||
{
|
||||
public:
|
||||
SerializedLoadContext()
|
||||
: mIsNotNull(false)
|
||||
, mIsPrivateBitValid(false)
|
||||
, mIsContent(false)
|
||||
, mUsePrivateBrowsing(false)
|
||||
, mUseRemoteTabs(false)
|
||||
{
|
||||
Init(nullptr);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,10 @@ public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIALARMHALSERVICE
|
||||
|
||||
AlarmHalService()
|
||||
: mAlarmEnabled(false)
|
||||
{}
|
||||
|
||||
void Init();
|
||||
|
||||
static already_AddRefed<AlarmHalService> GetInstance();
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
60
dom/animation/PseudoElementHashEntry.h
Normal file
60
dom/animation/PseudoElementHashEntry.h
Normal 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
|
@ -23,6 +23,7 @@ EXPORTS.mozilla += [
|
||||
'EffectCompositor.h',
|
||||
'EffectSet.h',
|
||||
'PendingAnimationTracker.h',
|
||||
'PseudoElementHashEntry.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
@ -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
|
||||
|
||||
|
@ -297,6 +297,7 @@ public:
|
||||
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
|
||||
NavigatorUserMediaErrorCallback& aOnError,
|
||||
uint64_t aInnerWindowID,
|
||||
const nsAString& aCallID,
|
||||
ErrorResult& aRv);
|
||||
#endif // MOZ_MEDIA_NAVIGATOR
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
@ -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]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
||||
|
@ -27,7 +27,8 @@ namespace dom {
|
||||
using namespace workers;
|
||||
|
||||
BroadcastChannelChild::BroadcastChannelChild(const nsACString& aOrigin)
|
||||
: mActorDestroyed(false)
|
||||
: mBC(nullptr)
|
||||
, mActorDestroyed(false)
|
||||
{
|
||||
CopyUTF8toUTF16(aOrigin, mOrigin);
|
||||
}
|
||||
|
@ -907,6 +907,7 @@ protected:
|
||||
public:
|
||||
ContextState() : textAlign(TextAlign::START),
|
||||
textBaseline(TextBaseline::ALPHABETIC),
|
||||
shadowColor(0),
|
||||
lineWidth(1.0f),
|
||||
miterLimit(10.0f),
|
||||
globalAlpha(1.0f),
|
||||
|
@ -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;
|
||||
|
@ -20,6 +20,8 @@ WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPo
|
||||
: mFB(fb)
|
||||
, mAttachmentPoint(attachmentPoint)
|
||||
, mTexImageTarget(LOCAL_GL_NONE)
|
||||
, mTexImageLayer(0)
|
||||
, mTexImageLevel(0)
|
||||
{ }
|
||||
|
||||
WebGLFBAttachPoint::~WebGLFBAttachPoint()
|
||||
|
@ -79,6 +79,8 @@ public:
|
||||
const nsAString& aManifestURL,
|
||||
bool aReadOnly,
|
||||
bool aEnabled)
|
||||
: mReadOnly(true)
|
||||
, mEnabled(false)
|
||||
{
|
||||
Init(aName, aOriginURL, aManifestURL, aReadOnly, aEnabled);
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ public:
|
||||
AsyncEventDispatcher(dom::EventTarget* aTarget, nsIDOMEvent* aEvent)
|
||||
: mTarget(aTarget)
|
||||
, mEvent(aEvent)
|
||||
, mBubbles(false)
|
||||
, mOnlyChromeDispatch(false)
|
||||
{
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -221,6 +221,9 @@ public:
|
||||
LoadTainting
|
||||
GetTainting() const;
|
||||
|
||||
already_AddRefed<InternalResponse>
|
||||
Unfiltered();
|
||||
|
||||
private:
|
||||
~InternalResponse();
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -124,6 +124,9 @@ public:
|
||||
already_AddRefed<Response>
|
||||
Clone(ErrorResult& aRv) const;
|
||||
|
||||
already_AddRefed<Response>
|
||||
CloneUnfiltered(ErrorResult& aRv) const;
|
||||
|
||||
void
|
||||
SetBody(nsIInputStream* aBody);
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user