Merge mozilla-central to inbound. a=merge CLOSED TREE

This commit is contained in:
Gurzau Raul 2018-03-14 00:44:06 +02:00
commit f785df755d
50 changed files with 2565 additions and 1140 deletions

View File

@ -40,6 +40,8 @@ xpcom/tests/.*
browser/components/translation/cld2/.*
browser/extensions/mortar/ppapi/.*
db/sqlite3/src/.*
devtools/client/sourceeditor/codemirror/.*
devtools/client/sourceeditor/tern/.*
extensions/spellcheck/hunspell/src/.*
gfx/angle/.*
gfx/cairo/.*
@ -50,8 +52,9 @@ gfx/qcms/.*
gfx/sfntly/.*
gfx/skia/.*
gfx/vr/openvr/.*
gfx/webrender.*
gfx/webrender_api.*
gfx/webrender/.*
gfx/webrender_api/.*
gfx/wrench/.*
gfx/ycbcr/.*
intl/hyphenation/hyphen/.*
intl/icu/.*
@ -94,6 +97,7 @@ modules/libbz2/.*
modules/libmar/.*
modules/pdfium/.*
modules/woff2/.*
modules/xz-embedded/.*
modules/zlib/.*
netwerk/sctp/src/.*
netwerk/srtp/src/.*
@ -104,7 +108,6 @@ security/nss/.*
security/sandbox/chromium/.*
testing/gtest/gmock/.*
testing/gtest/gtest/.*
testing/talos/talos/tests/canvasmark/.*
testing/talos/talos/tests/dromaeo/.*
testing/talos/talos/tests/kraken/.*
testing/talos/talos/tests/v8_7/.*
@ -134,4 +137,4 @@ toolkit/components/protobuf/.*
toolkit/components/url-classifier/chromium/.*
toolkit/components/url-classifier/protobuf/.*
toolkit/crashreporter/google-breakpad/.*
tools/fuzzing/libfuzzer.*
tools/fuzzing/libfuzzer/.*

View File

@ -141,25 +141,6 @@ if (AppConstants.MOZ_CRASHREPORTER) {
"nsICrashReporter");
}
Object.defineProperty(this, "gBrowser", {
configurable: true,
enumerable: true,
get() {
delete window.gBrowser;
// The tabbed browser only exists in proper browser windows, but on Mac we
// load browser.js in other windows and might try to access gBrowser.
if (!window._gBrowser) {
return window.gBrowser = null;
}
window.gBrowser = window._gBrowser;
delete window._gBrowser;
gBrowser.init();
return gBrowser;
},
});
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings.createBundle("chrome://browser/locale/browser.properties");
});
@ -243,6 +224,7 @@ XPCOMUtils.defineLazyGetter(this, "Win7Features", function() {
const nsIWebNavigation = Ci.nsIWebNavigation;
var gBrowser;
var gLastValidURLStr = "";
var gInPrintPreviewMode = false;
var gContextMenu = null; // nsContextMenu instance
@ -1208,6 +1190,10 @@ var gBrowserInit = {
delayedStartupFinished: false,
onDOMContentLoaded() {
gBrowser = window._gBrowser;
delete window._gBrowser;
gBrowser.init();
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
@ -5155,14 +5141,14 @@ var TabsProgressListener = {
}
},
onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI,
aFlags) {
onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
// Filter out location changes caused by anchor navigation
// or history.push/pop/replaceState.
if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
// Reader mode actually cares about these:
let mm = gBrowser.selectedBrowser.messageManager;
mm.sendAsyncMessage("Reader:PushState", {isArticle: gBrowser.selectedBrowser.isArticle});
// Reader mode cares about history.pushState and friends.
aBrowser.messageManager.sendAsyncMessage("Reader:PushState", {
isArticle: aBrowser.isArticle,
});
return;
}

View File

@ -46,8 +46,8 @@
tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
}
for (let tab of Array.from(tabs.tabbrowser._removingTabs)) {
tabs.tabbrowser.removeTab(tab);
for (let tab of Array.from(gBrowser._removingTabs)) {
gBrowser.removeTab(tab);
}
tabs._positionPinnedTabs();
@ -152,7 +152,7 @@
</destructor>
<field name="tabbox" readonly="true">
this.tabbrowser.tabbox;
document.getElementById("tabbrowser-tabbox");
</field>
<field name="contextMenu" readonly="true">
@ -177,12 +177,6 @@
window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
</field>
<property name="tabbrowser" readonly="true">
<getter>
return window.gBrowser;
</getter>
</property>
<property name="_tabMinWidth">
<setter>
this.style.setProperty("--tab-min-width", val + "px");
@ -304,13 +298,23 @@
]]></getter>
</property>
<method name="_getVisibleTabs">
<body><![CDATA[
// Cannot access gBrowser before it's initialized.
if (!gBrowser) {
return [ this.firstChild ];
}
return gBrowser.visibleTabs;
]]></body>
</method>
<method name="_setPositionalAttributes">
<body><![CDATA[
let visibleTabs = this.tabbrowser.visibleTabs;
if (!visibleTabs.length)
let visibleTabs = this._getVisibleTabs();
if (!visibleTabs.length) {
return;
}
let selectedIndex = visibleTabs.indexOf(this.selectedItem);
if (this._beforeSelectedTab) {
@ -395,10 +399,11 @@
<method name="updateVisibility">
<body><![CDATA[
if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
if (this.childNodes.length - gBrowser._removingTabs.length == 1) {
this.visible = window.toolbar.visible;
else
} else {
this.visible = true;
}
]]></body>
</method>
@ -437,7 +442,7 @@
.getInterface(Ci.nsIDOMWindowUtils)
.getBoundsWithoutFlushing(ele);
};
let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
let tab = this._getVisibleTabs()[gBrowser._numPinnedTabs];
if (tab && rect(tab).width <= this._tabClipWidth) {
this.setAttribute("closebuttons", "activetab");
} else {
@ -470,45 +475,48 @@
<method name="_lockTabSizing">
<parameter name="aTab"/>
<body><![CDATA[
var tabs = this.tabbrowser.visibleTabs;
if (!tabs.length)
let tabs = this._getVisibleTabs();
if (!tabs.length) {
return;
}
var isEndTab = (aTab._tPos > tabs[tabs.length - 1]._tPos);
var tabWidth = aTab.getBoundingClientRect().width;
if (!this._tabDefaultMaxWidth)
if (!this._tabDefaultMaxWidth) {
this._tabDefaultMaxWidth =
parseFloat(window.getComputedStyle(aTab).maxWidth);
}
this._lastTabClosedByMouse = true;
if (this.getAttribute("overflow") == "true") {
// Don't need to do anything if we're in overflow mode and aren't scrolled
// all the way to the right, or if we're closing the last tab.
if (isEndTab || !this.arrowScrollbox._scrollButtonDown.disabled)
if (isEndTab || !this.arrowScrollbox._scrollButtonDown.disabled) {
return;
}
// If the tab has an owner that will become the active tab, the owner will
// be to the left of it, so we actually want the left tab to slide over.
// This can't be done as easily in non-overflow mode, so we don't bother.
if (aTab.owner)
if (aTab.owner) {
return;
}
this._expandSpacerBy(tabWidth);
} else { // non-overflow mode
// Locking is neither in effect nor needed, so let tabs expand normally.
if (isEndTab && !this._hasTabTempMaxWidth)
if (isEndTab && !this._hasTabTempMaxWidth) {
return;
let numPinned = this.tabbrowser._numPinnedTabs;
}
let numPinned = gBrowser._numPinnedTabs;
// Force tabs to stay the same width, unless we're closing the last tab,
// which case we need to let them expand just enough so that the overall
// tabbar width is the same.
if (isEndTab) {
let numNormalTabs = tabs.length - numPinned;
tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
if (tabWidth > this._tabDefaultMaxWidth)
if (tabWidth > this._tabDefaultMaxWidth) {
tabWidth = this._tabDefaultMaxWidth;
}
}
tabWidth += "px";
for (let i = numPinned; i < tabs.length; i++) {
@ -521,7 +529,7 @@
}
}
this._hasTabTempMaxWidth = true;
this.tabbrowser.addEventListener("mousemove", this);
gBrowser.addEventListener("mousemove", this);
window.addEventListener("mouseout", this);
}
]]></body>
@ -533,21 +541,22 @@
let spacer = this._closingTabsSpacer;
spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
this.setAttribute("using-closing-tabs-spacer", "true");
this.tabbrowser.addEventListener("mousemove", this);
gBrowser.addEventListener("mousemove", this);
window.addEventListener("mouseout", this);
]]></body>
</method>
<method name="_unlockTabSizing">
<body><![CDATA[
this.tabbrowser.removeEventListener("mousemove", this);
gBrowser.removeEventListener("mousemove", this);
window.removeEventListener("mouseout", this);
if (this._hasTabTempMaxWidth) {
this._hasTabTempMaxWidth = false;
let tabs = this.tabbrowser.visibleTabs;
for (let i = 0; i < tabs.length; i++)
let tabs = this._getVisibleTabs();
for (let i = 0; i < tabs.length; i++) {
tabs[i].style.maxWidth = "";
}
}
if (this.hasAttribute("using-closing-tabs-spacer")) {
@ -569,9 +578,9 @@
<field name="_pinnedTabsLayoutCache">null</field>
<method name="_positionPinnedTabs">
<body><![CDATA[
var numPinned = this.tabbrowser._numPinnedTabs;
var doPosition = this.getAttribute("overflow") == "true" &&
this.tabbrowser.visibleTabs.length > numPinned &&
let numPinned = gBrowser._numPinnedTabs;
let doPosition = this.getAttribute("overflow") == "true" &&
this._getVisibleTabs().length > numPinned &&
numPinned > 0;
if (doPosition) {
@ -638,12 +647,13 @@
let rtl = (window.getComputedStyle(this).direction == "rtl");
let pinned = draggedTab.pinned;
let numPinned = this.tabbrowser._numPinnedTabs;
let tabs = this.tabbrowser.visibleTabs
.slice(pinned ? 0 : numPinned,
pinned ? numPinned : undefined);
if (rtl)
let numPinned = gBrowser._numPinnedTabs;
let tabs = this._getVisibleTabs()
.slice(pinned ? 0 : numPinned,
pinned ? numPinned : undefined);
if (rtl) {
tabs.reverse();
}
let tabWidth = draggedTab.getBoundingClientRect().width;
draggedTab._dragData.tabWidth = tabWidth;
@ -653,8 +663,9 @@
let rightTab = tabs[tabs.length - 1];
let tabScreenX = draggedTab.boxObject.screenX;
let translateX = screenX - draggedTab._dragData.screenX;
if (!pinned)
if (!pinned) {
translateX += this.arrowScrollbox._scrollbox.scrollLeft - draggedTab._dragData.scrollX;
}
let leftBound = leftTab.boxObject.screenX - tabScreenX;
let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
(tabScreenX + tabWidth);
@ -720,11 +731,13 @@
<method name="_finishAnimateTabMove">
<body><![CDATA[
if (this.getAttribute("movingtab") != "true")
if (this.getAttribute("movingtab") != "true") {
return;
}
for (let tab of this.tabbrowser.visibleTabs)
for (let tab of this._getVisibleTabs()) {
tab.style.transform = "";
}
this.removeAttribute("movingtab");
this.parentNode.removeAttribute("movingtab");
@ -883,10 +896,11 @@
<method name="_handleNewTab">
<parameter name="tab"/>
<body><![CDATA[
if (tab.parentNode != this)
if (tab.parentNode != this) {
return;
}
tab._fullyOpen = true;
this.tabbrowser.tabAnimationsInProgress--;
gBrowser.tabAnimationsInProgress--;
this._updateCloseButtons();
@ -904,7 +918,7 @@
this.arrowScrollbox._updateScrollButtonsDisabledState();
// Preload the next about:newtab if there isn't one already.
this.tabbrowser._createPreloadBrowser();
gBrowser._createPreloadBrowser();
]]></body>
</method>
@ -921,12 +935,19 @@
<parameter name="aTab"/>
<body>
<![CDATA[
if (!aTab)
if (!aTab) {
return null;
}
// Cannot access gBrowser before it's initialized.
if (!gBrowser) {
return this.tabbox.tabpanels.firstChild;
}
// If the tab's browser is lazy, we need to `_insertBrowser` in order
// to have a linkedPanel. This will also serve to bind the browser
// and make it ready to use when the tab is selected.
this.tabbrowser._insertBrowser(aTab);
gBrowser._insertBrowser(aTab);
return document.getElementById(aTab.linkedPanel);
]]>
</body>
@ -988,18 +1009,20 @@
<handler event="TabSelect" action="this._handleTabSelect();"/>
<handler event="transitionend"><![CDATA[
if (event.propertyName != "max-width")
if (event.propertyName != "max-width") {
return;
}
var tab = event.target;
if (tab.getAttribute("fadein") == "true") {
if (tab._fullyOpen)
if (tab._fullyOpen) {
this._updateCloseButtons();
else
} else {
this._handleNewTab(tab);
}
} else if (tab.closing) {
this.tabbrowser._endRemoveTab(tab);
gBrowser._endRemoveTab(tab);
}
]]></handler>
@ -1075,12 +1098,15 @@
]]></handler>
<handler event="click"><![CDATA[
if (event.button != 1)
if (event.button != 1) {
return;
}
if (event.target.localName == "tab") {
this.tabbrowser.removeTab(event.target, {animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
gBrowser.removeTab(event.target, {
animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
});
} else if (event.originalTarget.localName == "box") {
// The user middleclicked an open space on the tabstrip. This could
// be because they intend to open a new tab, but it could also be
@ -1089,8 +1115,8 @@
// want to open a tab. So if we're removing one or more tabs, and
// the tab click is before the end of the last visible tab, we do
// nothing.
if (this.tabbrowser._removingTabs.length) {
let visibleTabs = this.tabbrowser.visibleTabs;
if (gBrowser._removingTabs.length) {
let visibleTabs = this._getVisibleTabs();
let ltr = (window.getComputedStyle(this).direction == "ltr");
let lastTab = visibleTabs[visibleTabs.length - 1];
let endOfTab = lastTab.getBoundingClientRect()[ltr ? "right" : "left"];
@ -1127,20 +1153,20 @@
switch (event.keyCode) {
case KeyEvent.DOM_VK_UP:
this.tabbrowser.moveTabBackward();
gBrowser.moveTabBackward();
break;
case KeyEvent.DOM_VK_DOWN:
this.tabbrowser.moveTabForward();
gBrowser.moveTabForward();
break;
case KeyEvent.DOM_VK_RIGHT:
case KeyEvent.DOM_VK_LEFT:
this.tabbrowser.moveTabOver(event);
gBrowser.moveTabOver(event);
break;
case KeyEvent.DOM_VK_HOME:
this.tabbrowser.moveTabToStart();
gBrowser.moveTabToStart();
break;
case KeyEvent.DOM_VK_END:
this.tabbrowser.moveTabToEnd();
gBrowser.moveTabToEnd();
break;
default:
// Consume the keydown event for the above keyboard
@ -1359,10 +1385,11 @@
if (draggedTab && dropEffect == "copy") {
// copy the dropped tab (wherever it's from)
let newIndex = this._getDropIndex(event, false);
let newTab = this.tabbrowser.duplicateTab(draggedTab);
this.tabbrowser.moveTabTo(newTab, newIndex);
if (draggedTab.parentNode != this || event.shiftKey)
let newTab = gBrowser.duplicateTab(draggedTab);
gBrowser.moveTabTo(newTab, newIndex);
if (draggedTab.parentNode != this || event.shiftKey) {
this.selectedItem = newTab;
}
} else if (draggedTab && draggedTab.parentNode == this) {
let oldTranslateX = Math.round(draggedTab._dragData.translateX);
let tabWidth = Math.round(draggedTab._dragData.tabWidth);
@ -1379,7 +1406,7 @@
if (dropIndex && dropIndex > draggedTab._tPos)
dropIndex--;
let animate = this.tabbrowser.animationsEnabled;
let animate = gBrowser.animationsEnabled;
if (oldTranslateX && oldTranslateX != newTranslateX && animate) {
draggedTab.setAttribute("tabdrop-samewindow", "true");
draggedTab.style.transform = "translateX(" + newTranslateX + "px)";
@ -1393,20 +1420,22 @@
draggedTab.removeAttribute("tabdrop-samewindow");
this._finishAnimateTabMove();
if (dropIndex !== false)
this.tabbrowser.moveTabTo(draggedTab, dropIndex);
if (dropIndex !== false) {
gBrowser.moveTabTo(draggedTab, dropIndex);
}
this.tabbrowser.syncThrobberAnimations(draggedTab);
gBrowser.syncThrobberAnimations(draggedTab);
};
draggedTab.addEventListener("transitionend", onTransitionEnd);
} else {
this._finishAnimateTabMove();
if (dropIndex !== false)
this.tabbrowser.moveTabTo(draggedTab, dropIndex);
if (dropIndex !== false) {
gBrowser.moveTabTo(draggedTab, dropIndex);
}
}
} else if (draggedTab) {
let newIndex = this._getDropIndex(event, false);
this.tabbrowser.adoptTab(draggedTab, newIndex, true);
gBrowser.adoptTab(draggedTab, newIndex, true);
} else {
// Pass true to disallow dropping javascript: or data: urls
let links;
@ -1439,7 +1468,7 @@
}
}
this.tabbrowser.loadTabs(urls, {
gBrowser.loadTabs(urls, {
inBackground,
replace,
allowThirdPartyFixup: true,
@ -1520,7 +1549,7 @@
delete draggedTab._dragData;
if (this.tabbrowser.tabs.length == 1) {
if (gBrowser.tabs.length == 1) {
// resize _before_ move to ensure the window fits the new screen. if
// the window is too large for its screen, the window manager may do
// automatic repositioning.
@ -1533,7 +1562,7 @@
props.outerWidth = winWidth;
props.outerHeight = winHeight;
}
this.tabbrowser.replaceTabWithWindow(draggedTab, props);
gBrowser.replaceTabWithWindow(draggedTab, props);
}
event.stopPropagation();
]]></handler>
@ -1622,11 +1651,16 @@
<property name="_visuallySelected">
<setter>
<![CDATA[
if (val)
if (val == (this.getAttribute("visuallyselected") == "true")) {
return val;
}
if (val) {
this.setAttribute("visuallyselected", "true");
else
} else {
this.removeAttribute("visuallyselected");
this.parentNode.tabbrowser._tabAttrModified(this, ["visuallyselected"]);
}
gBrowser._tabAttrModified(this, ["visuallyselected"]);
return val;
]]>
@ -1740,11 +1774,12 @@
<method name="_mouseenter">
<body><![CDATA[
if (this.hidden || this.closing)
if (this.hidden || this.closing) {
return;
}
let tabContainer = this.parentNode;
let visibleTabs = tabContainer.tabbrowser.visibleTabs;
let visibleTabs = tabContainer._getVisibleTabs();
let tabIndex = visibleTabs.indexOf(this);
if (this.selected)
@ -1784,9 +1819,9 @@
let tabToWarm = this;
if (this.mOverCloseButton) {
tabToWarm = tabContainer.tabbrowser._findTabToBlurTo(this);
tabToWarm = gBrowser._findTabToBlurTo(this);
}
tabContainer.tabbrowser.warmupTab(tabToWarm);
gBrowser.warmupTab(tabToWarm);
]]></body>
</method>
@ -1871,7 +1906,6 @@
<parameter name="aMuteReason"/>
<body>
<![CDATA[
let tabContainer = this.parentNode;
let browser = this.linkedBrowser;
let modifiedAttrs = [];
let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED");
@ -1885,18 +1919,12 @@
this.finishMediaBlockTimer();
} else {
if (browser.audioMuted) {
if (this.linkedPanel) {
// "Lazy Browser" should not invoke its unmute method
browser.unmute();
}
browser.unmute();
this.removeAttribute("muted");
BrowserUITelemetry.countTabMutingEvent("unmute", aMuteReason);
hist.add(1 /* unmute */);
} else {
if (this.linkedPanel) {
// "Lazy Browser" should not invoke its mute method
browser.mute();
}
browser.mute();
this.setAttribute("muted", "true");
BrowserUITelemetry.countTabMutingEvent("mute", aMuteReason);
hist.add(0 /* mute */);
@ -1904,7 +1932,7 @@
this.muteReason = aMuteReason || null;
modifiedAttrs.push("muted");
}
tabContainer.tabbrowser._tabAttrModified(this, modifiedAttrs);
gBrowser._tabAttrModified(this, modifiedAttrs);
]]>
</body>
</method>
@ -1979,12 +2007,13 @@
}
if (event.originalTarget.getAttribute("anonid") == "close-button") {
let tabContainer = this.parentNode;
tabContainer.tabbrowser.removeTab(this, {animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
gBrowser.removeTab(this, {
animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
});
// This enables double-click protection for the tab container
// (see tabbrowser-tabs 'click' handler).
tabContainer._blockDblClick = true;
gBrowser.tabContainer._blockDblClick = true;
}
]]></handler>

View File

@ -30,6 +30,7 @@ run-if = debug || devedition || nightly_build # Requires startupRecorder.js, whi
skip-if = (os == 'linux') || (os == 'win' && debug) # Disabled on Linux and Windows debug due to perma failures. Bug 1392320.
[browser_urlbar_search_reflows.js]
skip-if = (debug || ccov) && (os == 'linux' || os == 'win') # Disabled on Linux and Windows debug and ccov due to intermittent timeouts. Bug 1414126, bug 1426611.
[browser_window_resize_reflows.js]
[browser_windowclose_reflows.js]
[browser_windowopen_flicker.js]
skip-if = (debug && os == 'win') # Disabled on windows debug for intermittent leaks

View File

@ -0,0 +1,138 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
* is a whitelist that should slowly go away as we improve the performance of
* the front-end. Instead of adding more reflows to the whitelist, you should
* be modifying your code to avoid the reflow.
*
* See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
* for tips on how to do that.
*/
const EXPECTED_REFLOWS = [
{
stack: [
"onOverflow@resource:///modules/CustomizableUI.jsm",
],
maxCount: 48,
},
{
stack: [
"_moveItemsBackToTheirOrigin@resource:///modules/CustomizableUI.jsm",
"_onLazyResize@resource:///modules/CustomizableUI.jsm",
],
maxCount: 5,
},
{
stack: [
"_onLazyResize@resource:///modules/CustomizableUI.jsm",
],
maxCount: 4,
},
];
const gToolbar = document.getElementById("PersonalToolbar");
/**
* Sets the visibility state on the Bookmarks Toolbar, and
* waits for it to transition to fully visible.
*
* @param visible (bool)
* Whether or not the bookmarks toolbar should be made visible.
* @returns Promise
*/
async function toggleBookmarksToolbar(visible) {
let transitionPromise =
BrowserTestUtils.waitForEvent(gToolbar, "transitionend",
e => e.propertyName == "max-height");
setToolbarVisibility(gToolbar, visible);
await transitionPromise;
}
/**
* Resizes a browser window to a particular width and height, and
* waits for it to reach a "steady state" with respect to its overflowing
* toolbars.
* @param win (browser window)
* The window to resize.
* @param width (int)
* The width to resize the window to.
* @param height (int)
* The height to resize the window to.
* @returns Promise
*/
async function resizeWindow(win, width, height) {
let toolbarEvent =
BrowserTestUtils.waitForEvent(win, "BookmarksToolbarVisibilityUpdated");
let resizeEvent =
BrowserTestUtils.waitForEvent(win, "resize");
let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
dwu.ensureDirtyRootFrame();
win.resizeTo(width, height);
await resizeEvent;
forceImmediateToolbarOverflowHandling(win);
await toolbarEvent;
}
/*
* This test ensures that there are no unexpected
* uninterruptible reflows when resizing windows.
*/
add_task(async function() {
const BOOKMARKS_COUNT = 150;
const STARTING_WIDTH = 600;
const STARTING_HEIGHT = 400;
const SMALL_WIDTH = 150;
const SMALL_HEIGHT = 150;
await PlacesUtils.bookmarks.eraseEverything();
// Add a bunch of bookmarks to display in the Bookmarks toolbar
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.toolbarGuid,
children: Array(BOOKMARKS_COUNT).fill("")
.map((_, i) => ({ url: `http://test.places.${i}/`}))
});
let wasCollapsed = gToolbar.collapsed;
Assert.ok(wasCollapsed, "The toolbar is collapsed by default");
if (wasCollapsed) {
let promiseReady =
BrowserTestUtils.waitForEvent(gToolbar, "BookmarksToolbarVisibilityUpdated");
await toggleBookmarksToolbar(true);
await promiseReady;
}
registerCleanupFunction(async () => {
if (wasCollapsed) {
await toggleBookmarksToolbar(false);
}
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.history.clear();
});
let win = await prepareSettledWindow();
if (win.screen.availWidth < STARTING_WIDTH ||
win.screen.availHeight < STARTING_HEIGHT) {
Assert.ok(false, "This test is running on too small a display - " +
`(${STARTING_WIDTH}x${STARTING_HEIGHT} min)`);
return;
}
await resizeWindow(win, STARTING_WIDTH, STARTING_HEIGHT);
await withReflowObserver(async function() {
await resizeWindow(win, SMALL_WIDTH, SMALL_HEIGHT);
await resizeWindow(win, STARTING_WIDTH, STARTING_HEIGHT);
}, EXPECTED_REFLOWS, win);
await BrowserTestUtils.closeWindow(win);
});

View File

@ -199,18 +199,33 @@ async function ensureNoPreloadedBrowser(win = window) {
});
}
/**
* The navigation toolbar is overflowable, meaning that some items
* will be moved and held within a sub-panel if the window gets too
* small to show their icons. The calculation for hiding those items
* occurs after resize events, and is debounced using a DeferredTask.
* This utility function allows us to fast-forward to just running
* that function for that DeferredTask instead of waiting for the
* debounce timeout to occur.
*/
function forceImmediateToolbarOverflowHandling(win) {
let overflowableToolbar = win.document.getElementById("nav-bar").overflowable;
if (overflowableToolbar._lazyResizeHandler && overflowableToolbar._lazyResizeHandler.isArmed) {
overflowableToolbar._lazyResizeHandler.disarm();
// Ensure the root frame is dirty before resize so that, if we're
// in the middle of a reflow test, we record the reflows deterministically.
let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
dwu.ensureDirtyRootFrame();
overflowableToolbar._onLazyResize();
}
}
async function prepareSettledWindow() {
let win = await BrowserTestUtils.openNewBrowserWindow();
await ensureNoPreloadedBrowser(win);
let overflowableToolbar = win.document.getElementById("nav-bar").overflowable;
if (overflowableToolbar._lazyResizeHandler && overflowableToolbar._lazyResizeHandler.isArmed) {
info("forcing deferred overflow handling of the navigation toolbar to happen immediately");
overflowableToolbar._lazyResizeHandler.disarm();
overflowableToolbar._onLazyResize();
}
forceImmediateToolbarOverflowHandling(win);
return win;
}

View File

@ -46,6 +46,10 @@ add_task(async function testSyncRemoteTabsButtonFunctionality() {
info("The sync now button was clicked");
await waitForCondition(() => syncWasCalled);
// We need to stop the Syncing animation manually otherwise the button
// will be disabled at the beginning of a next test.
gSync._onActivityStop();
});
add_task(async function asyncCleanup() {
@ -69,10 +73,10 @@ function mockFunctions() {
email: "user@mozilla.com"
});
service.sync = mocked_syncAndReportErrors;
service.sync = mocked_sync;
}
function mocked_syncAndReportErrors() {
function mocked_sync() {
syncWasCalled = true;
}

View File

@ -4,6 +4,7 @@
// The ext-* files are imported into the same scopes.
/* import-globals-from ext-browser.js */
/* import-globals-from ../../../toolkit/components/extensions/ext-tabs-base.js */
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
@ -97,6 +98,208 @@ let tabListener = {
},
};
const allAttrs = new Set(["audible", "favIconUrl", "mutedInfo", "sharingState", "title"]);
const allProperties = new Set([
"audible",
"discarded",
"favIconUrl",
"hidden",
"isarticle",
"mutedInfo",
"pinned",
"sharingState",
"status",
"title",
]);
const restricted = new Set(["url", "favIconUrl", "title"]);
class TabsUpdateFilterEventManager extends EventManager {
constructor(context, eventName) {
let {extension} = context;
let {tabManager} = extension;
let register = (fire, filterProps) => {
let filter = {...filterProps};
if (filter.urls) {
filter.urls = new MatchPatternSet(filter.urls);
}
let needsModified = true;
if (filter.properties) {
// Default is to listen for all events.
needsModified = filter.properties.some(p => allAttrs.has(p));
filter.properties = new Set(filter.properties);
} else {
filter.properties = allProperties;
}
function sanitize(extension, changeInfo) {
let result = {};
let nonempty = false;
let hasTabs = extension.hasPermission("tabs");
for (let prop in changeInfo) {
if (hasTabs || !restricted.has(prop)) {
nonempty = true;
result[prop] = changeInfo[prop];
}
}
return nonempty && result;
}
function getWindowID(windowId) {
if (windowId === WINDOW_ID_CURRENT) {
return windowTracker.getId(windowTracker.topWindow);
}
return windowId;
}
function matchFilters(tab, changed) {
if (!filterProps) {
return true;
}
if (filter.tabId != null && tab.id != filter.tabId) {
return false;
}
if (filter.windowId != null && tab.windowId != getWindowID(filter.windowId)) {
return false;
}
if (filter.urls) {
// We check permission first because tab.uri is null if !hasTabPermission.
return tab.hasTabPermission && filter.urls.matches(tab.uri);
}
return true;
}
let fireForTab = (tab, changed) => {
if (!matchFilters(tab, changed)) {
return;
}
let changeInfo = sanitize(extension, changed);
if (changeInfo) {
fire.async(tab.id, changeInfo, tab.convert());
}
};
let listener = event => {
let needed = [];
if (event.type == "TabAttrModified") {
let changed = event.detail.changed;
if (changed.includes("image") && filter.properties.has("favIconUrl")) {
needed.push("favIconUrl");
}
if (changed.includes("muted") && filter.properties.has("mutedInfo")) {
needed.push("mutedInfo");
}
if (changed.includes("soundplaying") && filter.properties.has("audible")) {
needed.push("audible");
}
if (changed.includes("label") && filter.properties.has("title")) {
needed.push("title");
}
if (changed.includes("sharing") && filter.properties.has("sharingState")) {
needed.push("sharingState");
}
} else if (event.type == "TabPinned") {
needed.push("pinned");
} else if (event.type == "TabUnpinned") {
needed.push("pinned");
} else if (event.type == "TabBrowserInserted" &&
!event.detail.insertedOnTabCreation) {
needed.push("discarded");
} else if (event.type == "TabBrowserDiscarded") {
needed.push("discarded");
} else if (event.type == "TabShow") {
needed.push("hidden");
} else if (event.type == "TabHide") {
needed.push("hidden");
}
let tab = tabManager.getWrapper(event.originalTarget);
let changeInfo = {};
for (let prop of needed) {
changeInfo[prop] = tab[prop];
}
fireForTab(tab, changeInfo);
};
let statusListener = ({browser, status, url}) => {
let {gBrowser} = browser.ownerGlobal;
let tabElem = gBrowser.getTabForBrowser(browser);
if (tabElem) {
let changed = {status};
if (url) {
changed.url = url;
}
fireForTab(tabManager.wrapTab(tabElem), changed);
}
};
let isArticleChangeListener = (messageName, message) => {
let {gBrowser} = message.target.ownerGlobal;
let nativeTab = gBrowser.getTabForBrowser(message.target);
if (nativeTab) {
let tab = tabManager.getWrapper(nativeTab);
fireForTab(tab, {isArticle: message.data.isArticle});
}
};
let listeners = new Map();
if (filter.properties.has("status")) {
listeners.set("status", statusListener);
}
if (needsModified) {
listeners.set("TabAttrModified", listener);
}
if (filter.properties.has("pinned")) {
listeners.set("TabPinned", listener);
listeners.set("TabUnpinned", listener);
}
if (filter.properties.has("discarded")) {
listeners.set("TabBrowserInserted", listener);
listeners.set("TabBrowserDiscarded", listener);
}
if (filter.properties.has("hidden")) {
listeners.set("TabShow", listener);
listeners.set("TabHide", listener);
}
for (let [name, listener] of listeners) {
windowTracker.addListener(name, listener);
}
if (filter.properties.has("isarticle")) {
tabTracker.on("tab-isarticle", isArticleChangeListener);
}
return () => {
for (let [name, listener] of listeners) {
windowTracker.removeListener(name, listener);
}
if (filter.properties.has("isarticle")) {
tabTracker.off("tab-isarticle", isArticleChangeListener);
}
};
};
super(context, eventName, register);
}
addListener(callback, filter) {
let {extension} = this.context;
if (filter && filter.urls &&
(!extension.hasPermission("tabs") && !extension.hasPermission("activeTab"))) {
Cu.reportError("Url filtering in tabs.onUpdated requires \"tabs\" or \"activeTab\" permission.");
return false;
}
return super.addListener(callback, filter);
}
}
this.tabs = class extends ExtensionAPI {
static onUpdate(id, manifest) {
if (!manifest.permissions || !manifest.permissions.includes("tabHide")) {
@ -254,117 +457,7 @@ this.tabs = class extends ExtensionAPI {
};
}).api(),
onUpdated: new EventManager(context, "tabs.onUpdated", fire => {
const restricted = ["url", "favIconUrl", "title"];
function sanitize(extension, changeInfo) {
let result = {};
let nonempty = false;
for (let prop in changeInfo) {
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
nonempty = true;
result[prop] = changeInfo[prop];
}
}
return [nonempty, result];
}
let fireForTab = (tab, changed) => {
let [needed, changeInfo] = sanitize(extension, changed);
if (needed) {
fire.async(tab.id, changeInfo, tab.convert());
}
};
let listener = event => {
let needed = [];
if (event.type == "TabAttrModified") {
let changed = event.detail.changed;
if (changed.includes("image")) {
needed.push("favIconUrl");
}
if (changed.includes("muted")) {
needed.push("mutedInfo");
}
if (changed.includes("soundplaying")) {
needed.push("audible");
}
if (changed.includes("label")) {
needed.push("title");
}
if (changed.includes("sharing")) {
needed.push("sharingState");
}
} else if (event.type == "TabPinned") {
needed.push("pinned");
} else if (event.type == "TabUnpinned") {
needed.push("pinned");
} else if (event.type == "TabBrowserInserted" &&
!event.detail.insertedOnTabCreation) {
needed.push("discarded");
} else if (event.type == "TabBrowserDiscarded") {
needed.push("discarded");
} else if (event.type == "TabShow") {
needed.push("hidden");
} else if (event.type == "TabHide") {
needed.push("hidden");
}
let tab = tabManager.getWrapper(event.originalTarget);
let changeInfo = {};
for (let prop of needed) {
changeInfo[prop] = tab[prop];
}
fireForTab(tab, changeInfo);
};
let statusListener = ({browser, status, url}) => {
let {gBrowser} = browser.ownerGlobal;
let tabElem = gBrowser.getTabForBrowser(browser);
if (tabElem) {
let changed = {status};
if (url) {
changed.url = url;
}
fireForTab(tabManager.wrapTab(tabElem), changed);
}
};
let isArticleChangeListener = (messageName, message) => {
let {gBrowser} = message.target.ownerGlobal;
let nativeTab = gBrowser.getTabForBrowser(message.target);
if (nativeTab) {
let tab = tabManager.getWrapper(nativeTab);
fireForTab(tab, {isArticle: message.data.isArticle});
}
};
windowTracker.addListener("status", statusListener);
windowTracker.addListener("TabAttrModified", listener);
windowTracker.addListener("TabPinned", listener);
windowTracker.addListener("TabUnpinned", listener);
windowTracker.addListener("TabBrowserInserted", listener);
windowTracker.addListener("TabBrowserDiscarded", listener);
windowTracker.addListener("TabShow", listener);
windowTracker.addListener("TabHide", listener);
tabTracker.on("tab-isarticle", isArticleChangeListener);
return () => {
windowTracker.removeListener("status", statusListener);
windowTracker.removeListener("TabAttrModified", listener);
windowTracker.removeListener("TabPinned", listener);
windowTracker.removeListener("TabUnpinned", listener);
windowTracker.removeListener("TabBrowserInserted", listener);
windowTracker.removeListener("TabBrowserDiscarded", listener);
windowTracker.removeListener("TabShow", listener);
windowTracker.removeListener("TabHide", listener);
tabTracker.off("tab-isarticle", isArticleChangeListener);
};
}).api(),
onUpdated: new TabsUpdateFilterEventManager(context, "tabs.onUpdated").api(),
create(createProperties) {
return new Promise((resolve, reject) => {

View File

@ -288,6 +288,46 @@
"type": "string",
"enum": ["normal", "popup", "panel", "app", "devtools"],
"description": "The type of window."
},
{
"id": "UpdatePropertyName",
"type": "string",
"enum": [
"audible",
"discarded",
"favIconUrl",
"hidden",
"isarticle",
"mutedInfo",
"pinned",
"sharingState",
"status",
"title"
],
"description": "Event names supported in onUpdated."
},
{
"id": "UpdateFilter",
"type": "object",
"description": "An object describing filters to apply to tabs.onUpdated events.",
"properties": {
"urls": {
"type": "array",
"description": "A list of URLs or URL patterns. Events that cannot match any of the URLs will be filtered out. Filtering with urls requires the <code>\"tabs\"</code> or <code>\"activeTab\"</code> permission.",
"optional": true,
"items": { "type": "string" },
"minItems": 1
},
"properties": {
"type": "array",
"optional": true,
"description": "A list of property names. Events that do not match any of the names will be filtered out.",
"items": { "$ref": "UpdatePropertyName" },
"minItems": 1
},
"tabId": { "type": "integer", "optional": true },
"windowId": { "type": "integer", "optional": true }
}
}
],
"properties": {
@ -1400,6 +1440,14 @@
"name": "tab",
"description": "Gives the state of the tab that was updated."
}
],
"extraParameters": [
{
"$ref": "UpdateFilter",
"name": "filter",
"optional": true,
"description": "A set of filters that restricts the events that will be sent to this listener."
}
]
},
{

View File

@ -178,6 +178,7 @@ skip-if = !e10s
[browser_ext_tabs_move_window_pinned.js]
[browser_ext_tabs_onHighlighted.js]
[browser_ext_tabs_onUpdated.js]
[browser_ext_tabs_onUpdated_filter.js]
[browser_ext_tabs_opener.js]
[browser_ext_tabs_printPreview.js]
[browser_ext_tabs_query.js]

View File

@ -0,0 +1,241 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(async function test_filter_url() {
let ext_fail = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
browser.test.fail(`received unexpected onUpdated event ${JSON.stringify(changeInfo)}`);
}, {urls: ["*://*.mozilla.org/*"]});
},
});
await ext_fail.startup();
let ext_perm = ExtensionTestUtils.loadExtension({
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
browser.test.fail(`received unexpected onUpdated event without tabs permission`);
}, {urls: ["*://mochi.test/*"]});
},
});
await ext_perm.startup();
let ext_ok = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
browser.test.log(`got onUpdated ${JSON.stringify(changeInfo)}`);
if (changeInfo.status === "complete") {
browser.test.notifyPass("onUpdated");
}
}, {urls: ["*://mochi.test/*"]});
},
});
await ext_ok.startup();
let ok1 = ext_ok.awaitFinish("onUpdated");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
await ok1;
await ext_ok.unload();
await ext_fail.unload();
await ext_perm.unload();
await BrowserTestUtils.removeTab(tab);
});
add_task(async function test_filter_url_activeTab() {
let ext = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["activeTab"],
},
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
browser.test.fail("should only have notification for activeTab, selectedTab is not activeTab");
}, {urls: ["*://mochi.test/*"]});
},
});
await ext.startup();
let ext2 = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.status === "complete") {
browser.test.notifyPass("onUpdated");
}
}, {urls: ["*://mochi.test/*"]});
},
});
await ext2.startup();
let ok = ext2.awaitFinish("onUpdated");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/#foreground");
await Promise.all([ok]);
await ext.unload();
await ext2.unload();
await BrowserTestUtils.removeTab(tab);
});
add_task(async function test_filter_tabId() {
let ext_fail = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
browser.test.fail(`received unexpected onUpdated event ${JSON.stringify(changeInfo)}`);
}, {tabId: 12345});
},
});
await ext_fail.startup();
let ext_ok = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.status === "complete") {
browser.test.notifyPass("onUpdated");
}
});
},
});
await ext_ok.startup();
let ok = ext_ok.awaitFinish("onUpdated");
let ext_ok2 = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
browser.tabs.onCreated.addListener(tab => {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.status === "complete") {
browser.test.notifyPass("onUpdated");
}
}, {tabId: tab.id});
browser.test.log(`Tab specific tab listener on tab ${tab.id}`);
});
},
});
await ext_ok2.startup();
let ok2 = ext_ok2.awaitFinish("onUpdated");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
await Promise.all([ok, ok2]);
await ext_ok.unload();
await ext_ok2.unload();
await ext_fail.unload();
await BrowserTestUtils.removeTab(tab);
});
add_task(async function test_filter_windowId() {
let ext_fail = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
browser.test.fail(`received unexpected onUpdated event ${JSON.stringify(changeInfo)}`);
}, {windowId: 12345});
},
});
await ext_fail.startup();
let ext_ok = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.status === "complete") {
browser.test.notifyPass("onUpdated");
}
}, {windowId: browser.windows.WINDOW_ID_CURRENT});
},
});
await ext_ok.startup();
let ok = ext_ok.awaitFinish("onUpdated");
let ext_ok2 = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
async background() {
let window = await browser.windows.getCurrent();
browser.test.log(`Window specific tab listener on window ${window.id}`);
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.status === "complete") {
browser.test.notifyPass("onUpdated");
}
}, {windowId: window.id});
browser.test.sendMessage("ready");
},
});
await ext_ok2.startup();
await ext_ok2.awaitMessage("ready");
let ok2 = ext_ok2.awaitFinish("onUpdated");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
await Promise.all([ok, ok2]);
await ext_ok.unload();
await ext_ok2.unload();
await ext_fail.unload();
await BrowserTestUtils.removeTab(tab);
});
add_task(async function test_filter_property() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
background() {
// We expect only status updates, anything else is a failure.
let properties = new Set([
"audible",
"discarded",
"favIconUrl",
"hidden",
"isarticle",
"mutedInfo",
"pinned",
"sharingState",
"title",
]);
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
browser.test.log(`got onUpdated ${JSON.stringify(changeInfo)}`);
browser.test.assertTrue(!!changeInfo.status, "changeInfo has status");
if (Object.keys(changeInfo).some(p => properties.has(p))) {
browser.test.fail(`received unexpected onUpdated event ${JSON.stringify(changeInfo)}`);
}
if (changeInfo.status === "complete") {
browser.test.notifyPass("onUpdated");
}
}, {properties: ["status"]});
},
});
await extension.startup();
let ok = extension.awaitFinish("onUpdated");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
await ok;
await extension.unload();
await BrowserTestUtils.removeTab(tab);
});

View File

@ -19,6 +19,7 @@ let expectedBackgroundApisTargetSpecific = [
"tabs.MutedInfoReason",
"tabs.TAB_ID_NONE",
"tabs.TabStatus",
"tabs.UpdatePropertyName",
"tabs.WindowType",
"tabs.ZoomSettingsMode",
"tabs.ZoomSettingsScope",

View File

@ -1000,6 +1000,8 @@ function PlacesToolbar(aPlace) {
this._addEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
}
this._updatingNodesVisibility = false;
PlacesViewBase.call(this, aPlace);
Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS")
@ -1253,34 +1255,55 @@ PlacesToolbar.prototype = {
this._updateNodesVisibilityTimer = this._setTimer(100);
},
_updateNodesVisibilityTimerCallback: function PT__updateNodesVisibilityTimerCallback() {
let scrollRect = this._rootElt.getBoundingClientRect();
let childOverflowed = false;
for (let child of this._rootElt.childNodes) {
// Once a child overflows, all the next ones will.
if (!childOverflowed) {
let childRect = child.getBoundingClientRect();
childOverflowed = this.isRTL ? (childRect.left < scrollRect.left)
: (childRect.right > scrollRect.right);
}
if (childOverflowed) {
child.removeAttribute("image");
child.style.visibility = "hidden";
} else {
let icon = child._placesNode.icon;
if (icon)
child.setAttribute("image", icon);
child.style.visibility = "visible";
}
async _updateNodesVisibilityTimerCallback() {
if (this._updatingNodesVisibility || window.closed) {
return;
}
this._updatingNodesVisibility = true;
// We rebuild the chevron on popupShowing, so if it is open
// we must update it.
if (!this._chevron.collapsed && this._chevron.open)
this._updateChevronPopupNodesVisibility();
let event = new CustomEvent("BookmarksToolbarVisibilityUpdated", {bubbles: true});
this._viewElt.dispatchEvent(event);
let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let scrollRect =
await window.promiseDocumentFlushed(() => dwu.getBoundsWithoutFlushing(this._rootElt));
let childOverflowed = false;
// We're about to potentially update a bunch of nodes, so we do it
// in a requestAnimationFrame so that other JS that's might execute
// in the same tick can avoid flushing styles and layout for these
// changes.
window.requestAnimationFrame(() => {
for (let child of this._rootElt.childNodes) {
// Once a child overflows, all the next ones will.
if (!childOverflowed) {
let childRect = dwu.getBoundsWithoutFlushing(child);
childOverflowed = this.isRTL ? (childRect.left < scrollRect.left)
: (childRect.right > scrollRect.right);
}
if (childOverflowed) {
child.removeAttribute("image");
child.style.visibility = "hidden";
} else {
let icon = child._placesNode.icon;
if (icon)
child.setAttribute("image", icon);
child.style.visibility = "visible";
}
}
// We rebuild the chevron on popupShowing, so if it is open
// we must update it.
if (!this._chevron.collapsed && this._chevron.open) {
this._updateChevronPopupNodesVisibility();
}
let event = new CustomEvent("BookmarksToolbarVisibilityUpdated", {bubbles: true});
this._viewElt.dispatchEvent(event);
});
this._updatingNodesVisibility = false;
},
nodeInserted:
@ -1608,9 +1631,7 @@ PlacesToolbar.prototype = {
notify: function PT_notify(aTimer) {
if (aTimer == this._updateNodesVisibilityTimer) {
this._updateNodesVisibilityTimer = null;
// Bug 1440070: This should use promiseDocumentFlushed, so that
// _updateNodesVisibilityTimerCallback can use getBoundsWithoutFlush.
window.requestAnimationFrame(this._updateNodesVisibilityTimerCallback.bind(this));
this._updateNodesVisibilityTimerCallback();
} else if (aTimer == this._ibTimer) {
// * Timer to turn off indicator bar.
this._dropIndicator.collapsed = true;

View File

@ -467,15 +467,12 @@ var gMainPane = {
OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
() => separateProfileModeCheckbox.checked = true);
if (!Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
document.getElementById("sync-dev-edition-root").hidden = true;
return;
if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
document.getElementById("sync-dev-edition-root").hidden = false;
fxAccounts.getSignedInUser().then(data => {
document.getElementById("getStarted").selectedIndex = data ? 1 : 0;
}).catch(Cu.reportError);
}
fxAccounts.getSignedInUser().then(data => {
document.getElementById("getStarted").selectedIndex = data ? 1 : 0;
})
.catch(Cu.reportError);
}
// Initialize the Firefox Updates section.

View File

@ -33,7 +33,7 @@
<vbox id="separateProfileBox">
<checkbox id="separateProfileMode"
data-l10n-id="separate-profile-mode"/>
<hbox id="sync-dev-edition-root" lign="center" class="indent">
<hbox id="sync-dev-edition-root" lign="center" class="indent" hidden="true">
<label id="useFirefoxSync" data-l10n-id="use-firefox-sync"/>
<deck id="getStarted">
<label class="text-link" data-l10n-id="get-started-not-logged-in"/>

View File

@ -660,7 +660,7 @@ notification[value="translation"] menulist > .menulist-dropmarker {
/* Add extra space to titlebar for dragging */
:root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar,
:root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar {
margin-top: var(--space-above-tabbar);
padding-top: var(--space-above-tabbar);
}
/* Private browsing and accessibility indicators */
@ -668,7 +668,15 @@ notification[value="translation"] menulist > .menulist-dropmarker {
:root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar > .accessibility-indicator,
:root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .private-browsing-indicator,
:root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .accessibility-indicator {
margin-top: calc(-1 * var(--space-above-tabbar));
padding-top: calc(-1 * var(--space-above-tabbar));
}
/* Make #TabsToolbar transparent as we style underlying #titlebar with
* -moz-window-titlebar (Gtk+ theme).
*/
:root[tabsintitlebar] #TabsToolbar,
:root[tabsintitlebar] #toolbar-menubar {
-moz-appearance: none;
}
/* The button box must appear on top of the navigator-toolbox in order for

View File

@ -667,7 +667,7 @@ function getAvailableIds() {
* Show available ids.
*/
function showAvailableIds() {
info(getAvailableIds);
info(getAvailableIds());
}
/**

View File

@ -807,22 +807,18 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
pluginHost->GetPluginTagForInstance(pluginInstance,
getter_AddRefs(pluginTag));
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
if (blocklist) {
uint32_t blockState = nsIBlocklistService::STATE_NOT_BLOCKED;
blocklist->GetPluginBlocklistState(pluginTag, EmptyString(),
EmptyString(), &blockState);
if (blockState == nsIBlocklistService::STATE_OUTDATED) {
// Fire plugin outdated event if necessary
LOG(("OBJLC [%p]: Dispatching plugin outdated event for content\n",
this));
nsCOMPtr<nsIRunnable> ev = new nsSimplePluginEvent(thisContent,
NS_LITERAL_STRING("PluginOutdated"));
nsresult rv = NS_DispatchToCurrentThread(ev);
if (NS_FAILED(rv)) {
NS_WARNING("failed to dispatch nsSimplePluginEvent");
}
uint32_t blockState = nsIBlocklistService::STATE_NOT_BLOCKED;
pluginTag->GetBlocklistState(&blockState);
if (blockState == nsIBlocklistService::STATE_OUTDATED) {
// Fire plugin outdated event if necessary
LOG(("OBJLC [%p]: Dispatching plugin outdated event for content\n",
this));
nsCOMPtr<nsIRunnable> ev = new nsSimplePluginEvent(thisContent,
NS_LITERAL_STRING("PluginOutdated"));
nsresult rv = NS_DispatchToCurrentThread(ev);
if (NS_FAILED(rv)) {
NS_WARNING("failed to dispatch nsSimplePluginEvent");
}
}

View File

@ -397,7 +397,8 @@ nsPluginArray::EnsurePlugins()
if (mPlugins.Length() == 0 && mCTPPlugins.Length() != 0) {
nsCOMPtr<nsPluginTag> hiddenTag = new nsPluginTag("Hidden Plugin", nullptr, "dummy.plugin", nullptr, nullptr,
nullptr, nullptr, nullptr, 0, 0, false);
nullptr, nullptr, nullptr, 0, 0, false,
nsIBlocklistService::STATE_NOT_BLOCKED);
mPlugins.AppendElement(new nsPluginElement(mWindow, hiddenTag));
}

View File

@ -134,7 +134,7 @@ static const char *kPrefDisableFullPage = "plugin.disable_full_page_plugin_for_t
static const char *kPrefUnloadPluginTimeoutSecs = "dom.ipc.plugins.unloadTimeoutSecs";
static const uint32_t kDefaultPluginUnloadingTimeout = 30;
static const char *kPluginRegistryVersion = "0.18";
static const char *kPluginRegistryVersion = "0.19";
static const char kDirectoryServiceContractID[] = "@mozilla.org/file/directory_service;1";
@ -266,7 +266,10 @@ nsPluginHost::nsPluginHost()
mozilla::services::GetObserverService();
if (obsService) {
obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
obsService->AddObserver(this, "blocklist-updated", false);
if (XRE_IsParentProcess()) {
obsService->AddObserver(this, "blocklist-updated", false);
obsService->AddObserver(this, "blocklist-loaded", false);
}
}
#ifdef PLUGIN_LOGGING
@ -1999,6 +2002,13 @@ nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir,
nsCOMArray<nsIFile> extensionDirs;
GetExtensionDirectories(extensionDirs);
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
bool isBlocklistLoaded = false;
if (blocklist && NS_FAILED(blocklist->GetIsLoaded(&isBlocklistLoaded))) {
isBlocklistLoaded = false;
}
for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) {
nsCOMPtr<nsIFile>& localfile = pluginFiles[i];
@ -2090,12 +2100,17 @@ nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir,
continue;
}
pluginTag = new nsPluginTag(&info, fileModTime, fromExtension);
pluginFile.FreePluginInfo(info);
uint32_t state = nsIBlocklistService::STATE_NOT_BLOCKED;
pluginTag = new nsPluginTag(&info, fileModTime, fromExtension, state);
pluginTag->mLibrary = library;
uint32_t state;
rv = pluginTag->GetBlocklistState(&state);
NS_ENSURE_SUCCESS(rv, rv);
// If the blocklist is loaded, get the blocklist state now.
// If it isn't loaded yet, we'll update it once it loads.
if (isBlocklistLoaded &&
NS_SUCCEEDED(blocklist->GetPluginBlocklistState(pluginTag, EmptyString(),
EmptyString(), &state))) {
pluginTag->SetBlocklistState(state);
}
pluginFile.FreePluginInfo(info);
// If the blocklist says it is risky and we have never seen this
// plugin before, then disable it.
@ -2744,8 +2759,8 @@ nsPluginHost::WritePluginInfo()
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
// lastModifiedTimeStamp|canUnload|tag->mFlags|fromExtension
PR_fprintf(fd, "%lld%c%d%c%lu%c%d%c%c\n",
// lastModifiedTimeStamp|canUnload|tag->mFlags|fromExtension|blocklistState
PR_fprintf(fd, "%lld%c%d%c%lu%c%d%c%d%c%c\n",
tag->mLastModifiedTime,
PLUGIN_REGISTRY_FIELD_DELIMITER,
false, // did store whether or not to unload in-process plugins
@ -2754,6 +2769,8 @@ nsPluginHost::WritePluginInfo()
PLUGIN_REGISTRY_FIELD_DELIMITER,
tag->IsFromExtension(),
PLUGIN_REGISTRY_FIELD_DELIMITER,
tag->BlocklistState(),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
//description, name & mtypecount are on separate line
@ -2974,12 +2991,13 @@ nsPluginHost::ReadPluginInfo()
if (!reader.NextLine())
return rv;
// lastModifiedTimeStamp|canUnload|tag.mFlag|fromExtension
if (4 != reader.ParseLine(values, 4))
// lastModifiedTimeStamp|canUnload|tag.mFlag|fromExtension|blocklistState
if (5 != reader.ParseLine(values, 5))
return rv;
int64_t lastmod = nsCRT::atoll(values[0]);
bool fromExtension = atoi(values[3]);
uint16_t blocklistState = atoi(values[4]);
if (!reader.NextLine())
return rv;
@ -3040,7 +3058,7 @@ nsPluginHost::ReadPluginInfo()
(const char* const*)mimetypes,
(const char* const*)mimedescriptions,
(const char* const*)extensions,
mimetypecount, lastmod, fromExtension, true);
mimetypecount, lastmod, fromExtension, blocklistState, true);
delete [] heapalloced;
@ -3389,15 +3407,32 @@ NS_IMETHODIMP nsPluginHost::Observe(nsISupports *aSubject,
LoadPlugins();
}
}
if (!strcmp("blocklist-updated", aTopic)) {
if (XRE_IsParentProcess() &&
(!strcmp("blocklist-updated", aTopic) || !strcmp("blocklist-loaded", aTopic))) {
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
if (!blocklist) {
return NS_OK;
}
nsPluginTag* plugin = mPlugins;
bool blocklistAlteredPlugins = false;
while (plugin) {
plugin->InvalidateBlocklistState();
uint32_t blocklistState = nsIBlocklistService::STATE_NOT_BLOCKED;
nsresult rv = blocklist->GetPluginBlocklistState(plugin, EmptyString(),
EmptyString(), &blocklistState);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t oldBlocklistState;
plugin->GetBlocklistState(&oldBlocklistState);
plugin->SetBlocklistState(blocklistState);
blocklistAlteredPlugins |= (oldBlocklistState != blocklistState);
plugin = plugin->mNext;
}
// We update blocklists asynchronously by just sending a new plugin list to
// content.
if (XRE_IsParentProcess()) {
if (blocklistAlteredPlugins) {
// Write the changed list to disk:
WritePluginInfo();
// We update blocklists asynchronously by just sending a new plugin list to
// content.
// We'll need to repack our tags and send them to content again.
IncrementChromeEpoch();
SendPluginsToContent();

View File

@ -223,7 +223,8 @@ uint32_t nsPluginTag::sNextId;
nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo,
int64_t aLastModifiedTime,
bool fromExtension)
bool fromExtension,
uint32_t aBlocklistState)
: nsIInternalPluginTag(aPluginInfo->fName, aPluginInfo->fDescription,
aPluginInfo->fFileName, aPluginInfo->fVersion),
mId(sNextId++),
@ -236,9 +237,8 @@ nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo,
mLastModifiedTime(aLastModifiedTime),
mSandboxLevel(0),
mIsSandboxLoggingEnabled(false),
mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED),
mCachedBlocklistStateValid(false),
mIsFromExtension(fromExtension)
mIsFromExtension(fromExtension),
mBlocklistState(aBlocklistState)
{
InitMime(aPluginInfo->fMimeTypeArray,
aPluginInfo->fMimeDescriptionArray,
@ -260,6 +260,7 @@ nsPluginTag::nsPluginTag(const char* aName,
int32_t aVariants,
int64_t aLastModifiedTime,
bool fromExtension,
uint32_t aBlocklistState,
bool aArgsAreUTF8)
: nsIInternalPluginTag(aName, aDescription, aFileName, aVersion),
mId(sNextId++),
@ -272,9 +273,8 @@ nsPluginTag::nsPluginTag(const char* aName,
mLastModifiedTime(aLastModifiedTime),
mSandboxLevel(0),
mIsSandboxLoggingEnabled(false),
mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED),
mCachedBlocklistStateValid(false),
mIsFromExtension(fromExtension)
mIsFromExtension(fromExtension),
mBlocklistState(aBlocklistState)
{
InitMime(aMimeTypes, aMimeDescriptions, aExtensions,
static_cast<uint32_t>(aVariants));
@ -298,7 +298,7 @@ nsPluginTag::nsPluginTag(uint32_t aId,
int64_t aLastModifiedTime,
bool aFromExtension,
int32_t aSandboxLevel,
uint16_t aBlocklistState)
uint32_t aBlocklistState)
: nsIInternalPluginTag(aName, aDescription, aFileName, aVersion, aMimeTypes,
aMimeDescriptions, aExtensions),
mId(aId),
@ -310,9 +310,8 @@ nsPluginTag::nsPluginTag(uint32_t aId,
mSandboxLevel(aSandboxLevel),
mIsSandboxLoggingEnabled(false),
mNiceFileName(),
mCachedBlocklistState(aBlocklistState),
mCachedBlocklistStateValid(true),
mIsFromExtension(aFromExtension)
mIsFromExtension(aFromExtension),
mBlocklistState(aBlocklistState)
{
}
@ -548,9 +547,7 @@ nsPluginTag::GetDisabled(bool* aDisabled)
bool
nsPluginTag::IsBlocklisted()
{
uint32_t blocklistState;
nsresult rv = GetBlocklistState(&blocklistState);
return NS_FAILED(rv) || blocklistState == nsIBlocklistService::STATE_BLOCKED;
return mBlocklistState == nsIBlocklistService::STATE_BLOCKED;
}
NS_IMETHODIMP
@ -722,48 +719,20 @@ nsPluginTag::GetNiceName(nsACString & aResult)
NS_IMETHODIMP
nsPluginTag::GetBlocklistState(uint32_t *aResult)
{
// If we're in the content process, assume our cache state to always be valid,
// as the only way it can be updated is via a plugin list push from the
// parent process.
if (!XRE_IsParentProcess()) {
*aResult = mCachedBlocklistState;
return NS_OK;
}
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
if (!blocklist) {
*aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
}
// The EmptyString()s are so we use the currently running application
// and toolkit versions
else if (NS_FAILED(blocklist->GetPluginBlocklistState(this, EmptyString(),
EmptyString(), aResult))) {
*aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
}
MOZ_ASSERT(*aResult <= UINT16_MAX);
mCachedBlocklistState = (uint16_t) *aResult;
mCachedBlocklistStateValid = true;
*aResult = mBlocklistState;
return NS_OK;
}
void
nsPluginTag::SetBlocklistState(uint16_t aBlocklistState)
nsPluginTag::SetBlocklistState(uint32_t aBlocklistState)
{
// We should only ever call this on content processes. Any calls in the parent
// process should route through GetBlocklistState since we'll have the
// blocklist service there.
MOZ_ASSERT(!XRE_IsParentProcess());
mCachedBlocklistState = aBlocklistState;
mCachedBlocklistStateValid = true;
mBlocklistState = aBlocklistState;
}
void
nsPluginTag::InvalidateBlocklistState()
uint32_t
nsPluginTag::BlocklistState()
{
mCachedBlocklistStateValid = false;
return mBlocklistState;
}
NS_IMETHODIMP

View File

@ -10,6 +10,7 @@
#include "nscore.h"
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsIBlocklistService.h"
#include "nsIPluginTag.h"
#include "nsITimer.h"
#include "nsString.h"
@ -108,7 +109,8 @@ public:
nsPluginTag(nsPluginInfo* aPluginInfo,
int64_t aLastModifiedTime,
bool fromExtension);
bool fromExtension,
uint32_t aBlocklistState);
nsPluginTag(const char* aName,
const char* aDescription,
const char* aFileName,
@ -120,6 +122,7 @@ public:
int32_t aVariants,
int64_t aLastModifiedTime,
bool fromExtension,
uint32_t aBlocklistState,
bool aArgsAreUTF8 = false);
nsPluginTag(uint32_t aId,
const char* aName,
@ -135,7 +138,7 @@ public:
int64_t aLastModifiedTime,
bool aFromExtension,
int32_t aSandboxLevel,
uint16_t aBlocklistState);
uint32_t aBlocklistState);
void TryUnloadPlugin(bool inShutdown);
@ -146,10 +149,11 @@ public:
void SetEnabled(bool enabled);
bool IsClicktoplay();
bool IsBlocklisted();
uint32_t BlocklistState();
PluginState GetPluginState();
void SetPluginState(PluginState state);
void SetBlocklistState(uint16_t aBlocklistState);
void SetBlocklistState(uint32_t aBlocklistState);
bool HasSameNameAndMimes(const nsPluginTag *aPluginTag) const;
const nsCString& GetNiceFileName() override;
@ -175,15 +179,12 @@ public:
int32_t mSandboxLevel;
bool mIsSandboxLoggingEnabled;
void InvalidateBlocklistState();
private:
virtual ~nsPluginTag();
nsCString mNiceFileName; // UTF-8
uint16_t mCachedBlocklistState;
bool mCachedBlocklistStateValid;
bool mIsFromExtension;
uint32_t mBlocklistState;
void InitMime(const char* const* aMimeTypes,
const char* const* aMimeDescriptions,

View File

@ -229,6 +229,8 @@ void
Layer::SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller)
{
MOZ_ASSERT(aIndex < GetScrollMetadataCount());
// We should never be setting an APZC on a non-scrollable layer
MOZ_ASSERT(!controller || GetFrameMetrics(aIndex).IsScrollable());
mApzcs[aIndex] = controller;
}

View File

@ -1798,6 +1798,10 @@ public:
// and can be used anytime.
// A layer has an APZC at index aIndex only-if GetFrameMetrics(aIndex).IsScrollable();
// attempting to get an APZC for a non-scrollable metrics will return null.
// The reverse is also true (that if GetFrameMetrics(aIndex).IsScrollable()
// is true, then the layer will have an APZC), although that only holds on
// the compositor-side layer tree, and only after the APZ code has had a chance
// to rebuild its internal hit-testing tree using the layer tree.
// The aIndex for these functions must be less than GetScrollMetadataCount().
void SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller);
AsyncPanZoomController* GetAsyncPanZoomController(uint32_t aIndex) const;

View File

@ -7,9 +7,12 @@
#ifndef mozilla_layers_APZSampler_h
#define mozilla_layers_APZSampler_h
#include "LayersTypes.h"
#include "mozilla/layers/APZTestData.h"
#include "mozilla/layers/AsyncCompositionManager.h" // for AsyncTransform
#include "mozilla/Maybe.h"
#include "nsTArray.h"
#include "Units.h"
namespace mozilla {
@ -25,6 +28,8 @@ namespace layers {
class APZCTreeManager;
class FocusTarget;
class Layer;
class LayerMetricsWrapper;
struct ScrollThumbData;
class WebRenderScrollData;
/**
@ -70,6 +75,35 @@ public:
const FrameMetrics::ViewID& aScrollId,
const LayerToParentLayerScale& aZoom);
bool SampleAnimations(const LayerMetricsWrapper& aLayer,
const TimeStamp& aSampleTime);
/**
* Compute the updated shadow transform for a scroll thumb layer that
* reflects async scrolling of the associated scroll frame.
*
* Refer to APZCTreeManager::ComputeTransformForScrollThumb for the
* description of parameters. The only difference is that this function takes
* |aContent| instead of |aApzc| and |aMetrics|; aContent is the
* LayerMetricsWrapper corresponding to the scroll frame that is scrolled by
* the scroll thumb, and so the APZC and metrics can be obtained from
* |aContent|.
*/
LayerToParentLayerMatrix4x4 ComputeTransformForScrollThumb(
const LayerToParentLayerMatrix4x4& aCurrentTransform,
const LayerMetricsWrapper& aContent,
const ScrollThumbData& aThumbData,
bool aScrollbarIsDescendant,
AsyncTransformComponentMatrix* aOutClipTransform);
ParentLayerPoint GetCurrentAsyncScrollOffset(const LayerMetricsWrapper& aLayer);
AsyncTransform GetCurrentAsyncTransform(const LayerMetricsWrapper& aLayer);
AsyncTransformComponentMatrix GetOverscrollTransform(const LayerMetricsWrapper& aLayer);
AsyncTransformComponentMatrix GetCurrentAsyncTransformWithOverscroll(const LayerMetricsWrapper& aLayer);
void MarkAsyncTransformAppliedToContent(const LayerMetricsWrapper& aLayer);
bool HasUnusedAsyncTransform(const LayerMetricsWrapper& aLayer);
protected:
virtual ~APZSampler();

View File

@ -609,7 +609,7 @@ APZCTreeManager::PushStateToWR(wr::TransactionBuilder& aTxn,
MOZ_ASSERT(scrollTargetApzc);
LayerToParentLayerMatrix4x4 transform = scrollTargetApzc->CallWithLastContentPaintMetrics(
[&](const FrameMetrics& aMetrics) {
return AsyncCompositionManager::ComputeTransformForScrollThumb(
return ComputeTransformForScrollThumb(
aNode->GetTransform() * AsyncTransformMatrix(),
scrollTargetNode->GetTransform().ToUnknownMatrix(),
scrollTargetApzc,
@ -2936,7 +2936,7 @@ APZCTreeManager::ComputeTransformForNode(const HitTestingTreeNode* aNode) const
MOZ_ASSERT(scrollTargetApzc);
return scrollTargetApzc->CallWithLastContentPaintMetrics(
[&](const FrameMetrics& aMetrics) {
return AsyncCompositionManager::ComputeTransformForScrollThumb(
return ComputeTransformForScrollThumb(
aNode->GetTransform() * AsyncTransformMatrix(),
scrollTargetNode->GetTransform().ToUnknownMatrix(),
scrollTargetApzc,
@ -2989,6 +2989,168 @@ APZCTreeManager::GetAPZTestData(uint64_t aLayersId,
return true;
}
/*static*/ LayerToParentLayerMatrix4x4
APZCTreeManager::ComputeTransformForScrollThumb(
const LayerToParentLayerMatrix4x4& aCurrentTransform,
const Matrix4x4& aScrollableContentTransform,
AsyncPanZoomController* aApzc,
const FrameMetrics& aMetrics,
const ScrollThumbData& aThumbData,
bool aScrollbarIsDescendant,
AsyncTransformComponentMatrix* aOutClipTransform)
{
// We only apply the transform if the scroll-target layer has non-container
// children (i.e. when it has some possibly-visible content). This is to
// avoid moving scroll-bars in the situation that only a scroll information
// layer has been built for a scroll frame, as this would result in a
// disparity between scrollbars and visible content.
if (aMetrics.IsScrollInfoLayer()) {
return LayerToParentLayerMatrix4x4{};
}
MOZ_RELEASE_ASSERT(aApzc);
AsyncTransformComponentMatrix asyncTransform =
aApzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing);
// |asyncTransform| represents the amount by which we have scrolled and
// zoomed since the last paint. Because the scrollbar was sized and positioned based
// on the painted content, we need to adjust it based on asyncTransform so that
// it reflects what the user is actually seeing now.
AsyncTransformComponentMatrix scrollbarTransform;
if (*aThumbData.mDirection == ScrollDirection::eVertical) {
const ParentLayerCoord asyncScrollY = asyncTransform._42;
const float asyncZoomY = asyncTransform._22;
// The scroll thumb needs to be scaled in the direction of scrolling by the
// inverse of the async zoom. This is because zooming in decreases the
// fraction of the whole srollable rect that is in view.
const float yScale = 1.f / asyncZoomY;
// Note: |metrics.GetZoom()| doesn't yet include the async zoom.
const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().yScale * asyncZoomY);
// Here we convert the scrollbar thumb ratio into a true unitless ratio by
// dividing out the conversion factor from the scrollframe's parent's space
// to the scrollframe's space.
const float ratio = aThumbData.mThumbRatio /
(aMetrics.GetPresShellResolution() * asyncZoomY);
// The scroll thumb needs to be translated in opposite direction of the
// async scroll. This is because scrolling down, which translates the layer
// content up, should result in moving the scroll thumb down.
ParentLayerCoord yTranslation = -asyncScrollY * ratio;
// The scroll thumb additionally needs to be translated to compensate for
// the scale applied above. The origin with respect to which the scale is
// applied is the origin of the entire scrollbar, rather than the origin of
// the scroll thumb (meaning, for a vertical scrollbar it's at the top of
// the composition bounds). This means that empty space above the thumb
// is scaled too, effectively translating the thumb. We undo that
// translation here.
// (One can think of the adjustment being done to the translation here as
// a change of basis. We have a method to help with that,
// Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code
// cleaner in this case).
const CSSCoord thumbOrigin = (aMetrics.GetScrollOffset().y * ratio);
const CSSCoord thumbOriginScaled = thumbOrigin * yScale;
const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom;
yTranslation -= thumbOriginDeltaPL;
if (aMetrics.IsRootContent()) {
// Scrollbar for the root are painted at the same resolution as the
// content. Since the coordinate space we apply this transform in includes
// the resolution, we need to adjust for it as well here. Note that in
// another metrics.IsRootContent() hunk below we apply a
// resolution-cancelling transform which ensures the scroll thumb isn't
// actually rendered at a larger scale.
yTranslation *= aMetrics.GetPresShellResolution();
}
scrollbarTransform.PostScale(1.f, yScale, 1.f);
scrollbarTransform.PostTranslate(0, yTranslation, 0);
}
if (*aThumbData.mDirection == ScrollDirection::eHorizontal) {
// See detailed comments under the VERTICAL case.
const ParentLayerCoord asyncScrollX = asyncTransform._41;
const float asyncZoomX = asyncTransform._11;
const float xScale = 1.f / asyncZoomX;
const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().xScale * asyncZoomX);
const float ratio = aThumbData.mThumbRatio /
(aMetrics.GetPresShellResolution() * asyncZoomX);
ParentLayerCoord xTranslation = -asyncScrollX * ratio;
const CSSCoord thumbOrigin = (aMetrics.GetScrollOffset().x * ratio);
const CSSCoord thumbOriginScaled = thumbOrigin * xScale;
const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom;
xTranslation -= thumbOriginDeltaPL;
if (aMetrics.IsRootContent()) {
xTranslation *= aMetrics.GetPresShellResolution();
}
scrollbarTransform.PostScale(xScale, 1.f, 1.f);
scrollbarTransform.PostTranslate(xTranslation, 0, 0);
}
LayerToParentLayerMatrix4x4 transform =
aCurrentTransform * scrollbarTransform;
AsyncTransformComponentMatrix compensation;
// If the scrollbar layer is for the root then the content's resolution
// applies to the scrollbar as well. Since we don't actually want the scroll
// thumb's size to vary with the zoom (other than its length reflecting the
// fraction of the scrollable length that's in view, which is taken care of
// above), we apply a transform to cancel out this resolution.
if (aMetrics.IsRootContent()) {
compensation =
AsyncTransformComponentMatrix::Scaling(
aMetrics.GetPresShellResolution(),
aMetrics.GetPresShellResolution(),
1.0f).Inverse();
}
// If the scrollbar layer is a child of the content it is a scrollbar for,
// then we need to adjust for any async transform (including an overscroll
// transform) on the content. This needs to be cancelled out because layout
// positions and sizes the scrollbar on the assumption that there is no async
// transform, and without this adjustment the scrollbar will end up in the
// wrong place.
//
// Note that since the async transform is applied on top of the content's
// regular transform, we need to make sure to unapply the async transform in
// the same coordinate space. This requires applying the content transform
// and then unapplying it after unapplying the async transform.
if (aScrollbarIsDescendant) {
AsyncTransformComponentMatrix overscroll =
aApzc->GetOverscrollTransform(AsyncPanZoomController::eForCompositing);
Matrix4x4 asyncUntransform = (asyncTransform * overscroll).Inverse().ToUnknownMatrix();
Matrix4x4 contentTransform = aScrollableContentTransform;
Matrix4x4 contentUntransform = contentTransform.Inverse();
AsyncTransformComponentMatrix asyncCompensation =
ViewAs<AsyncTransformComponentMatrix>(
contentTransform
* asyncUntransform
* contentUntransform);
compensation = compensation * asyncCompensation;
// Pass the async compensation out to the caller so that it can use it
// to transform clip transforms as needed.
if (aOutClipTransform) {
*aOutClipTransform = asyncCompensation;
}
}
transform = transform * compensation;
return transform;
}
#if defined(MOZ_WIDGET_ANDROID)
AndroidDynamicToolbarAnimator*
APZCTreeManager::GetAndroidDynamicToolbarAnimator()

View File

@ -55,6 +55,7 @@ class GeckoContentController;
class HitTestingTreeNode;
class WebRenderScrollData;
struct AncestorTransform;
struct ScrollThumbData;
/**
* ****************** NOTE ON LOCK ORDERING IN APZ **************************
@ -484,6 +485,38 @@ public:
bool GetAPZTestData(uint64_t aLayersId, APZTestData* aOutData);
/**
* Compute the updated shadow transform for a scroll thumb layer that
* reflects async scrolling of the associated scroll frame.
*
* @param aCurrentTransform The current shadow transform on the scroll thumb
* layer, as returned by Layer::GetLocalTransform() or similar.
* @param aScrollableContentTransform The current content transform on the
* scrollable content, as returned by Layer::GetTransform().
* @param aApzc The APZC that scrolls the scroll frame.
* @param aMetrics The metrics associated with the scroll frame, reflecting
* the last paint of the associated content. Note: this metrics should
* NOT reflect async scrolling, i.e. they should be the layer tree's
* copy of the metrics, or APZC's last-content-paint metrics.
* @param aThumbData The scroll thumb data for the the scroll thumb layer.
* @param aScrollbarIsDescendant True iff. the scroll thumb layer is a
* descendant of the layer bearing the scroll frame's metrics.
* @param aOutClipTransform If not null, and |aScrollbarIsDescendant| is true,
* this will be populated with a transform that should be applied to the
* clip rects of all layers between the scroll thumb layer and the ancestor
* layer for the scrollable content.
* @return The new shadow transform for the scroll thumb layer, including
* any pre- or post-scales.
*/
static LayerToParentLayerMatrix4x4 ComputeTransformForScrollThumb(
const LayerToParentLayerMatrix4x4& aCurrentTransform,
const gfx::Matrix4x4& aScrollableContentTransform,
AsyncPanZoomController* aApzc,
const FrameMetrics& aMetrics,
const ScrollThumbData& aThumbData,
bool aScrollbarIsDescendant,
AsyncTransformComponentMatrix* aOutClipTransform);
protected:
// Protected destructor, to discourage deletion outside of Release():
virtual ~APZCTreeManager();

View File

@ -7,7 +7,10 @@
#include "mozilla/layers/APZSampler.h"
#include "APZCTreeManager.h"
#include "AsyncPanZoomController.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/LayerMetricsWrapper.h"
#include "TreeTraversal.h"
namespace mozilla {
namespace layers {
@ -122,5 +125,90 @@ APZSampler::SetTestAsyncZoom(uint64_t aLayersId,
}
}
bool
APZSampler::SampleAnimations(const LayerMetricsWrapper& aLayer,
const TimeStamp& aSampleTime)
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
// TODO: eventually we can drop the aLayer argument and just walk the APZ
// tree directly in mApz.
bool activeAnimations = false;
ForEachNodePostOrder<ForwardIterator>(aLayer,
[&activeAnimations, &aSampleTime](LayerMetricsWrapper aLayerMetrics)
{
if (AsyncPanZoomController* apzc = aLayerMetrics.GetApzc()) {
apzc->ReportCheckerboard(aSampleTime);
activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
}
}
);
return activeAnimations;
}
LayerToParentLayerMatrix4x4
APZSampler::ComputeTransformForScrollThumb(const LayerToParentLayerMatrix4x4& aCurrentTransform,
const LayerMetricsWrapper& aContent,
const ScrollThumbData& aThumbData,
bool aScrollbarIsDescendant,
AsyncTransformComponentMatrix* aOutClipTransform)
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
return mApz->ComputeTransformForScrollThumb(aCurrentTransform,
aContent.GetTransform(),
aContent.GetApzc(),
aContent.Metrics(),
aThumbData,
aScrollbarIsDescendant,
aOutClipTransform);
}
ParentLayerPoint
APZSampler::GetCurrentAsyncScrollOffset(const LayerMetricsWrapper& aLayer)
{
MOZ_ASSERT(aLayer.GetApzc());
return aLayer.GetApzc()->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForCompositing);
}
AsyncTransform
APZSampler::GetCurrentAsyncTransform(const LayerMetricsWrapper& aLayer)
{
MOZ_ASSERT(aLayer.GetApzc());
return aLayer.GetApzc()->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing);
}
AsyncTransformComponentMatrix
APZSampler::GetOverscrollTransform(const LayerMetricsWrapper& aLayer)
{
MOZ_ASSERT(aLayer.GetApzc());
return aLayer.GetApzc()->GetOverscrollTransform(AsyncPanZoomController::eForCompositing);
}
AsyncTransformComponentMatrix
APZSampler::GetCurrentAsyncTransformWithOverscroll(const LayerMetricsWrapper& aLayer)
{
MOZ_ASSERT(aLayer.GetApzc());
return aLayer.GetApzc()->GetCurrentAsyncTransformWithOverscroll(AsyncPanZoomController::eForCompositing);
}
void
APZSampler::MarkAsyncTransformAppliedToContent(const LayerMetricsWrapper& aLayer)
{
MOZ_ASSERT(aLayer.GetApzc());
aLayer.GetApzc()->MarkAsyncTransformAppliedToContent();
}
bool
APZSampler::HasUnusedAsyncTransform(const LayerMetricsWrapper& aLayer)
{
AsyncPanZoomController* apzc = aLayer.GetApzc();
return apzc
&& !apzc->GetAsyncTransformAppliedToContent()
&& !AsyncTransformComponentMatrix(apzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForHitTesting)).IsIdentity();
}
} // namespace layers
} // namespace mozilla

View File

@ -6,7 +6,6 @@
#include "mozilla/layers/AsyncCompositionManager.h"
#include <stdint.h> // for uint32_t
#include "apz/src/AsyncPanZoomController.h"
#include "FrameMetrics.h" // for FrameMetrics
#include "LayerManagerComposite.h" // for LayerManagerComposite, etc
#include "Layers.h" // for Layer, ContainerLayer, etc
@ -19,6 +18,7 @@
#include "mozilla/gfx/Rect.h" // for RoundedToInt, RectTyped
#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor
#include "mozilla/layers/AnimationHelper.h"
#include "mozilla/layers/APZSampler.h" // for APZSampler
#include "mozilla/layers/APZUtils.h" // for CompleteAsyncTransform
#include "mozilla/layers/Compositor.h" // for Compositor
#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
@ -68,14 +68,15 @@ ContentMightReflowOnOrientationChange(const IntRect& rect)
return rect.Width() != rect.Height();
}
AsyncCompositionManager::AsyncCompositionManager(CompositorBridgeParent* aParent,
HostLayerManager* aManager)
AsyncCompositionManager::AsyncCompositionManager(CompositorBridgeParent* aParent,
HostLayerManager* aManager)
: mLayerManager(aManager)
, mIsFirstPaint(true)
, mLayersUpdated(false)
, mReadyForCompose(true)
, mCompositorBridge(aParent)
{
MOZ_ASSERT(mCompositorBridge);
}
AsyncCompositionManager::~AsyncCompositionManager()
@ -695,24 +696,6 @@ SampleAnimations(Layer* aLayer,
return animProcess;
}
static bool
SampleAPZAnimations(const LayerMetricsWrapper& aLayer, TimeStamp aSampleTime)
{
bool activeAnimations = false;
ForEachNodePostOrder<ForwardIterator>(aLayer,
[&activeAnimations, &aSampleTime](LayerMetricsWrapper aLayerMetrics)
{
if (AsyncPanZoomController* apzc = aLayerMetrics.GetApzc()) {
apzc->ReportCheckerboard(aSampleTime);
activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
}
}
);
return activeAnimations;
}
void
AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer)
{
@ -724,8 +707,7 @@ AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer)
[this] (Layer* layer)
{
for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) {
AsyncPanZoomController* apzc = layer->GetAsyncPanZoomController(i);
if (!apzc) {
if (!layer->GetFrameMetrics(i).IsScrollable()) {
continue;
}
gfx::Matrix4x4 shadowTransform = layer->AsHostLayer()->GetShadowBaseTransform();
@ -901,144 +883,146 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer,
}
}
for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) {
AsyncPanZoomController* controller = layer->GetAsyncPanZoomController(i);
if (!controller) {
continue;
}
if (RefPtr<APZSampler> sampler = mCompositorBridge->GetAPZSampler()) {
for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) {
LayerMetricsWrapper wrapper(layer, i);
const FrameMetrics& metrics = wrapper.Metrics();
if (!metrics.IsScrollable()) {
continue;
}
hasAsyncTransform = true;
hasAsyncTransform = true;
AsyncTransform asyncTransformWithoutOverscroll =
controller->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing);
AsyncTransformComponentMatrix overscrollTransform =
controller->GetOverscrollTransform(AsyncPanZoomController::eForCompositing);
AsyncTransformComponentMatrix asyncTransform =
AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll)
* overscrollTransform;
AsyncTransform asyncTransformWithoutOverscroll =
sampler->GetCurrentAsyncTransform(wrapper);
AsyncTransformComponentMatrix overscrollTransform =
sampler->GetOverscrollTransform(wrapper);
AsyncTransformComponentMatrix asyncTransform =
AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll)
* overscrollTransform;
if (!layer->IsScrollableWithoutContent()) {
controller->MarkAsyncTransformAppliedToContent();
}
if (!layer->IsScrollableWithoutContent()) {
sampler->MarkAsyncTransformAppliedToContent(wrapper);
}
const ScrollMetadata& scrollMetadata = layer->GetScrollMetadata(i);
const FrameMetrics& metrics = scrollMetadata.GetMetrics();
const ScrollMetadata& scrollMetadata = wrapper.Metadata();
#if defined(MOZ_WIDGET_ANDROID)
// If we find a metrics which is the root content doc, use that. If not, use
// the root layer. Since this function recurses on children first we should
// only end up using the root layer if the entire tree was devoid of a
// root content metrics. This is a temporary solution; in the long term we
// should not need the root content metrics at all. See bug 1201529 comment
// 6 for details.
if (!(*aOutFoundRoot)) {
*aOutFoundRoot = metrics.IsRootContent() || /* RCD */
(layer->GetParent() == nullptr && /* rootmost metrics */
i + 1 >= layer->GetScrollMetadataCount());
if (*aOutFoundRoot) {
mRootScrollableId = metrics.GetScrollId();
Compositor* compositor = mLayerManager->GetCompositor();
if (CompositorBridgeParent* bridge = compositor->GetCompositorBridgeParent()) {
AndroidDynamicToolbarAnimator* animator = bridge->GetAndroidDynamicToolbarAnimator();
MOZ_ASSERT(animator);
if (mIsFirstPaint) {
animator->UpdateRootFrameMetrics(metrics);
animator->FirstPaint();
mIsFirstPaint = false;
}
if (mLayersUpdated) {
animator->NotifyLayersUpdated();
mLayersUpdated = false;
}
// If this is not actually the root content then the animator is not getting updated in AsyncPanZoomController::NotifyLayersUpdated
// because the root content document is not scrollable. So update it here so it knows if the root composition size has changed.
if (!metrics.IsRootContent()) {
animator->MaybeUpdateCompositionSizeAndRootFrameMetrics(metrics);
// If we find a metrics which is the root content doc, use that. If not, use
// the root layer. Since this function recurses on children first we should
// only end up using the root layer if the entire tree was devoid of a
// root content metrics. This is a temporary solution; in the long term we
// should not need the root content metrics at all. See bug 1201529 comment
// 6 for details.
if (!(*aOutFoundRoot)) {
*aOutFoundRoot = metrics.IsRootContent() || /* RCD */
(layer->GetParent() == nullptr && /* rootmost metrics */
i + 1 >= layer->GetScrollMetadataCount());
if (*aOutFoundRoot) {
mRootScrollableId = metrics.GetScrollId();
Compositor* compositor = mLayerManager->GetCompositor();
if (CompositorBridgeParent* bridge = compositor->GetCompositorBridgeParent()) {
AndroidDynamicToolbarAnimator* animator = bridge->GetAndroidDynamicToolbarAnimator();
MOZ_ASSERT(animator);
if (mIsFirstPaint) {
animator->UpdateRootFrameMetrics(metrics);
animator->FirstPaint();
mIsFirstPaint = false;
}
if (mLayersUpdated) {
animator->NotifyLayersUpdated();
mLayersUpdated = false;
}
// If this is not actually the root content then the animator is not getting updated in AsyncPanZoomController::NotifyLayersUpdated
// because the root content document is not scrollable. So update it here so it knows if the root composition size has changed.
if (!metrics.IsRootContent()) {
animator->MaybeUpdateCompositionSizeAndRootFrameMetrics(metrics);
}
}
fixedLayerMargins = mFixedLayerMargins;
}
fixedLayerMargins = mFixedLayerMargins;
}
}
#else
*aOutFoundRoot = false;
// Non-Android platforms still care about this flag being cleared after
// the first call to TransformShadowTree().
mIsFirstPaint = false;
*aOutFoundRoot = false;
// Non-Android platforms still care about this flag being cleared after
// the first call to TransformShadowTree().
mIsFirstPaint = false;
#endif
// Transform the current local clips by this APZC's async transform. If we're
// using containerful scrolling, then the clip is not part of the scrolled
// frame and should not be transformed.
if (!scrollMetadata.UsesContainerScrolling()) {
MOZ_ASSERT(asyncTransform.Is2D());
if (clipParts.mFixedClip) {
*clipParts.mFixedClip = TransformBy(asyncTransform, *clipParts.mFixedClip);
// Transform the current local clips by this APZC's async transform. If we're
// using containerful scrolling, then the clip is not part of the scrolled
// frame and should not be transformed.
if (!scrollMetadata.UsesContainerScrolling()) {
MOZ_ASSERT(asyncTransform.Is2D());
if (clipParts.mFixedClip) {
*clipParts.mFixedClip = TransformBy(asyncTransform, *clipParts.mFixedClip);
}
if (clipParts.mScrolledClip) {
*clipParts.mScrolledClip = TransformBy(asyncTransform, *clipParts.mScrolledClip);
}
}
if (clipParts.mScrolledClip) {
*clipParts.mScrolledClip = TransformBy(asyncTransform, *clipParts.mScrolledClip);
// Note: we don't set the layer's shadow clip rect property yet;
// AlignFixedAndStickyLayers will use the clip parts from the clip parts
// cache.
combinedAsyncTransform *= asyncTransform;
// For the purpose of aligning fixed and sticky layers, we disregard
// the overscroll transform as well as any OMTA transform when computing the
// 'aCurrentTransformForRoot' parameter. This ensures that the overscroll
// and OMTA transforms are not unapplied, and therefore that the visual
// effects apply to fixed and sticky layers. We do this by using
// GetTransform() as the base transform rather than GetLocalTransform(),
// which would include those factors.
LayerToParentLayerMatrix4x4 transformWithoutOverscrollOrOmta =
layer->GetTransformTyped()
* CompleteAsyncTransform(
AdjustForClip(asyncTransformWithoutOverscroll, layer));
AlignFixedAndStickyLayers(layer, layer, metrics.GetScrollId(), oldTransform,
transformWithoutOverscrollOrOmta, fixedLayerMargins,
&clipPartsCache);
// Combine the local clip with the ancestor scrollframe clip. This is not
// included in the async transform above, since the ancestor clip should not
// move with this APZC.
if (scrollMetadata.HasScrollClip()) {
ParentLayerIntRect clip = scrollMetadata.ScrollClip().GetClipRect();
if (layer->GetParent() && layer->GetParent()->GetTransformIsPerspective()) {
// If our parent layer has a perspective transform, we want to apply
// our scroll clip to it instead of to this layer (see bug 1168263).
// A layer with a perspective transform shouldn't have multiple
// children with FrameMetrics, nor a child with multiple FrameMetrics.
// (A child with multiple FrameMetrics would mean that there's *another*
// scrollable element between the one with the CSS perspective and the
// transformed element. But you'd have to use preserve-3d on the inner
// scrollable element in order to have the perspective apply to the
// transformed child, and preserve-3d is not supported on scrollable
// elements, so this case can't occur.)
MOZ_ASSERT(!stackDeferredClips.top());
stackDeferredClips.top().emplace(clip);
} else {
clipParts.mScrolledClip = IntersectMaybeRects(Some(clip),
clipParts.mScrolledClip);
}
}
}
// Note: we don't set the layer's shadow clip rect property yet;
// AlignFixedAndStickyLayers will use the clip parts from the clip parts
// cache.
combinedAsyncTransform *= asyncTransform;
// For the purpose of aligning fixed and sticky layers, we disregard
// the overscroll transform as well as any OMTA transform when computing the
// 'aCurrentTransformForRoot' parameter. This ensures that the overscroll
// and OMTA transforms are not unapplied, and therefore that the visual
// effects apply to fixed and sticky layers. We do this by using
// GetTransform() as the base transform rather than GetLocalTransform(),
// which would include those factors.
LayerToParentLayerMatrix4x4 transformWithoutOverscrollOrOmta =
layer->GetTransformTyped()
* CompleteAsyncTransform(
AdjustForClip(asyncTransformWithoutOverscroll, layer));
AlignFixedAndStickyLayers(layer, layer, metrics.GetScrollId(), oldTransform,
transformWithoutOverscrollOrOmta, fixedLayerMargins,
&clipPartsCache);
// Combine the local clip with the ancestor scrollframe clip. This is not
// included in the async transform above, since the ancestor clip should not
// move with this APZC.
if (scrollMetadata.HasScrollClip()) {
ParentLayerIntRect clip = scrollMetadata.ScrollClip().GetClipRect();
if (layer->GetParent() && layer->GetParent()->GetTransformIsPerspective()) {
// If our parent layer has a perspective transform, we want to apply
// our scroll clip to it instead of to this layer (see bug 1168263).
// A layer with a perspective transform shouldn't have multiple
// children with FrameMetrics, nor a child with multiple FrameMetrics.
// (A child with multiple FrameMetrics would mean that there's *another*
// scrollable element between the one with the CSS perspective and the
// transformed element. But you'd have to use preserve-3d on the inner
// scrollable element in order to have the perspective apply to the
// transformed child, and preserve-3d is not supported on scrollable
// elements, so this case can't occur.)
MOZ_ASSERT(!stackDeferredClips.top());
stackDeferredClips.top().emplace(clip);
} else {
clipParts.mScrolledClip = IntersectMaybeRects(Some(clip),
clipParts.mScrolledClip);
// Do the same for the ancestor mask layers: ancestorMaskLayers contains
// the ancestor mask layers for scroll frames *inside* the current scroll
// frame, so these are the ones we need to shift by our async transform.
for (Layer* ancestorMaskLayer : ancestorMaskLayers) {
SetShadowTransform(ancestorMaskLayer,
ancestorMaskLayer->GetLocalTransformTyped() * asyncTransform);
}
}
// Do the same for the ancestor mask layers: ancestorMaskLayers contains
// the ancestor mask layers for scroll frames *inside* the current scroll
// frame, so these are the ones we need to shift by our async transform.
for (Layer* ancestorMaskLayer : ancestorMaskLayers) {
SetShadowTransform(ancestorMaskLayer,
ancestorMaskLayer->GetLocalTransformTyped() * asyncTransform);
}
// Append the ancestor mask layer for this scroll frame to ancestorMaskLayers.
if (scrollMetadata.HasScrollClip()) {
const LayerClip& scrollClip = scrollMetadata.ScrollClip();
if (scrollClip.GetMaskLayerIndex()) {
size_t maskLayerIndex = scrollClip.GetMaskLayerIndex().value();
Layer* ancestorMaskLayer = layer->GetAncestorMaskLayerAt(maskLayerIndex);
ancestorMaskLayers.AppendElement(ancestorMaskLayer);
// Append the ancestor mask layer for this scroll frame to ancestorMaskLayers.
if (scrollMetadata.HasScrollClip()) {
const LayerClip& scrollClip = scrollMetadata.ScrollClip();
if (scrollClip.GetMaskLayerIndex()) {
size_t maskLayerIndex = scrollClip.GetMaskLayerIndex().value();
Layer* ancestorMaskLayer = layer->GetAncestorMaskLayerAt(maskLayerIndex);
ancestorMaskLayers.AppendElement(ancestorMaskLayer);
}
}
}
}
@ -1085,11 +1069,10 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer,
static bool
LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, Layer* aScrollbar)
{
AsyncPanZoomController* apzc = aTarget.GetApzc();
if (!apzc) {
const FrameMetrics& metrics = aTarget.Metrics();
if (!metrics.IsScrollable()) {
return false;
}
const FrameMetrics& metrics = aTarget.Metrics();
if (metrics.GetScrollId() != aScrollbar->GetScrollbarTargetContainerId()) {
return false;
}
@ -1097,18 +1080,18 @@ LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, Layer* aScrollbar)
}
static void
ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
ApplyAsyncTransformToScrollbarForContent(const RefPtr<APZSampler>& aSampler,
Layer* aScrollbar,
const LayerMetricsWrapper& aContent,
bool aScrollbarIsDescendant)
{
AsyncTransformComponentMatrix clipTransform;
MOZ_ASSERT(aSampler);
LayerToParentLayerMatrix4x4 transform =
AsyncCompositionManager::ComputeTransformForScrollThumb(
aSampler->ComputeTransformForScrollThumb(
aScrollbar->GetLocalTransformTyped(),
aContent.GetTransform(),
aContent.GetApzc(),
aContent.Metrics(),
aContent,
aScrollbar->GetScrollThumbData(),
aScrollbarIsDescendant,
&clipTransform);
@ -1126,168 +1109,6 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
SetShadowTransform(aScrollbar, transform);
}
/* static */ LayerToParentLayerMatrix4x4
AsyncCompositionManager::ComputeTransformForScrollThumb(
const LayerToParentLayerMatrix4x4& aCurrentTransform,
const Matrix4x4& aScrollableContentTransform,
AsyncPanZoomController* aApzc,
const FrameMetrics& aMetrics,
const ScrollThumbData& aThumbData,
bool aScrollbarIsDescendant,
AsyncTransformComponentMatrix* aOutClipTransform)
{
// We only apply the transform if the scroll-target layer has non-container
// children (i.e. when it has some possibly-visible content). This is to
// avoid moving scroll-bars in the situation that only a scroll information
// layer has been built for a scroll frame, as this would result in a
// disparity between scrollbars and visible content.
if (aMetrics.IsScrollInfoLayer()) {
return LayerToParentLayerMatrix4x4{};
}
MOZ_RELEASE_ASSERT(aApzc);
AsyncTransformComponentMatrix asyncTransform =
aApzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing);
// |asyncTransform| represents the amount by which we have scrolled and
// zoomed since the last paint. Because the scrollbar was sized and positioned based
// on the painted content, we need to adjust it based on asyncTransform so that
// it reflects what the user is actually seeing now.
AsyncTransformComponentMatrix scrollbarTransform;
if (*aThumbData.mDirection == ScrollDirection::eVertical) {
const ParentLayerCoord asyncScrollY = asyncTransform._42;
const float asyncZoomY = asyncTransform._22;
// The scroll thumb needs to be scaled in the direction of scrolling by the
// inverse of the async zoom. This is because zooming in decreases the
// fraction of the whole srollable rect that is in view.
const float yScale = 1.f / asyncZoomY;
// Note: |metrics.GetZoom()| doesn't yet include the async zoom.
const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().yScale * asyncZoomY);
// Here we convert the scrollbar thumb ratio into a true unitless ratio by
// dividing out the conversion factor from the scrollframe's parent's space
// to the scrollframe's space.
const float ratio = aThumbData.mThumbRatio /
(aMetrics.GetPresShellResolution() * asyncZoomY);
// The scroll thumb needs to be translated in opposite direction of the
// async scroll. This is because scrolling down, which translates the layer
// content up, should result in moving the scroll thumb down.
ParentLayerCoord yTranslation = -asyncScrollY * ratio;
// The scroll thumb additionally needs to be translated to compensate for
// the scale applied above. The origin with respect to which the scale is
// applied is the origin of the entire scrollbar, rather than the origin of
// the scroll thumb (meaning, for a vertical scrollbar it's at the top of
// the composition bounds). This means that empty space above the thumb
// is scaled too, effectively translating the thumb. We undo that
// translation here.
// (One can think of the adjustment being done to the translation here as
// a change of basis. We have a method to help with that,
// Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code
// cleaner in this case).
const CSSCoord thumbOrigin = (aMetrics.GetScrollOffset().y * ratio);
const CSSCoord thumbOriginScaled = thumbOrigin * yScale;
const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom;
yTranslation -= thumbOriginDeltaPL;
if (aMetrics.IsRootContent()) {
// Scrollbar for the root are painted at the same resolution as the
// content. Since the coordinate space we apply this transform in includes
// the resolution, we need to adjust for it as well here. Note that in
// another metrics.IsRootContent() hunk below we apply a
// resolution-cancelling transform which ensures the scroll thumb isn't
// actually rendered at a larger scale.
yTranslation *= aMetrics.GetPresShellResolution();
}
scrollbarTransform.PostScale(1.f, yScale, 1.f);
scrollbarTransform.PostTranslate(0, yTranslation, 0);
}
if (*aThumbData.mDirection == ScrollDirection::eHorizontal) {
// See detailed comments under the VERTICAL case.
const ParentLayerCoord asyncScrollX = asyncTransform._41;
const float asyncZoomX = asyncTransform._11;
const float xScale = 1.f / asyncZoomX;
const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().xScale * asyncZoomX);
const float ratio = aThumbData.mThumbRatio /
(aMetrics.GetPresShellResolution() * asyncZoomX);
ParentLayerCoord xTranslation = -asyncScrollX * ratio;
const CSSCoord thumbOrigin = (aMetrics.GetScrollOffset().x * ratio);
const CSSCoord thumbOriginScaled = thumbOrigin * xScale;
const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom;
xTranslation -= thumbOriginDeltaPL;
if (aMetrics.IsRootContent()) {
xTranslation *= aMetrics.GetPresShellResolution();
}
scrollbarTransform.PostScale(xScale, 1.f, 1.f);
scrollbarTransform.PostTranslate(xTranslation, 0, 0);
}
LayerToParentLayerMatrix4x4 transform =
aCurrentTransform * scrollbarTransform;
AsyncTransformComponentMatrix compensation;
// If the scrollbar layer is for the root then the content's resolution
// applies to the scrollbar as well. Since we don't actually want the scroll
// thumb's size to vary with the zoom (other than its length reflecting the
// fraction of the scrollable length that's in view, which is taken care of
// above), we apply a transform to cancel out this resolution.
if (aMetrics.IsRootContent()) {
compensation =
AsyncTransformComponentMatrix::Scaling(
aMetrics.GetPresShellResolution(),
aMetrics.GetPresShellResolution(),
1.0f).Inverse();
}
// If the scrollbar layer is a child of the content it is a scrollbar for,
// then we need to adjust for any async transform (including an overscroll
// transform) on the content. This needs to be cancelled out because layout
// positions and sizes the scrollbar on the assumption that there is no async
// transform, and without this adjustment the scrollbar will end up in the
// wrong place.
//
// Note that since the async transform is applied on top of the content's
// regular transform, we need to make sure to unapply the async transform in
// the same coordinate space. This requires applying the content transform
// and then unapplying it after unapplying the async transform.
if (aScrollbarIsDescendant) {
AsyncTransformComponentMatrix overscroll =
aApzc->GetOverscrollTransform(AsyncPanZoomController::eForCompositing);
Matrix4x4 asyncUntransform = (asyncTransform * overscroll).Inverse().ToUnknownMatrix();
Matrix4x4 contentTransform = aScrollableContentTransform;
Matrix4x4 contentUntransform = contentTransform.Inverse();
AsyncTransformComponentMatrix asyncCompensation =
ViewAs<AsyncTransformComponentMatrix>(
contentTransform
* asyncUntransform
* contentUntransform);
compensation = compensation * asyncCompensation;
// Pass the async compensation out to the caller so that it can use it
// to transform clip transforms as needed.
if (aOutClipTransform) {
*aOutClipTransform = asyncCompensation;
}
}
transform = transform * compensation;
return transform;
}
static LayerMetricsWrapper
FindScrolledLayerForScrollbar(Layer* aScrollbar, bool* aOutIsAncestor)
{
@ -1344,7 +1165,8 @@ AsyncCompositionManager::ApplyAsyncTransformToScrollbar(Layer* aLayer)
bool isAncestor = false;
const LayerMetricsWrapper& scrollTarget = FindScrolledLayerForScrollbar(aLayer, &isAncestor);
if (scrollTarget) {
ApplyAsyncTransformToScrollbarForContent(aLayer, scrollTarget, isAncestor);
ApplyAsyncTransformToScrollbarForContent(mCompositorBridge->GetAPZSampler(),
aLayer, scrollTarget, isAncestor);
}
}
@ -1439,7 +1261,10 @@ AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame,
#endif
}
bool apzAnimating = SampleAPZAnimations(LayerMetricsWrapper(root), nextFrame);
bool apzAnimating = false;
if (RefPtr<APZSampler> apz = mCompositorBridge->GetAPZSampler()) {
apzAnimating = apz->SampleAnimations(LayerMetricsWrapper(root), nextFrame);
}
mAnimationMetricsTracker.UpdateApzAnimationInProgress(apzAnimating, aVsyncRate);
wantNextFrame |= apzAnimating;
}

View File

@ -24,7 +24,6 @@
namespace mozilla {
namespace layers {
class AsyncPanZoomController;
class Layer;
class LayerManagerComposite;
class AutoResolveRefLayers;
@ -133,37 +132,6 @@ public:
typedef std::map<Layer*, ClipParts> ClipPartsCache;
/**
* Compute the updated shadow transform for a scroll thumb layer that
* reflects async scrolling of the associated scroll frame.
*
* @param aCurrentTransform The current shadow transform on the scroll thumb
* layer, as returned by Layer::GetLocalTransform() or similar.
* @param aScrollableContentTransform The current content transform on the
* scrollable content, as returned by Layer::GetTransform().
* @param aApzc The APZC that scrolls the scroll frame.
* @param aMetrics The metrics associated with the scroll frame, reflecting
* the last paint of the associated content. Note: this metrics should
* NOT reflect async scrolling, i.e. they should be the layer tree's
* copy of the metrics, or APZC's last-content-paint metrics.
* @param aThumbData The scroll thumb data for the the scroll thumb layer.
* @param aScrollbarIsDescendant True iff. the scroll thumb layer is a
* descendant of the layer bearing the scroll frame's metrics.
* @param aOutClipTransform If not null, and |aScrollbarIsDescendant| is true,
* this will be populated with a transform that should be applied to the
* clip rects of all layers between the scroll thumb layer and the ancestor
* layer for the scrollable content.
* @return The new shadow transform for the scroll thumb layer, including
* any pre- or post-scales.
*/
static LayerToParentLayerMatrix4x4 ComputeTransformForScrollThumb(
const LayerToParentLayerMatrix4x4& aCurrentTransform,
const gfx::Matrix4x4& aScrollableContentTransform,
AsyncPanZoomController* aApzc,
const FrameMetrics& aMetrics,
const ScrollThumbData& aThumbData,
bool aScrollbarIsDescendant,
AsyncTransformComponentMatrix* aOutClipTransform);
private:
// Return true if an AsyncPanZoomController content transform was
// applied for |aLayer|. |*aOutFoundRoot| is set to true on Android only, if
@ -256,7 +224,7 @@ private:
TimeStamp mPreviousFrameTimeStamp;
AnimationMetricsTracker mAnimationMetricsTracker;
CompositorBridgeParent* mCompositorBridge;
MOZ_NON_OWNING_REF CompositorBridgeParent* mCompositorBridge;
#ifdef MOZ_WIDGET_ANDROID
public:

View File

@ -6,7 +6,6 @@
#include "ContainerLayerComposite.h"
#include <algorithm> // for min
#include "apz/src/AsyncPanZoomController.h" // for AsyncPanZoomController
#include "FrameMetrics.h" // for FrameMetrics
#include "Units.h" // for LayerRect, LayerPixel, etc
#include "CompositableHost.h" // for CompositableHost
@ -19,6 +18,7 @@
#include "mozilla/gfx/Matrix.h" // for Matrix4x4
#include "mozilla/gfx/Point.h" // for Point, IntPoint
#include "mozilla/gfx/Rect.h" // for IntRect, Rect
#include "mozilla/layers/APZSampler.h" // for APZSampler
#include "mozilla/layers/Compositor.h" // for Compositor, etc
#include "mozilla/layers/CompositorTypes.h" // for DiagnosticFlags::CONTAINER
#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc
@ -286,21 +286,25 @@ ContainerPrepare(ContainerT* aContainer,
}
template<class ContainerT> void
RenderMinimap(ContainerT* aContainer, LayerManagerComposite* aManager,
const RenderTargetIntRect& aClipRect, Layer* aLayer)
RenderMinimap(ContainerT* aContainer,
const RefPtr<APZSampler>& aSampler,
LayerManagerComposite* aManager,
const RenderTargetIntRect& aClipRect, Layer* aLayer)
{
Compositor* compositor = aManager->GetCompositor();
MOZ_ASSERT(aSampler);
if (aLayer->GetScrollMetadataCount() < 1) {
return;
}
AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(0);
if (!controller) {
LayerMetricsWrapper wrapper(aLayer, 0);
const FrameMetrics& fm = wrapper.Metrics();
if (!fm.IsScrollable()) {
return;
}
ParentLayerPoint scrollOffset = controller->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForCompositing);
ParentLayerPoint scrollOffset = aSampler->GetCurrentAsyncScrollOffset(wrapper);
// Options
const int verticalPadding = 10;
@ -314,7 +318,6 @@ RenderMinimap(ContainerT* aContainer, LayerManagerComposite* aManager,
gfx::Color viewPortColor(0, 0, 1.f, 0.3f);
// Rects
const FrameMetrics& fm = aLayer->GetFrameMetrics(0);
ParentLayerRect compositionBounds = fm.GetCompositionBounds();
LayerRect scrollRect = fm.GetScrollableRect() * fm.LayersPixelsPerCSSPixel();
LayerRect viewRect = ParentLayerRect(scrollOffset, compositionBounds.Size()) / LayerToParentLayerScale(1);
@ -382,6 +385,11 @@ RenderLayers(ContainerT* aContainer, LayerManagerComposite* aManager,
{
Compositor* compositor = aManager->GetCompositor();
RefPtr<APZSampler> sampler;
if (CompositorBridgeParent* cbp = compositor->GetCompositorBridgeParent()) {
sampler = cbp->GetAPZSampler();
}
for (size_t i = 0u; i < aContainer->mPrepared->mLayers.Length(); i++) {
PreparedLayer& preparedData = aContainer->mPrepared->mLayers[i];
@ -450,25 +458,26 @@ RenderLayers(ContainerT* aContainer, LayerManagerComposite* aManager,
// frames higher up, so loop from the top down, and accumulate an async
// transform as we go along.
Matrix4x4 asyncTransform;
for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; --i) {
if (layer->GetFrameMetrics(i - 1).IsScrollable()) {
// Since the composition bounds are in the parent layer's coordinates,
// use the parent's effective transform rather than the layer's own.
ParentLayerRect compositionBounds = layer->GetFrameMetrics(i - 1).GetCompositionBounds();
aManager->GetCompositor()->DrawDiagnostics(DiagnosticFlags::CONTAINER,
compositionBounds.ToUnknownRect(),
aClipRect.ToUnknownRect(),
asyncTransform * aContainer->GetEffectiveTransform());
if (AsyncPanZoomController* apzc = layer->GetAsyncPanZoomController(i - 1)) {
if (sampler) {
for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; --i) {
LayerMetricsWrapper wrapper(layer, i - 1);
if (wrapper.Metrics().IsScrollable()) {
// Since the composition bounds are in the parent layer's coordinates,
// use the parent's effective transform rather than the layer's own.
ParentLayerRect compositionBounds = wrapper.Metrics().GetCompositionBounds();
aManager->GetCompositor()->DrawDiagnostics(DiagnosticFlags::CONTAINER,
compositionBounds.ToUnknownRect(),
aClipRect.ToUnknownRect(),
asyncTransform * aContainer->GetEffectiveTransform());
asyncTransform =
apzc->GetCurrentAsyncTransformWithOverscroll(AsyncPanZoomController::eForCompositing).ToUnknownMatrix()
* asyncTransform;
sampler->GetCurrentAsyncTransformWithOverscroll(wrapper).ToUnknownMatrix()
* asyncTransform;
}
}
}
if (gfxPrefs::APZMinimap()) {
RenderMinimap(aContainer, aManager, aClipRect, layer);
if (gfxPrefs::APZMinimap()) {
RenderMinimap(aContainer, sampler, aManager, aClipRect, layer);
}
}
// invariant: our GL context should be current here, I don't think we can
@ -605,16 +614,14 @@ ContainerRender(ContainerT* aContainer,
// to any visible content. Display a warning box (conditioned on the FPS display being
// enabled).
if (gfxPrefs::LayersDrawFPS() && aContainer->IsScrollableWithoutContent()) {
RefPtr<APZSampler> sampler = aManager->GetCompositor()->GetCompositorBridgeParent()->GetAPZSampler();
// Since aContainer doesn't have any children we can just iterate from the top metrics
// on it down to the bottom using GetFirstChild and not worry about walking onto another
// underlying layer.
for (LayerMetricsWrapper i(aContainer); i; i = i.GetFirstChild()) {
if (AsyncPanZoomController* apzc = i.GetApzc()) {
if (!apzc->GetAsyncTransformAppliedToContent()
&& !AsyncTransformComponentMatrix(apzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForHitTesting)).IsIdentity()) {
aManager->UnusedApzTransformWarning();
break;
}
if (sampler->HasUnusedAsyncTransform(i)) {
aManager->UnusedApzTransformWarning();
break;
}
}
}

View File

@ -16,6 +16,7 @@
namespace mozilla {
namespace layers {
class APZSampler;
class CompositableHost;
class CompositingRenderTarget;
struct PreparedData;
@ -54,7 +55,9 @@ class ContainerLayerComposite : public ContainerLayer,
const RenderTargetIntRect& aClipRect);
template<class ContainerT>
void RenderMinimap(ContainerT* aContainer, LayerManagerComposite* aManager,
void RenderMinimap(ContainerT* aContainer,
const RefPtr<APZSampler>& aSampler,
LayerManagerComposite* aManager,
const RenderTargetIntRect& aClipRect, Layer* aLayer);
public:
explicit ContainerLayerComposite(LayerManagerComposite *aManager);

View File

@ -1163,4 +1163,4 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = {
static const int32_t kUnknownId = -1;
static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1529351485588000);
static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1529438538158000);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
#!/bin/bash
set -x -e -v
# 0.2.2 + a few fixes
SCCACHE_REVISION=8871ae7bd8d7f844228fbcfecb6f471b22a01e1d
# 0.2.6
SCCACHE_REVISION=dfdfce28e0cf6c16eb821e8aa3c3f63f78b25497
# This script is for building sccache

View File

@ -22,9 +22,15 @@ from marionette_harness import (
here = os.path.abspath(os.path.dirname(__file__))
BLACK_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==' # noqa
RED_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=' # noqa
def inline(doc):
return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
def inline_image(data):
return 'data:image/png;base64,%s' % data
class BaseNavigationTestCase(WindowManagerMixin, MarionetteTestCase):
@ -487,6 +493,9 @@ class TestBackForwardNavigation(BaseNavigationTestCase):
test_pages = [
{"url": self.marionette.absolute_url("black.png")},
{"url": self.marionette.absolute_url("white.png")},
{"url": inline_image(RED_PIXEL)},
{"url": inline_image(BLACK_PIXEL)},
{"url": self.marionette.absolute_url("black.png")},
]
self.run_bfcache_test(test_pages)

View File

@ -150,6 +150,23 @@ class ScriptMixin(PlatformMixin):
env = None
script_obj = None
def query_filesize(self, file_path):
self.info("Determining filesize for %s" % file_path)
length = os.path.getsize(file_path)
self.info(" %s" % str(length))
return length
# TODO this should be parallelized with the to-be-written BaseHelper!
def query_sha512sum(self, file_path):
self.info("Determining sha512sum for %s" % file_path)
m = hashlib.sha512()
contents = self.read_from_file(file_path, verbose=False,
open_mode='rb')
m.update(contents)
sha512 = m.hexdigest()
self.info(" %s" % sha512)
return sha512
def platform_name(self):
""" Return the platform name on which the script is running on.
Returns:

View File

@ -41,7 +41,6 @@ from mozharness.mozilla.buildbot import (
)
from mozharness.mozilla.purge import PurgeMixin
from mozharness.mozilla.secrets import SecretsMixin
from mozharness.mozilla.signing import SigningMixin
from mozharness.mozilla.testing.errors import TinderBoxPrintRe
from mozharness.mozilla.testing.unittest import tbox_print_summary
from mozharness.mozilla.updates.balrog import BalrogMixin
@ -669,7 +668,7 @@ def generate_build_UID():
class BuildScript(BuildbotMixin, PurgeMixin, BalrogMixin,
SigningMixin, VirtualenvMixin, MercurialScript,
VirtualenvMixin, MercurialScript,
SecretsMixin, PerfherderResourceOptionsMixin):
def __init__(self, **kwargs):
# objdir is referenced in _query_abs_dirs() so let's make sure we
@ -927,22 +926,6 @@ or run without that action (ie: --no-{action})"
if self.config.get('pgo_build') or self._compile_against_pgo():
env['MOZ_PGO'] = '1'
if c.get('enable_signing'):
if os.environ.get('MOZ_SIGNING_SERVERS'):
moz_sign_cmd = subprocess.list2cmdline(
self.query_moz_sign_cmd(formats=None)
)
# windows fix. This is passed to mach build env and we call that
# with python, not with bash so we need to fix the slashes here
env['MOZ_SIGN_CMD'] = moz_sign_cmd.replace('\\', '\\\\\\\\')
else:
self.warning("signing disabled because MOZ_SIGNING_SERVERS is not set")
elif 'MOZ_SIGN_CMD' in env:
# Ensure that signing is truly disabled
# MOZ_SIGN_CMD may be defined by default in buildbot (see MozillaBuildFactory)
self.warning("Clearing MOZ_SIGN_CMD because we don't have config['enable_signing']")
del env['MOZ_SIGN_CMD']
# to activate the right behaviour in mozonfigs while we transition
if c.get('enable_release_promotion'):
env['ENABLE_RELEASE_PROMOTION'] = "1"
@ -951,8 +934,6 @@ or run without that action (ie: --no-{action})"
% (update_channel,))
env["MOZ_UPDATE_CHANNEL"] = update_channel
# we can't make env an attribute of self because env can change on
# every call for reasons like MOZ_SIGN_CMD
return env
def query_mach_build_env(self, multiLocale=None):

View File

@ -1,106 +0,0 @@
#!/usr/bin/env python
# ***** BEGIN LICENSE BLOCK *****
# 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/.
# ***** END LICENSE BLOCK *****
"""Mozilla-specific signing methods.
"""
import os
import re
import json
import sys
from mozharness.base.errors import BaseErrorList
from mozharness.base.log import ERROR, FATAL
from mozharness.base.signing import AndroidSigningMixin, BaseSigningMixin
AndroidSignatureVerificationErrorList = BaseErrorList + [{
"regex": re.compile(r'''^Invalid$'''),
"level": FATAL,
"explanation": "Signature is invalid!"
}, {
"substr": "filename not matched",
"level": ERROR,
}, {
"substr": "ERROR: Could not unzip",
"level": ERROR,
}, {
"regex": re.compile(r'''Are you sure this is a (nightly|release) package'''),
"level": FATAL,
"explanation": "Not signed!"
}]
# SigningMixin {{{1
class SigningMixin(BaseSigningMixin):
"""Generic signing helper methods."""
def query_moz_sign_cmd(self, formats=['gpg']):
if 'MOZ_SIGNING_SERVERS' not in os.environ:
self.fatal("MOZ_SIGNING_SERVERS not in env; no MOZ_SIGN_CMD for you!")
dirs = self.query_abs_dirs()
signing_dir = os.path.join(dirs['abs_work_dir'], 'tools', 'release', 'signing')
cache_dir = os.path.join(dirs['abs_work_dir'], 'signing_cache')
token = os.path.join(dirs['base_work_dir'], 'token')
nonce = os.path.join(dirs['base_work_dir'], 'nonce')
host_cert = os.path.join(signing_dir, 'host.cert')
python = sys.executable
# A mock environment is a special case, the system python isn't
# available there
if 'mock_target' in self.config:
python = 'python2.7'
cmd = [
python,
os.path.join(signing_dir, 'signtool.py'),
'--cachedir', cache_dir,
'-t', token,
'-n', nonce,
'-c', host_cert,
]
if formats:
for f in formats:
cmd += ['-f', f]
for h in os.environ['MOZ_SIGNING_SERVERS'].split(","):
cmd += ['-H', h]
return cmd
def generate_signing_manifest(self, files):
"""Generate signing manifest for signingworkers
Every entry in the manifest requires a dictionary of
"file_to_sign" (basename) and "hash" (SHA512) of every file to be
signed. Signing format is defined in the signing task.
"""
manifest_content = [
{
"file_to_sign": os.path.basename(f),
"hash": self.query_sha512sum(f)
}
for f in files
]
return json.dumps(manifest_content)
# MobileSigningMixin {{{1
class MobileSigningMixin(AndroidSigningMixin, SigningMixin):
def verify_android_signature(self, apk, script=None, key_alias="nightly",
tools_dir="tools/", env=None):
"""Runs mjessome's android signature verification script.
This currently doesn't check to see if the apk exists; you may want
to do that before calling the method.
"""
c = self.config
dirs = self.query_abs_dirs()
if script is None:
script = c.get('signature_verification_script')
if env is None:
env = self.query_env()
return self.run_command(
[script, "--tools-dir=%s" % tools_dir, "--%s" % key_alias,
"--apk=%s" % apk],
cwd=dirs['abs_work_dir'],
env=env,
error_list=AndroidSignatureVerificationErrorList
)

View File

@ -13,7 +13,6 @@ import glob
import re
import sys
import shlex
import subprocess
# load modules from parent dir
sys.path.insert(1, os.path.dirname(sys.path[0]))
@ -31,7 +30,6 @@ from mozharness.mozilla.building.buildbase import (
from mozharness.mozilla.l10n.locales import LocalesMixin
from mozharness.mozilla.mar import MarMixin
from mozharness.mozilla.release import ReleaseMixin
from mozharness.mozilla.signing import SigningMixin
from mozharness.mozilla.updates.balrog import BalrogMixin
from mozharness.base.python import VirtualenvMixin
@ -68,7 +66,7 @@ runtime_config_tokens = ('buildid', 'version', 'locale', 'from_buildid',
# DesktopSingleLocale {{{1
class DesktopSingleLocale(LocalesMixin, ReleaseMixin, BuildbotMixin,
VCSMixin, SigningMixin, PurgeMixin, BaseScript,
VCSMixin, PurgeMixin, BaseScript,
BalrogMixin, MarMixin, VirtualenvMixin, TransferMixin):
"""Manages desktop repacks"""
config_options = [[
@ -373,11 +371,6 @@ class DesktopSingleLocale(LocalesMixin, ReleaseMixin, BuildbotMixin,
self.buildbot_config["properties"]["en_us_installer_binary_url"])
bootstrap_env['EN_US_INSTALLER_BINARY_URL'] = str(
self.buildbot_config["properties"]["en_us_installer_binary_url"])
if 'MOZ_SIGNING_SERVERS' in os.environ:
sign_cmd = self.query_moz_sign_cmd(formats=None)
sign_cmd = subprocess.list2cmdline(sign_cmd)
# windows fix
bootstrap_env['MOZ_SIGN_CMD'] = sign_cmd.replace('\\', '\\\\\\\\')
for binary in self._mar_binaries():
# "mar -> MAR" and 'mar.exe -> MAR' (windows)
name = binary.replace('.exe', '')
@ -411,11 +404,6 @@ class DesktopSingleLocale(LocalesMixin, ReleaseMixin, BuildbotMixin,
def query_l10n_env(self):
l10n_env = self._query_upload_env().copy()
# both upload_env and bootstrap_env define MOZ_SIGN_CMD
# the one from upload_env is taken from os.environ, the one from
# bootstrap_env is set with query_moz_sign_cmd()
# we need to use the value provided my query_moz_sign_cmd or make upload
# will fail (signtool.py path is wrong)
l10n_env.update(self.query_bootstrap_env())
return l10n_env

View File

@ -13,7 +13,6 @@ Android. This also creates nightly updates.
import glob
import os
import re
import subprocess
import sys
try:
@ -31,7 +30,6 @@ from mozharness.base.transfer import TransferMixin
from mozharness.mozilla.buildbot import BuildbotMixin
from mozharness.mozilla.purge import PurgeMixin
from mozharness.mozilla.release import ReleaseMixin
from mozharness.mozilla.signing import MobileSigningMixin
from mozharness.mozilla.tooltool import TooltoolMixin
from mozharness.base.vcs.vcsbase import MercurialScript
from mozharness.mozilla.l10n.locales import LocalesMixin
@ -43,8 +41,8 @@ from mozharness.base.python import VirtualenvMixin
# MobileSingleLocale {{{1
class MobileSingleLocale(MockMixin, LocalesMixin, ReleaseMixin,
MobileSigningMixin, TransferMixin, TooltoolMixin,
BuildbotMixin, PurgeMixin, MercurialScript, BalrogMixin,
TransferMixin, TooltoolMixin, BuildbotMixin,
PurgeMixin, MercurialScript, BalrogMixin,
VirtualenvMixin, SecretsMixin):
config_options = [[
['--locale', ],
@ -185,9 +183,6 @@ class MobileSingleLocale(MockMixin, LocalesMixin, ReleaseMixin,
if c.get('base_en_us_binary_url') and c.get('release_config_file'):
rc = self.query_release_config()
repack_env['EN_US_BINARY_URL'] = c['base_en_us_binary_url'] % replace_dict
if 'MOZ_SIGNING_SERVERS' in os.environ:
repack_env['MOZ_SIGN_CMD'] = \
subprocess.list2cmdline(self.query_moz_sign_cmd(formats=['jar']))
if self.query_is_nightly() or self.query_is_nightly_promotion():
if self.query_is_nightly():
@ -227,8 +222,6 @@ class MobileSingleLocale(MockMixin, LocalesMixin, ReleaseMixin,
upload_env = self.query_env(partial_env=c.get("upload_env"),
replace_dict=replace_dict)
if 'MOZ_SIGNING_SERVERS' in os.environ:
upload_env['MOZ_SIGN_CMD'] = subprocess.list2cmdline(self.query_moz_sign_cmd())
if self.query_is_release_or_beta():
upload_env['MOZ_PKG_VERSION'] = '%(version)s' % replace_dict
self.upload_env = upload_env

View File

@ -144,7 +144,7 @@ nsRFPService::IsTimerPrecisionReductionEnabled(TimerPrecisionType aType)
#define HASH_DIGEST_SIZE_BITS (256)
#define HASH_DIGEST_SIZE_BYTES (HASH_DIGEST_SIZE_BITS / 8)
class LRUCache
class LRUCache final
{
public:
LRUCache()
@ -152,6 +152,8 @@ public:
this->cache.SetLength(LRU_CACHE_SIZE);
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LRUCache)
nsCString Get(long long aKey) {
for (auto & cacheEntry : this->cache) {
// Read optimistically befor locking
@ -202,6 +204,8 @@ public:
private:
~LRUCache() = default;
struct CacheEntry {
Atomic<long long, Relaxed> key;
PRTime accessTime = 0;
@ -224,7 +228,7 @@ private:
};
// We make a single LRUCache
static StaticAutoPtr<LRUCache> sCache;
static StaticRefPtr<LRUCache> sCache;
/**
* The purpose of this function is to deterministicly generate a random midpoint
@ -293,18 +297,20 @@ nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
const int kClampTimesPerDigest = HASH_DIGEST_SIZE_BITS / 32;
static uint8_t * sSecretMidpointSeed = nullptr;
if(MOZ_UNLIKELY(!sCache)) {
StaticMutexAutoLock lock(sLock);
if(MOZ_LIKELY(!sCache)) {
sCache = new LRUCache();
ClearOnShutdown(&sCache);
}
}
if(MOZ_UNLIKELY(!aMidpointOut)) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<LRUCache> cache;
{
StaticMutexAutoLock lock(sLock);
cache = sCache;
}
if(!cache) {
return NS_ERROR_FAILURE;
}
/*
* Below, we will call a cryptographic hash function. That's expensive. We look for ways to
* make it more efficient.
@ -328,7 +334,7 @@ nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
long long reducedResolution = aResolutionUSec * kClampTimesPerDigest;
long long extraClampedTime = (aClampedTimeUSec / reducedResolution) * reducedResolution;
nsCString hashResult = sCache->Get(extraClampedTime);
nsCString hashResult = cache->Get(extraClampedTime);
if(hashResult.Length() != HASH_DIGEST_SIZE_BYTES) { // Cache Miss =(
// If someone has pased in the testing-only parameter, replace our seed with it
@ -395,7 +401,7 @@ nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
NS_ENSURE_SUCCESS(rv, rv);
// Finally, store it in the cache
sCache->Store(extraClampedTime, derivedSecret);
cache->Store(extraClampedTime, derivedSecret);
hashResult = derivedSecret;
}
@ -695,6 +701,12 @@ nsRFPService::Init()
// Call Update here to cache the values of the prefs and set the timezone.
UpdateRFPPref();
// Create the LRU Cache when we initialize, to avoid accidently trying to
// create it (and call ClearOnShutdown) on a non-main-thread
if(!sCache) {
sCache = new LRUCache();
}
return rv;
}
@ -764,6 +776,11 @@ nsRFPService::StartShutdown()
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
StaticMutexAutoLock lock(sLock);
{
sCache = nullptr;
}
if (obs) {
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);

View File

@ -377,7 +377,7 @@ PluginWrapper.prototype = {
get blocklistState() {
let { tags: [tag] } = pluginFor(this);
return Services.blocklist.getPluginBlocklistState(tag);
return tag.blocklistState;
},
get blocklistURL() {

View File

@ -230,7 +230,6 @@ function Blocklist() {
Services.prefs.addObserver(PREF_EM_LOGGING_ENABLED, this);
this.wrappedJSObject = this;
// requests from child processes come in here, see receiveMessage.
Services.ppmm.addMessageListener("Blocklist:getPluginBlocklistState", this);
Services.ppmm.addMessageListener("Blocklist:content-blocklist-updated", this);
}
@ -256,7 +255,6 @@ Blocklist.prototype = {
shutdown() {
Services.obs.removeObserver(this, "xpcom-shutdown");
Services.ppmm.removeMessageListener("Blocklist:getPluginBlocklistState", this);
Services.ppmm.removeMessageListener("Blocklist:content-blocklist-updated", this);
Services.prefs.removeObserver("extensions.blocklist.", this);
Services.prefs.removeObserver(PREF_EM_LOGGING_ENABLED, this);
@ -294,10 +292,6 @@ Blocklist.prototype = {
// Message manager message handlers
receiveMessage(aMsg) {
switch (aMsg.name) {
case "Blocklist:getPluginBlocklistState":
return this.getPluginBlocklistState(aMsg.data.addonData,
aMsg.data.appVersion,
aMsg.data.toolkitVersion);
case "Blocklist:content-blocklist-updated":
Services.obs.notifyObservers(null, "content-blocklist-updated");
break;
@ -628,9 +622,6 @@ Blocklist.prototype = {
var oldAddonEntries = this._addonEntries;
var oldPluginEntries = this._pluginEntries;
this._addonEntries = [];
this._gfxEntries = [];
this._pluginEntries = [];
this._loadBlocklistFromXML(responseXML);
// We don't inform the users when the graphics blocklist changed at runtime.
@ -675,13 +666,6 @@ Blocklist.prototype = {
this._gfxEntries = [];
this._pluginEntries = [];
if (this._isBlocklistPreloaded()) {
Services.telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
this._loadBlocklistFromString(this._preloadedBlocklistContent);
delete this._preloadedBlocklistContent;
return;
}
Services.telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true);
var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
@ -806,16 +790,11 @@ Blocklist.prototype = {
return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
},
_isBlocklistPreloaded() {
return this._preloadedBlocklistContent != null;
},
/* Used for testing */
_clear() {
this._addonEntries = null;
this._gfxEntries = null;
this._pluginEntries = null;
this._preloadedBlocklistContent = null;
},
async _preloadBlocklist() {
@ -851,10 +830,15 @@ Blocklist.prototype = {
let text = await OS.File.read(path, { encoding: "utf-8" });
if (!this._addonEntries) {
// Store the content only if a sync load has not been performed in the meantime.
this._preloadedBlocklistContent = text;
}
await new Promise(resolve => {
Services.tm.idleDispatchToMainThread(() => {
if (!this.isLoaded) {
Services.telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
this._loadBlocklistFromString(text);
}
resolve();
});
});
},
_loadBlocklistFromString(text) {
@ -876,6 +860,9 @@ Blocklist.prototype = {
},
_loadBlocklistFromXML(doc) {
this._addonEntries = [];
this._gfxEntries = [];
this._pluginEntries = [];
try {
var childNodes = doc.documentElement.childNodes;
for (let element of childNodes) {

View File

@ -89,13 +89,8 @@ Blocklist.prototype = {
return true;
},
// There are a few callers in layout that rely on this.
getPluginBlocklistState(aPluginTag, aAppVersion, aToolkitVersion) {
return Services.cpmm.sendSyncMessage("Blocklist:getPluginBlocklistState", {
addonData: this.flattenObject(aPluginTag),
appVersion: aAppVersion,
toolkitVersion: aToolkitVersion
})[0];
throw new Error(kMissingAPIMessage);
},
getAddonBlocklistURL(aAddon, aAppVersion, aToolkitVersion) {

View File

@ -7,34 +7,41 @@ add_task(async function() {
getService().wrappedJSObject;
let scope = ChromeUtils.import("resource://gre/modules/osfile.jsm", {});
// sync -> async
// sync -> async. Check that async code doesn't try to read the file
// once it's already been read synchronously.
let read = scope.OS.File.read;
let triedToRead = false;
scope.OS.File.read = () => triedToRead = true;
blocklist._loadBlocklist();
Assert.ok(blocklist.isLoaded);
await blocklist._preloadBlocklist();
Assert.ok(!blocklist._isBlocklistPreloaded());
Assert.ok(!triedToRead);
scope.OS.File.read = read;
blocklist._clear();
// async -> sync
info("sync -> async complete");
// async first. Check that once we preload the content, that is sufficient.
await blocklist._preloadBlocklist();
Assert.ok(!blocklist.isLoaded);
Assert.ok(blocklist._isBlocklistPreloaded());
blocklist._loadBlocklist();
Assert.ok(blocklist.isLoaded);
Assert.ok(!blocklist._isBlocklistPreloaded());
// Calling _loadBlocklist now would just re-load the list sync.
info("async test complete");
blocklist._clear();
// async -> sync -> async
let read = scope.OS.File.read;
scope.OS.File.read = function(...args) {
return new Promise((resolve, reject) => {
executeSoon(() => {
blocklist._loadBlocklist();
// Now do the async bit after all:
resolve(read(...args));
});
});
};
await blocklist._preloadBlocklist();
// We're mostly just checking this doesn't error out.
Assert.ok(blocklist.isLoaded);
Assert.ok(!blocklist._isBlocklistPreloaded());
info("mixed async/sync test complete");
});

View File

@ -2,8 +2,17 @@
codespell:
description: Check code for common misspellings
include:
- tools/lint
- browser/base/content/docs/
- browser/experiments/docs
- build/docs
- mobile/android/docs
- python/mozlint
- taskcluster/docs
- testing/mozbase/docs
- toolkit/components/extensions/docs
- toolkit/components/telemetry/docs/
- toolkit/crashreporter/docs
- tools/lint
exclude:
- third_party
# List of extensions coming from:

View File

@ -1,6 +1,8 @@
browser/components/translation/cld2/
browser/extensions/mortar/ppapi/
db/sqlite3/src/
devtools/client/sourceeditor/codemirror/
devtools/client/sourceeditor/tern/
extensions/spellcheck/hunspell/src/
gfx/angle/
gfx/cairo/
@ -11,8 +13,8 @@ gfx/qcms/
gfx/sfntly/
gfx/skia/
gfx/vr/openvr/
gfx/webrender
gfx/webrender_api
gfx/webrender/
gfx/webrender_api/
gfx/wrench/
gfx/ycbcr/
intl/hyphenation/hyphen/
@ -56,6 +58,7 @@ modules/libbz2/
modules/libmar/
modules/pdfium/
modules/woff2/
modules/xz-embedded/
modules/zlib/
netwerk/sctp/src/
netwerk/srtp/src/
@ -95,4 +98,4 @@ toolkit/components/protobuf/
toolkit/components/url-classifier/chromium/
toolkit/components/url-classifier/protobuf/
toolkit/crashreporter/google-breakpad/
tools/fuzzing/libfuzzer
tools/fuzzing/libfuzzer/

View File

@ -2285,9 +2285,15 @@ moz_gtk_header_bar_paint(WidgetNodeType widgetType,
GtkStyleContext *style = GetStyleContext(widgetType, GTK_TEXT_DIR_LTR,
state_flags);
InsetByMargin(rect, style);
gtk_render_background(style, cr, rect->x, rect->y, rect->width,
rect->height);
gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
// Some themes (Adwaita for instance) draws bold dark line at
// titlebar bottom. It does not fit well with Firefox tabs so
// draw with some extent to make the titlebar bottom part invisible.
#define TITLEBAR_EXTENT 4
gtk_render_background(style, cr, rect->x, rect->y,
rect->width, rect->height + TITLEBAR_EXTENT);
gtk_render_frame(style, cr, rect->x, rect->y,
rect->width, rect->height + TITLEBAR_EXTENT);
return MOZ_GTK_SUCCESS;
}