mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 06:45:42 +00:00
Merge mozilla-central to inbound. a=merge CLOSED TREE
This commit is contained in:
commit
f785df755d
@ -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/.*
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
});
|
@ -19,6 +19,7 @@ let expectedBackgroundApisTargetSpecific = [
|
||||
"tabs.MutedInfoReason",
|
||||
"tabs.TAB_ID_NONE",
|
||||
"tabs.TabStatus",
|
||||
"tabs.UpdatePropertyName",
|
||||
"tabs.WindowType",
|
||||
"tabs.ZoomSettingsMode",
|
||||
"tabs.ZoomSettingsScope",
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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"/>
|
||||
|
@ -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
|
||||
|
@ -667,7 +667,7 @@ function getAvailableIds() {
|
||||
* Show available ids.
|
||||
*/
|
||||
function showAvailableIds() {
|
||||
info(getAvailableIds);
|
||||
info(getAvailableIds());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
)
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
41
toolkit/components/resistfingerprinting/nsRFPService.cpp
Normal file → Executable file
41
toolkit/components/resistfingerprinting/nsRFPService.cpp
Normal file → Executable 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);
|
||||
|
||||
|
@ -377,7 +377,7 @@ PluginWrapper.prototype = {
|
||||
|
||||
get blocklistState() {
|
||||
let { tags: [tag] } = pluginFor(this);
|
||||
return Services.blocklist.getPluginBlocklistState(tag);
|
||||
return tag.blocklistState;
|
||||
},
|
||||
|
||||
get blocklistURL() {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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:
|
||||
|
@ -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/
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user