diff --git a/README.md b/README.md
index 434e58315..f391024f4 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne
### Firefox Versions
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `154.0.1`!
-- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 154.0.1`!
+- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 155.0`!
### Contributing
diff --git a/build/firefox-cache/l10n-last-commit-hash b/build/firefox-cache/l10n-last-commit-hash
index 2dbfec531..e668d9111 100644
--- a/build/firefox-cache/l10n-last-commit-hash
+++ b/build/firefox-cache/l10n-last-commit-hash
@@ -1 +1 @@
-bf4b57fd7fe94054457be7018e8e7beaf754c520
\ No newline at end of file
+6b4abe0093721c1be89eb41b3fb060dd6336923d
\ No newline at end of file
diff --git a/src/-stylelintrc-js.patch b/src/-stylelintrc-js.patch
index baadd6208..75a6e5999 100644
--- a/src/-stylelintrc-js.patch
+++ b/src/-stylelintrc-js.patch
@@ -1,5 +1,5 @@
diff --git a/.stylelintrc.js b/.stylelintrc.js
-index b484bf6600a7e1b8ad2aed8c8b7fe5e84cc3d023..f89712e2c13dfa0bcb48e55cd3872c67f5c98dad 100644
+index b5f8245fa04ffb8fc82240261d63f893f4629f19..1b6ead24adbe69fcb5aeb95cfd3e7fa9662d89d8 100644
--- a/.stylelintrc.js
+++ b/.stylelintrc.js
@@ -67,7 +67,7 @@ module.exports = {
@@ -11,10 +11,10 @@ index b484bf6600a7e1b8ad2aed8c8b7fe5e84cc3d023..f89712e2c13dfa0bcb48e55cd3872c67
{
ignore: ["blockless-at-rules"],
},
-@@ -280,7 +280,7 @@ module.exports = {
- "media-query-no-invalid": null,
+@@ -281,7 +281,7 @@ module.exports = {
"stylelint-plugin-mozilla/media-query-no-invalid": true,
"stylelint-plugin-mozilla/no-base-design-tokens": true,
+ "stylelint-plugin-mozilla/no-has-selector": true,
- "stylelint-plugin-mozilla/use-design-tokens": true,
+ "stylelint-plugin-mozilla/use-design-tokens": false,
},
diff --git a/src/browser/components/customizableui/CustomizableUI-sys-mjs.patch b/src/browser/components/customizableui/CustomizableUI-sys-mjs.patch
index 15192e8c1..ba2b8eb35 100644
--- a/src/browser/components/customizableui/CustomizableUI-sys-mjs.patch
+++ b/src/browser/components/customizableui/CustomizableUI-sys-mjs.patch
@@ -1,5 +1,5 @@
diff --git a/browser/components/customizableui/CustomizableUI.sys.mjs b/browser/components/customizableui/CustomizableUI.sys.mjs
-index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2681652e5 100644
+index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd8869254d59 100644
--- a/browser/components/customizableui/CustomizableUI.sys.mjs
+++ b/browser/components/customizableui/CustomizableUI.sys.mjs
@@ -13,6 +13,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
@@ -10,7 +10,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
HomePage: "resource:///modules/HomePage.sys.mjs",
PanelMultiView:
"moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs",
-@@ -348,7 +349,7 @@ var CustomizableUIInternal = {
+@@ -352,7 +353,7 @@ var CustomizableUIInternal = {
{
type: CustomizableUI.TYPE_PANEL,
defaultPlacements: [],
@@ -19,7 +19,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
},
false
);
-@@ -358,27 +359,23 @@ var CustomizableUIInternal = {
+@@ -362,27 +363,23 @@ var CustomizableUIInternal = {
"back-button",
"forward-button",
"stop-reload-button",
@@ -50,17 +50,18 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
defaultCollapsed: false,
},
true
-@@ -402,9 +399,6 @@ var CustomizableUIInternal = {
+@@ -406,10 +403,6 @@ var CustomizableUIInternal = {
type: CustomizableUI.TYPE_TOOLBAR,
defaultPlacements: [
"tabbrowser-tabs",
- "new-tab-button",
+- "spring",
- "alltabs-button",
- "ai-window-toggle",
],
verticalTabsDefaultPlacements: [],
defaultCollapsed: null,
-@@ -484,6 +478,7 @@ var CustomizableUIInternal = {
+@@ -502,6 +495,7 @@ var CustomizableUIInternal = {
CustomizableUI.AREA_NAVBAR,
CustomizableUI.AREA_BOOKMARKS,
CustomizableUI.AREA_TABSTRIP,
@@ -68,7 +69,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
]);
if (AppConstants.platform != "macosx") {
toolbars.add(CustomizableUI.AREA_MENUBAR);
-@@ -1285,6 +1280,9 @@ var CustomizableUIInternal = {
+@@ -1334,6 +1328,9 @@ var CustomizableUIInternal = {
placements = gPlacements.get(area);
}
@@ -78,7 +79,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
// For toolbars that need it, mark as dirty.
let defaultPlacements = areaProperties.get("defaultPlacements");
if (
-@@ -1793,7 +1791,6 @@ var CustomizableUIInternal = {
+@@ -1842,7 +1839,6 @@ var CustomizableUIInternal = {
lazy.log.info(
"Widget " + aWidgetId + " not found, unable to remove from " + aArea
);
@@ -86,7 +87,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
}
this.notifyDOMChange(widgetNode, null, container, true, () => {
-@@ -1803,7 +1800,7 @@ var CustomizableUIInternal = {
+@@ -1852,7 +1848,7 @@ var CustomizableUIInternal = {
// We also need to remove the panel context menu if it's there:
this.ensureButtonContextMenu(widgetNode);
if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
@@ -95,7 +96,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
} else {
window.gNavToolbox.palette.appendChild(widgetNode);
}
-@@ -1971,16 +1968,16 @@ var CustomizableUIInternal = {
+@@ -2020,16 +2016,16 @@ var CustomizableUIInternal = {
elem.setAttribute("skipintoolbarset", "true");
}
}
@@ -115,7 +116,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
// Handle initial state of vertical tabs.
if (isVerticalTabs) {
// Show the vertical tabs toolbar
-@@ -2222,6 +2219,10 @@ var CustomizableUIInternal = {
+@@ -2271,6 +2267,10 @@ var CustomizableUIInternal = {
* The identifier string of the area that aNode is being inserted into.
*/
insertWidgetBefore(aNode, aNextNode, aContainer, aAreaId) {
@@ -126,7 +127,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
this.notifyDOMChange(aNode, aNextNode, aContainer, false, () => {
this.setLocationAttributes(aNode, aAreaId);
aContainer.insertBefore(aNode, aNextNode);
-@@ -4592,7 +4593,7 @@ var CustomizableUIInternal = {
+@@ -4647,7 +4647,7 @@ var CustomizableUIInternal = {
* For all registered areas, builds those areas to reflect the current
* placement state of all widgets.
*/
@@ -135,7 +136,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
for (let [areaId, areaNodes] of gBuildAreas) {
let placements = gPlacements.get(areaId);
let isFirstChangedToolbar = true;
-@@ -4603,7 +4604,7 @@ var CustomizableUIInternal = {
+@@ -4658,7 +4658,7 @@ var CustomizableUIInternal = {
if (area.get("type") == CustomizableUI.TYPE_TOOLBAR) {
let defaultCollapsed = area.get("defaultCollapsed");
let win = areaNode.documentGlobal;
@@ -144,7 +145,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
win.setToolbarVisibility(
areaNode,
typeof defaultCollapsed == "string"
-@@ -5899,6 +5900,7 @@ export var CustomizableUI = {
+@@ -5971,6 +5971,7 @@ export var CustomizableUI = {
unregisterArea(aName, aDestroyPlacements) {
CustomizableUIInternal.unregisterArea(aName, aDestroyPlacements);
},
@@ -152,7 +153,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
/**
* Add a widget to an area.
* If the area to which you try to add is not known to CustomizableUI,
-@@ -7869,7 +7871,9 @@ class OverflowableToolbar {
+@@ -7946,7 +7947,9 @@ class OverflowableToolbar {
);
if (webExtList && CustomizableUI.isWebExtensionWidget(child.id)) {
@@ -162,7 +163,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
webExtList.insertBefore(child, webExtList.firstElementChild);
} else {
child.setAttribute("cui-anchorid", this.#defaultListButton.id);
-@@ -7929,7 +7933,7 @@ class OverflowableToolbar {
+@@ -8006,7 +8009,7 @@ class OverflowableToolbar {
) {
continue;
}
@@ -171,7 +172,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
if (child != aExceptChild) {
sum += getInlineSize(child);
}
-@@ -7953,11 +7957,11 @@ class OverflowableToolbar {
+@@ -8030,11 +8033,11 @@ class OverflowableToolbar {
parseFloat(style.paddingLeft) -
parseFloat(style.paddingRight) -
toolbarChildrenWidth;
@@ -185,7 +186,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
});
lazy.log.debug(
-@@ -7972,7 +7976,39 @@ class OverflowableToolbar {
+@@ -8049,7 +8052,39 @@ class OverflowableToolbar {
Math.max(targetWidth, targetChildrenWidth)
);
totalAvailWidth = Math.ceil(totalAvailWidth);
@@ -226,7 +227,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
return { isOverflowing, targetContentWidth, totalAvailWidth };
}
-@@ -8033,7 +8069,11 @@ class OverflowableToolbar {
+@@ -8110,7 +8145,11 @@ class OverflowableToolbar {
return;
}
}
@@ -239,7 +240,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
lazy.log.debug(
`Need ${minSize} but width is ${totalAvailWidth} so bailing`
);
-@@ -8066,7 +8106,7 @@ class OverflowableToolbar {
+@@ -8143,7 +8182,7 @@ class OverflowableToolbar {
}
}
if (!inserted) {
@@ -248,7 +249,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
}
child.removeAttribute("cui-anchorid");
child.removeAttribute("overflowedItem");
-@@ -8192,6 +8232,9 @@ class OverflowableToolbar {
+@@ -8269,6 +8308,9 @@ class OverflowableToolbar {
* if no such list exists.
*/
get #webExtList() {
@@ -258,7 +259,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
if (!this.#webExtListRef) {
let targetID = this.#toolbar.getAttribute("addon-webext-overflowtarget");
if (!targetID) {
-@@ -8203,6 +8246,9 @@ class OverflowableToolbar {
+@@ -8280,6 +8322,9 @@ class OverflowableToolbar {
let win = this.#toolbar.documentGlobal;
let { panel } = win.gUnifiedExtensions;
this.#webExtListRef = panel.querySelector(`#${targetID}`);
@@ -268,7 +269,7 @@ index 491161e48044f75167a8e8a0348779cfa22fc40e..867d51b8a6b74f841afc6d84dcdfd8f2
}
return this.#webExtListRef;
}
-@@ -8411,7 +8457,7 @@ class OverflowableToolbar {
+@@ -8488,7 +8533,7 @@ class OverflowableToolbar {
break;
}
case "mousedown": {
diff --git a/src/browser/components/preferences/preferences-xhtml.patch b/src/browser/components/preferences/preferences-xhtml.patch
index 2c2c134fb..354e6c5c5 100644
--- a/src/browser/components/preferences/preferences-xhtml.patch
+++ b/src/browser/components/preferences/preferences-xhtml.patch
@@ -27,7 +27,7 @@ index 1b8c38159314b6edee9e6f455beb2b63db1ad3f0..1308345db640dba6b2848b49ef7c1181
+
+
+
+ {
-@@ -552,7 +566,7 @@
+@@ -580,7 +594,7 @@
if (tab.selected) {
selectedTab = tab;
indexForSelectedTab = newIndex;
@@ -113,7 +113,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
const droppedIntoPinnedArea = dropIndex < gBrowser.pinnedTabCount;
const newSplitView = gBrowser.adoptSplitView(tab, {
elementIndex: droppedIntoPinnedArea
-@@ -577,7 +591,7 @@
+@@ -605,7 +619,7 @@
}
}
}
@@ -122,7 +122,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
const newTab = gBrowser.adoptTab(selectedTab, {
elementIndex: indexForSelectedTab,
selectTab: selectedTab == draggedTab,
-@@ -611,10 +625,6 @@
+@@ -639,10 +653,6 @@
this._tabbrowserTabs.dragAndDropElements[
gBrowser.pinnedTabCount + unpinnedSplitViews.length - 1
];
@@ -133,7 +133,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
}
}
} else {
-@@ -664,6 +674,7 @@
+@@ -703,6 +713,7 @@
let nextItem = this._tabbrowserTabs.dragAndDropElements[newIndex];
let tabGroup = isTab(nextItem) && nextItem.group;
@@ -141,12 +141,15 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
gBrowser.loadTabs(urls, {
inBackground,
replace,
-@@ -705,7 +716,16 @@
+@@ -744,6 +755,22 @@
this._expandGroupOnDrop(draggedTab);
}
- this._resetTabsAfterDrop(draggedTab.ownerDocument);
--
-+ if (!dt.mozUserCancelled && dt.dropEffect == "none" && !this._isCustomizing) {
+ this._resetTabsAfterDrop(draggedTab);
++ if (
++ !dt.mozUserCancelled &&
++ dt.dropEffect == "none" &&
++ !this._tabbrowserTabs._isCustomizing
++ ) {
+ const moved = gZenViewSplitter.moveTabToSplitView(event, draggedTab);
+ if (moved) {
+ delete draggedTab._dragData;
@@ -154,12 +157,14 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
+ }
+ } else if (dt.mozUserCancelled) {
+ gZenViewSplitter.onBrowserDragEndToSplit(event, true);
-+ if (gZenViewSplitter._lastOpenedTab) gZenViewSplitter._lastOpenedTab._visuallySelected = false;
++ if (gZenViewSplitter._lastOpenedTab) {
++ gZenViewSplitter._lastOpenedTab._visuallySelected = false;
++ }
+ }
+
if (
dt.mozUserCancelled ||
- dt.dropEffect != "none" ||
-@@ -927,11 +947,10 @@
+@@ -1074,11 +1101,10 @@
return this.#getHorizontalScrollboxDragTarget(event, ignoreSides);
}
while (target) {
@@ -175,7 +180,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
break;
}
target = target.parentNode;
-@@ -961,6 +980,9 @@
+@@ -1108,6 +1134,9 @@
return null;
}
}
@@ -185,17 +190,36 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
return target;
}
-@@ -1014,7 +1036,8 @@
- isTabGroupLabel(draggedTab) &&
- draggedTab._dragData?.expandGroupOnDrop
- ) {
-- draggedTab.group.collapsed = false;
-+ draggedTab.group.collapsed = draggedTab.group.hasAttribute("has-active");
-+ gZenFolders.animateGroupMove(draggedTab.group, true);
+@@ -1170,14 +1199,20 @@
+ let releaseReservedSpace = () =>
+ this.#releaseSpaceInScrolledContent(periphery);
+ if (group.collapsed) {
+- // The group's tabs animate back to their full size, so hold on to the
+- // space reserved for them until they get there.
+- group.addEventListener(
+- "TabGroupAnimationComplete",
+- releaseReservedSpace,
+- { once: true }
+- );
+- group.collapsed = false;
++ const expand = !group.hasAttribute("has-active");
++ if (expand) {
++ // The group's tabs animate back to their full size, so hold on to the
++ // space reserved for them until they get there.
++ group.addEventListener(
++ "TabGroupAnimationComplete",
++ releaseReservedSpace,
++ { once: true }
++ );
++ } else {
++ releaseReservedSpace();
++ }
++ group.collapsed = !expand;
++ gZenFolders.animateGroupMove(group, true);
+ } else {
+ releaseReservedSpace();
}
- }
-
-@@ -1206,7 +1229,6 @@
+@@ -1362,7 +1397,6 @@
// using updateDragImage. On Linux, we can use a panel.
if (platform == "win" || platform == "macosx") {
captureListener = function () {
@@ -203,7 +227,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
};
} else {
// Create a panel to use it in setDragImage
-@@ -1244,7 +1266,6 @@
+@@ -1400,7 +1434,6 @@
);
dragImageOffset = dragImageOffset * scale;
}
@@ -211,7 +235,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
// _dragData.offsetX/Y give the coordinates that the mouse should be
// positioned relative to the corner of the new window created upon
-@@ -1263,7 +1284,7 @@
+@@ -1419,7 +1452,7 @@
let dropEffect = this.getDropEffectForTabDrag(event);
let isMovingInTabStrip = !fromTabList && dropEffect == "move";
let collapseTabGroupDuringDrag =
@@ -220,7 +244,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
tab._dragData = {
offsetX: this._tabbrowserTabs.verticalMode
-@@ -1273,7 +1294,7 @@
+@@ -1429,7 +1462,7 @@
? event.screenY - window.screenY - tabOffset
: event.screenY - window.screenY,
scrollPos:
@@ -229,7 +253,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
? this._tabbrowserTabs.pinnedTabsContainer.scrollPosition
: this._tabbrowserTabs.arrowScrollbox.scrollPosition,
screenX: event.screenX,
-@@ -1320,6 +1341,7 @@
+@@ -1522,6 +1555,7 @@
if (tabStripItemElement.hasAttribute("dragtarget")) {
return;
}
@@ -237,7 +261,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
let isPinned = tab.pinned;
let dragAndDropElements = this._tabbrowserTabs.dragAndDropElements;
let isGrid = this._tabbrowserTabs.isContainerVerticalPinnedGrid(tab);
-@@ -1686,23 +1708,6 @@
+@@ -1895,23 +1929,6 @@
// Slide the relevant tabs to their new position.
// non-moving tabs adjust for RTL
@@ -261,7 +285,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
// moving tabs don't adjust for RTL
for (let item of selectedElements) {
if (
-@@ -1751,7 +1756,6 @@
+@@ -1960,7 +1977,6 @@
for (let item of this._tabbrowserTabs.dragAndDropElements) {
delete item._moveTogetherSelectedTabsData;
item = elementToMove(item);
@@ -269,7 +293,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
item.removeAttribute("multiselected-move-together");
}
}
-@@ -2598,7 +2602,6 @@
+@@ -2812,7 +2828,6 @@
tab.style.top = "";
tab.style.maxWidth = "";
tab.style.pointerEvents = "";
@@ -277,7 +301,7 @@ index 8bb1219962a1368f50b53c61041f135c68e35f31..8e72441d26883cbef7a352b00c0fe2b1
tab.removeAttribute("small-stack");
tab.removeAttribute("big-stack");
}
-@@ -2607,11 +2610,9 @@
+@@ -2821,11 +2836,9 @@
)) {
label.style.width = "";
label.style.maxWidth = "";
diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
index 54ab76446..e5813e9ba 100644
--- a/src/browser/components/tabbrowser/content/tabbrowser-js.patch
+++ b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
@@ -1,8 +1,8 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
-index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da5504f9d8 100644
+index 43180487a2fac15b7a65fac94f233eeed9094990..ae1288974cae29da929f27b78ff8db542566eba2 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
-@@ -513,6 +513,7 @@
+@@ -514,6 +514,7 @@
* @type {MozBrowser[]}
*/
get splitViewBrowsers() {
@@ -10,7 +10,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
const browsers = [];
if (this.#activeSplitView) {
for (const tab of this.#activeSplitView.tabs) {
-@@ -611,15 +612,66 @@
+@@ -612,15 +613,66 @@
return this.tabContainer.visibleTabs;
}
@@ -79,7 +79,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
setSelectedTab(val, metricsContext = null) {
if (this.selectedTab === val) {
return;
-@@ -632,6 +684,9 @@
+@@ -633,6 +685,9 @@
) {
return;
}
@@ -89,7 +89,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// Update the tab
this.tabbox.selectedTab = val;
-@@ -708,6 +763,10 @@
+@@ -709,6 +764,10 @@
userContextId = parseInt(tabArgument.getAttribute("usercontextid"), 10);
}
@@ -100,7 +100,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (openWindowInfo) {
userContextId = openWindowInfo.originAttributes.userContextId;
}
-@@ -809,6 +868,8 @@
+@@ -810,6 +869,8 @@
this.tabpanels.appendChild(panel);
let tab = this.tabs[0];
@@ -109,7 +109,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
tab.linkedPanel = uniqueId;
this._selectedTab = tab;
this._selectedBrowser = browser;
-@@ -1178,18 +1239,24 @@
+@@ -1179,18 +1240,24 @@
* The context for the operation for telemetry purposes, defaults to an unknown context.
*/
pinTab(aTab, { metricsContext = this.TabMetrics.UNKNOWN_CONTEXT } = {}) {
@@ -135,7 +135,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
aTab.setAttribute("pinned", "true");
this._updateTabBarForPinnedTabs();
-@@ -1206,16 +1273,25 @@
+@@ -1207,16 +1274,25 @@
* The context for the operation for telemetry purposes, defaults to an unknown context.
*/
unpinTab(aTab, { metricsContext = this.TabMetrics.UNKNOWN_CONTEXT } = {}) {
@@ -162,7 +162,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
});
aTab.style.marginInlineStart = "";
-@@ -1430,6 +1506,9 @@
+@@ -1432,6 +1508,9 @@
let LOCAL_PROTOCOLS = ["chrome:", "about:", "resource:", "data:"];
@@ -172,7 +172,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (
aIconURL &&
!LOCAL_PROTOCOLS.some(protocol => aIconURL.startsWith(protocol))
-@@ -1439,6 +1518,9 @@
+@@ -1441,6 +1520,9 @@
);
return;
}
@@ -182,7 +182,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
let browser = this.getBrowserForTab(aTab);
browser.mIconURL = aIconURL;
-@@ -1761,7 +1843,6 @@
+@@ -1763,7 +1845,6 @@
// Preview mode should not reset the owner
if (!this.#previewMode && !oldTab.selected) {
@@ -190,7 +190,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
let lastRelatedTab = this.#lastRelatedTabMap.get(oldTab);
-@@ -1852,6 +1933,7 @@
+@@ -1854,6 +1935,7 @@
if (!this.#previewMode) {
newTab.recordTimeFromUnloadToReload();
newTab.updateLastAccessed();
@@ -198,7 +198,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
oldTab.updateLastAccessed();
// if this is the foreground window, update the last-seen timestamps.
if (this.documentGlobal == BrowserWindowTracker.getTopWindow()) {
-@@ -2066,6 +2148,9 @@
+@@ -2068,6 +2150,9 @@
}
let activeEl = document.activeElement;
@@ -208,7 +208,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// If focus is on the old tab, move it to the new tab.
if (activeEl == oldTab) {
newTab.focus();
-@@ -2104,7 +2189,7 @@
+@@ -2106,7 +2191,7 @@
// Focus the location bar if it was previously focused for that tab.
// In full screen mode, only bother making the location bar visible
// if the tab is a blank one.
@@ -217,7 +217,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
let selectURL = () => {
if (this._asyncTabSwitching) {
// Set _awaitingSetURI flag to suppress popup notification
-@@ -2392,7 +2477,12 @@
+@@ -2394,7 +2479,12 @@
return this._setTabLabel(aTab, aLabel);
}
@@ -231,7 +231,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (!aLabel || (isURL && /^about:reader\?url=/.test(aLabel))) {
return false;
}
-@@ -2518,7 +2608,7 @@
+@@ -2520,7 +2610,7 @@
newIndex = this.selectedTab._tPos + 1;
}
@@ -240,7 +240,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (this.isTabGroupLabel(targetTab)) {
throw new Error(
"Replacing a tab group label with a tab is not supported"
-@@ -2793,6 +2883,7 @@
+@@ -2795,6 +2885,7 @@
uriIsAboutBlank,
userContextId,
skipLoad,
@@ -248,7 +248,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
} = {}) {
let b = document.createXULElement("browser");
// Use the JSM global to create the permanentKey, so that if the
-@@ -2866,8 +2957,7 @@
+@@ -2868,8 +2959,7 @@
// we use a different attribute name for this?
b.setAttribute("name", name);
}
@@ -258,7 +258,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
b.setAttribute("transparent", "true");
}
-@@ -3022,7 +3112,7 @@
+@@ -3024,7 +3114,7 @@
let panel = this.getPanel(browser);
let uniqueId = this._generateUniquePanelID();
@@ -267,7 +267,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
aTab.linkedPanel = uniqueId;
// Inject the into the DOM if necessary.
-@@ -3082,8 +3172,8 @@
+@@ -3084,8 +3174,8 @@
// If we transitioned from one browser to two browsers, we need to set
// hasSiblings=false on both the existing browser and the new browser.
if (this.tabs.length == 2) {
@@ -278,7 +278,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
} else {
aTab.linkedBrowser.browsingContext.hasSiblings = this.tabs.length > 1;
}
-@@ -3268,7 +3358,6 @@
+@@ -3270,7 +3360,6 @@
this.selectedTab = this.addTrustedTab(BROWSER_NEW_TAB_URL, {
tabIndex: tab._tPos + 1,
userContextId: tab.userContextId,
@@ -286,7 +286,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
focusUrlBar: true,
});
resolve(this.selectedBrowser);
-@@ -3378,6 +3467,10 @@
+@@ -3380,6 +3469,10 @@
schemelessInput,
hasValidUserGestureActivation = false,
textDirectiveUserActivation = false,
@@ -297,7 +297,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
} = {}
) {
// all callers of addTab that pass a params object need to pass
-@@ -3388,10 +3481,25 @@
+@@ -3390,10 +3483,25 @@
);
}
@@ -323,7 +323,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// If we're opening a foreground tab, set the owner by default.
ownerTab ??= inBackground ? null : this.selectedTab;
-@@ -3399,6 +3507,7 @@
+@@ -3401,6 +3509,7 @@
if (this.selectedTab.owner) {
this.selectedTab.owner = null;
}
@@ -331,7 +331,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// Find the tab that opened this one, if any. This is used for
// determining positioning, and inherited attributes such as the
-@@ -3451,6 +3560,22 @@
+@@ -3453,6 +3562,22 @@
noInitialLabel,
skipBackgroundNotify,
});
@@ -354,7 +354,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (insertTab) {
// Insert the tab into the tab container in the correct position.
this.#insertTabAtIndex(t, {
-@@ -3459,6 +3584,7 @@
+@@ -3461,6 +3586,7 @@
ownerTab,
openerTab,
pinned,
@@ -362,7 +362,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
bulkOrderedOpen,
tabGroup: tabGroup ?? openerTab?.group,
});
-@@ -3477,6 +3603,7 @@
+@@ -3479,6 +3605,7 @@
openWindowInfo,
skipLoad,
triggeringRemoteType,
@@ -370,7 +370,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}));
if (focusUrlBar) {
-@@ -3601,6 +3728,12 @@
+@@ -3603,6 +3730,12 @@
}
}
@@ -383,7 +383,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// Additionally send pinned tab events
if (pinned) {
this.#notifyPinnedStatus(t);
-@@ -3611,6 +3744,15 @@
+@@ -3613,6 +3746,15 @@
if (!inBackground) {
this.selectedTab = t;
}
@@ -399,7 +399,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
return t;
}
-@@ -3834,6 +3976,7 @@
+@@ -3836,6 +3978,7 @@
insertBefore = null,
isAdoptingGroup = false,
metricsContext = this.TabMetrics.UNKNOWN_CONTEXT,
@@ -407,7 +407,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
} = {}
) {
if (
-@@ -3844,9 +3987,6 @@
+@@ -3846,9 +3989,6 @@
!this.isSplitViewWrapper(tabOrSplitView)
)
) {
@@ -417,7 +417,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
if (!color) {
-@@ -3867,9 +4007,14 @@
+@@ -3869,9 +4009,14 @@
label,
isAdoptingGroup
);
@@ -434,7 +434,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
);
group.addTabs(tabsAndSplitViews, metricsContext);
-@@ -3973,7 +4118,7 @@
+@@ -3975,7 +4120,7 @@
}
this.#handleTabMove(tab, () =>
@@ -443,7 +443,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
);
}
-@@ -4059,6 +4204,7 @@
+@@ -4061,6 +4206,7 @@
color: group.color,
insertBefore: newTabs[0],
isAdoptingGroup: true,
@@ -451,7 +451,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
});
}
-@@ -4269,6 +4415,7 @@
+@@ -4271,6 +4417,7 @@
openWindowInfo,
skipLoad,
triggeringRemoteType,
@@ -459,7 +459,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
) {
// If we don't have a preferred remote type (or it is `NOT_REMOTE`), and
-@@ -4329,6 +4476,7 @@
+@@ -4331,6 +4478,7 @@
openWindowInfo,
name,
skipLoad,
@@ -467,7 +467,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
});
}
-@@ -4542,9 +4690,9 @@
+@@ -4544,9 +4692,9 @@
}
// Add a new tab if needed.
@@ -479,7 +479,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
let url = "about:blank";
if (tabData.entries?.length) {
-@@ -4577,8 +4725,10 @@
+@@ -4579,8 +4727,10 @@
insertTab: false,
skipLoad: true,
preferredRemoteType,
@@ -491,7 +491,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (select) {
tabToSelect = tab;
}
-@@ -4600,7 +4750,8 @@
+@@ -4602,7 +4752,8 @@
this.pinTab(tab);
// Then ensure all the tab open/pinning information is sent.
this._fireTabOpen(tab, {});
@@ -501,7 +501,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
let { groupId } = tabData;
const tabGroup = tabGroupWorkingData.get(groupId);
// if a tab refers to a tab group we don't know, skip any group
-@@ -4620,7 +4771,10 @@
+@@ -4622,7 +4773,10 @@
tabGroup.stateData.id,
tabGroup.stateData.color,
tabGroup.stateData.collapsed,
@@ -513,7 +513,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
);
tabsFragment.appendChild(tabGroup.node);
}
-@@ -4675,9 +4829,21 @@
+@@ -4677,9 +4831,21 @@
// to remove the old selected tab.
if (tabToSelect) {
let leftoverTab = this.selectedTab;
@@ -535,7 +535,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (tabs.length > 1 || !tabs[0].selected) {
this._updateTabsAfterInsert();
-@@ -4882,11 +5048,17 @@
+@@ -4884,11 +5050,17 @@
if (ownerTab) {
tab.owner = ownerTab;
}
@@ -554,7 +554,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (
!bulkOrderedOpen &&
((openerTab &&
-@@ -4898,7 +5070,7 @@
+@@ -4900,7 +5072,7 @@
let lastRelatedTab =
openerTab && this.#lastRelatedTabMap.get(openerTab);
let previousTab = lastRelatedTab || openerTab || this.selectedTab;
@@ -563,7 +563,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
tabGroup = previousTab.group;
}
if (
-@@ -4914,7 +5086,7 @@
+@@ -4916,7 +5088,7 @@
previousTab.splitview
) + 1;
} else if (previousTab.visible) {
@@ -572,7 +572,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
} else if (previousTab == FirefoxViewHandler.tab) {
elementIndex = 0;
}
-@@ -4942,14 +5114,14 @@
+@@ -4944,14 +5116,14 @@
}
// Ensure index is within bounds.
if (tab.pinned) {
@@ -591,7 +591,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (pinned && !itemAfter?.pinned) {
itemAfter = null;
-@@ -4966,7 +5138,7 @@
+@@ -4968,7 +5140,7 @@
this.tabContainer._invalidateCachedTabs();
@@ -600,7 +600,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (
(this.isTab(itemAfter) && itemAfter.group == tabGroup) ||
this.isSplitViewWrapper(itemAfter)
-@@ -4997,7 +5169,11 @@
+@@ -4999,7 +5171,11 @@
const tabContainer = pinned
? this.tabContainer.pinnedTabsContainer
: this.tabContainer;
@@ -612,7 +612,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
if (tab.group?.collapsed) {
-@@ -5012,6 +5188,7 @@
+@@ -5014,6 +5190,7 @@
if (pinned) {
this._updateTabBarForPinnedTabs();
}
@@ -620,7 +620,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
TabBarVisibility.update();
}
-@@ -5568,6 +5745,7 @@
+@@ -5577,6 +5754,7 @@
metricsContext,
} = {}
) {
@@ -628,7 +628,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
// can be considered equivalent to closing the window.
if (
-@@ -5678,6 +5856,7 @@
+@@ -5687,6 +5865,7 @@
closedTabCount -= 1;
}
}
@@ -636,7 +636,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (closedTabCount > 0) {
this.recordTabMetrics(
-@@ -5730,6 +5909,14 @@
+@@ -5761,6 +5940,14 @@
return;
}
@@ -651,7 +651,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
let isVisibleTab = aTab.visible;
// We have to sample the tab width now, since _beginRemoveTab might
// end up modifying the DOM in such a way that aTab gets a new
-@@ -5737,6 +5924,9 @@
+@@ -5768,6 +5955,9 @@
// state).
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
let isLastTab = this.#isLastTabInWindow(aTab);
@@ -661,7 +661,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (
!this._beginRemoveTab(aTab, {
closeWindowFastpath: true,
-@@ -5747,13 +5937,14 @@
+@@ -5778,13 +5968,14 @@
metricsContext,
})
) {
@@ -677,7 +677,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
let lockTabSizing =
!this.tabContainer.verticalMode &&
!aTab.pinned &&
-@@ -5784,7 +5975,13 @@
+@@ -5815,7 +6006,13 @@
// We're not animating, so we can cancel the animation stopwatch.
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
aTab._closeTimeAnimTimerId = null;
@@ -692,7 +692,18 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
return;
}
-@@ -5827,7 +6024,7 @@
+@@ -5855,7 +6052,9 @@
+ get #shouldCloseWindowWithLastTab() {
+ return (
+ !window.toolbar.visible ||
+- Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab")
++ (Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab") &&
++ !gZenWorkspaces._isClosingWindow &&
++ !gZenWorkspaces._removedByStartupPage)
+ );
+ }
+
+@@ -5871,7 +6070,7 @@
*/
#isLastTabInWindow(tab) {
for (const otherTab of this.tabs) {
@@ -701,16 +712,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
return false;
}
}
-@@ -5917,7 +6114,7 @@
- closeWindowWithLastTab != null
- ? closeWindowWithLastTab
- : !window.toolbar.visible ||
-- Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
-+ Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab") && !gZenWorkspaces._isClosingWindow && !gZenWorkspaces._removedByStartupPage;
-
- if (closeWindow) {
- // We've already called beforeunload on all the relevant tabs if we get here,
-@@ -5941,6 +6138,7 @@
+@@ -5982,6 +6181,7 @@
newTab = true;
}
@@ -718,8 +720,8 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation.
-@@ -5982,13 +6180,7 @@
- aTab._mouseleave();
+@@ -6029,13 +6229,7 @@
+ }
if (newTab) {
- this.addTrustedTab(BROWSER_NEW_TAB_URL, {
@@ -733,7 +735,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
} else {
TabBarVisibility.update();
}
-@@ -6128,6 +6320,7 @@
+@@ -6192,6 +6386,7 @@
this.tabs[i]._tPos = i;
}
@@ -741,7 +743,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (!this.#windowIsClosing) {
// update tab close buttons state
this.tabContainer._updateCloseButtons();
-@@ -6313,6 +6506,7 @@
+@@ -6377,6 +6572,7 @@
memory_after: await getTotalMemoryUsage(),
time_to_unload_in_ms: timeElapsed,
});
@@ -749,7 +751,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
/**
-@@ -6358,6 +6552,7 @@
+@@ -6422,6 +6618,7 @@
}
let excludeTabs = new Set(aExcludeTabs);
@@ -757,7 +759,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor.
-@@ -6370,13 +6565,13 @@
+@@ -6434,13 +6631,13 @@
!excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) {
@@ -773,7 +775,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
);
if (Services.prefs.getBoolPref("browser.tabs.selectMRUOnClose", false)) {
-@@ -6392,6 +6587,13 @@
+@@ -6456,6 +6653,13 @@
}
}
@@ -787,7 +789,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
let tab = this.tabContainer.findNextTab(aTab, {
direction: 1,
filter: _tab => remainingTabs.includes(_tab),
-@@ -6405,7 +6607,7 @@
+@@ -6469,7 +6673,7 @@
}
if (tab) {
@@ -796,7 +798,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
// If no qualifying visible tab was found, see if there is a tab in
-@@ -6426,7 +6628,7 @@
+@@ -6490,7 +6694,7 @@
});
}
@@ -805,7 +807,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
_blurTab(aTab) {
-@@ -6437,7 +6639,7 @@
+@@ -6501,7 +6705,7 @@
* @returns {boolean}
* False if swapping isn't permitted, true otherwise.
*/
@@ -814,7 +816,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// Do not allow transfering a private tab to a non-private window
// and vice versa.
if (
-@@ -6491,6 +6693,7 @@
+@@ -6555,6 +6759,7 @@
// fire the beforeunload event in the process. Close the other
// window if this was its last tab.
if (
@@ -822,7 +824,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
!remoteBrowser._beginRemoveTab(aOtherTab, {
adoptedByTab: aOurTab,
closeWindowWithLastTab: true,
-@@ -6502,7 +6705,7 @@
+@@ -6566,7 +6771,7 @@
// If this is the last tab of the window, hide the window
// immediately without animation before the docshell swap, to avoid
// about:blank being painted.
@@ -831,7 +833,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (closeWindow) {
let win = aOtherTab.documentGlobal;
win.windowUtils.suppressAnimation(true);
-@@ -6636,11 +6839,13 @@
+@@ -6700,11 +6905,13 @@
}
// Finish tearing down the tab that's going away.
@@ -845,7 +847,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
this.setTabTitle(aOurTab);
-@@ -6860,10 +7065,10 @@
+@@ -6928,10 +7135,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
}
@@ -858,7 +860,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
aTab.selected ||
aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
-@@ -6923,7 +7128,8 @@
+@@ -6991,7 +7198,8 @@
* @param {object} [aOptions={}]
* Key-value pairs that will be serialized into the features string.
*/
@@ -868,7 +870,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (this.tabs.length == 1) {
return null;
}
-@@ -6940,7 +7146,7 @@
+@@ -7008,7 +7216,7 @@
// tell a new window to take the "dropped" tab
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
args.appendElement(aTab.splitview ?? aTab);
@@ -877,7 +879,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
private: PrivateBrowsingUtils.isWindowPrivate(window),
features: Object.entries(aOptions)
.map(([key, value]) => `${key}=${value}`)
-@@ -6948,6 +7154,8 @@
+@@ -7016,6 +7224,8 @@
openerWindow: window,
args,
});
@@ -886,7 +888,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
/**
-@@ -7074,7 +7282,7 @@
+@@ -7142,7 +7352,7 @@
* `true` if element is a ``
*/
isTabGroup(element) {
@@ -895,7 +897,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
/**
-@@ -7146,8 +7354,8 @@
+@@ -7214,8 +7424,8 @@
}
// Don't allow mixing pinned and unpinned tabs.
@@ -906,7 +908,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
} else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
}
-@@ -7193,8 +7401,8 @@
+@@ -7261,8 +7471,8 @@
this.#handleTabMove(
element,
() => {
@@ -917,7 +919,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
neighbor = neighbor.group;
}
if (neighbor?.splitview) {
-@@ -7205,6 +7413,12 @@
+@@ -7273,6 +7483,12 @@
return;
}
}
@@ -930,7 +932,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
if (movingForwards && neighbor) {
neighbor.after(element);
-@@ -7278,23 +7492,31 @@
+@@ -7346,23 +7562,31 @@
) {
if (this.isTabGroupLabel(targetElement)) {
targetElement = targetElement.group;
@@ -968,7 +970,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
} else if (!element.pinned && targetElement && targetElement.pinned) {
// If the caller asks to move an unpinned element next to a pinned
// tab, move the unpinned element to be the first unpinned element
-@@ -7307,12 +7529,35 @@
+@@ -7375,12 +7599,35 @@
// move the tab group right before the first unpinned tab.
// 4. Moving a tab group and the first unpinned tab is grouped:
// move the tab group right before the first unpinned tab's tab group.
@@ -1005,7 +1007,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// We want to include the splitview wrapper if it's the targetElement, but
// not in the case where we want to reverse tabs within the same splitview.
-@@ -7321,6 +7566,7 @@
+@@ -7389,6 +7636,7 @@
}
let getContainer = () =>
@@ -1013,7 +1015,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
element.pinned
? this.tabContainer.pinnedTabsContainer
: this.tabContainer;
-@@ -7329,11 +7575,15 @@
+@@ -7397,11 +7645,15 @@
element,
() => {
if (moveBefore) {
@@ -1030,7 +1032,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
},
{ metricsContext }
-@@ -7411,11 +7661,15 @@
+@@ -7479,11 +7731,15 @@
* The context for the operation for telemetry purposes.
*/
moveTabToExistingGroup(aTab, aGroup, { metricsContext } = {}) {
@@ -1049,7 +1051,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
}
if (aTab.group && aTab.group.id === aGroup.id) {
return;
-@@ -7489,6 +7743,7 @@
+@@ -7557,6 +7813,7 @@
let state = {
tabIndex: tab._tPos,
@@ -1057,7 +1059,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
};
if (tab.visible) {
state.elementIndex = tab.elementIndex;
-@@ -7527,7 +7782,7 @@
+@@ -7595,7 +7852,7 @@
let changedSplitView =
previousTabState.splitViewId != currentTabState.splitViewId;
@@ -1066,7 +1068,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
tab.dispatchEvent(
new CustomEvent("TabMove", {
bubbles: true,
-@@ -7579,6 +7834,10 @@
+@@ -7647,6 +7904,10 @@
moveActionCallback();
@@ -1077,7 +1079,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// Clear tabs cache after moving nodes because the order of tabs may have
// changed.
this.tabContainer._invalidateCachedTabs();
-@@ -7626,7 +7885,22 @@
+@@ -7694,7 +7955,22 @@
* @returns {object}
* The new tab in the current window, null if the tab couldn't be adopted.
*/
@@ -1101,7 +1103,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
// Swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows). We also ensure that the tab we create to swap into has
-@@ -7669,6 +7943,8 @@
+@@ -7737,6 +8013,8 @@
}
params.skipLoad = true;
let newTab = this.addWebTab("about:blank", params);
@@ -1110,7 +1112,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
aTab.container.tabDragAndDrop.finishAnimateTabMove();
-@@ -8448,7 +8724,7 @@
+@@ -8516,7 +8794,7 @@
// preventDefault(). It will still raise the window if appropriate.
return;
}
@@ -1119,7 +1121,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
window.focus();
aEvent.preventDefault();
}
-@@ -8465,7 +8741,6 @@
+@@ -8533,7 +8811,6 @@
on_TabGroupCollapse(aEvent) {
aEvent.target.tabs.forEach(tab => {
@@ -1127,7 +1129,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
});
}
-@@ -8819,7 +9094,9 @@
+@@ -8887,7 +9164,9 @@
let filter = this.#tabFilters.get(tab);
if (filter) {
@@ -1137,7 +1139,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
let listener = this.#tabListeners.get(tab);
if (listener) {
-@@ -9604,6 +9881,7 @@
+@@ -9676,6 +9955,7 @@
aWebProgress.isTopLevel
) {
this._tab.setAttribute("busy", "true");
@@ -1145,7 +1147,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
gBrowser._tabAttrModified(this._tab, ["busy"]);
this._tab._notselectedsinceload = !this._tab.selected;
}
-@@ -9684,6 +9962,7 @@
+@@ -9756,6 +10036,7 @@
// known defaults. Note we use the original URL since about:newtab
// redirects to a prerendered page.
const shouldRemoveFavicon =
@@ -1153,7 +1155,7 @@ index 39b5babee5942427a82a7a64ad69e1b16efebcc1..32beac13dd4215a16de3a783db6c32da
!this._browser.mIconURL &&
!ignoreBlank &&
!(originalLocation.spec in FAVICON_DEFAULTS);
-@@ -9858,13 +10137,6 @@
+@@ -9930,13 +10211,6 @@
this._browser.originalURI = aRequest.originalURI;
}
diff --git a/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch b/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch
index 9644cca42..c99cdbb52 100644
--- a/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch
+++ b/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch
@@ -1,8 +1,8 @@
diff --git a/browser/components/urlbar/UrlbarPrefs.sys.mjs b/browser/components/urlbar/UrlbarPrefs.sys.mjs
-index 89f0d44d462de3d7d0f581d6371606f475443822..0d45ce5f6455e9c7276c54f504906bade5b3d286 100644
+index e48f0295d803ba9eac5ab9d01ebb3b3dacbc9d4b..cb78b74230f63a47479404767b5ab5883278dfba 100644
--- a/browser/components/urlbar/UrlbarPrefs.sys.mjs
+++ b/browser/components/urlbar/UrlbarPrefs.sys.mjs
-@@ -163,6 +163,9 @@ const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([
+@@ -161,6 +161,9 @@ const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([
// Feature gate pref for flight status suggestions in the urlbar.
["flightStatus.featureGate", false],
@@ -12,7 +12,7 @@ index 89f0d44d462de3d7d0f581d6371606f475443822..0d45ce5f6455e9c7276c54f504906bad
// The minimum prefix length of a flight status keyword the user must type to
// trigger the suggestion. 0 means the min length should be taken from Nimbus
// or remote settings.
-@@ -482,6 +485,7 @@ const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([
+@@ -486,6 +489,7 @@ const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([
["shortcuts.tabs", true],
["shortcuts.history", true],
["shortcuts.actions", true],
@@ -20,12 +20,12 @@ index 89f0d44d462de3d7d0f581d6371606f475443822..0d45ce5f6455e9c7276c54f504906bad
// Boolean to determine if the providers defined in `exposureResults`
// should be displayed in search results. This can be set by a
-@@ -840,6 +844,8 @@ function makeDefaultResultGroups({
+@@ -854,6 +858,8 @@ function makeDefaultResultGroups({
*/
let rootGroup = {
children: [
-+ { children: [{ group: lazy.UrlbarUtils.RESULT_GROUP.ZEN_ACTION }] },
-+ { children: [{ group: lazy.UrlbarUtils.RESULT_GROUP.ZEN_WORKSPACE }] },
++ { children: [{ group: lazy.UrlbarShared.RESULT_GROUP.ZEN_ACTION }] },
++ { children: [{ group: lazy.UrlbarShared.RESULT_GROUP.ZEN_WORKSPACE }] },
// heuristic
{
maxResultCount: 1,
diff --git a/src/browser/components/urlbar/UrlbarProviderHeuristicFallback-sys-mjs.patch b/src/browser/components/urlbar/UrlbarProviderHeuristicFallback-sys-mjs.patch
index 82e3f9236..e79eef7e8 100644
--- a/src/browser/components/urlbar/UrlbarProviderHeuristicFallback-sys-mjs.patch
+++ b/src/browser/components/urlbar/UrlbarProviderHeuristicFallback-sys-mjs.patch
@@ -1,8 +1,8 @@
diff --git a/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs b/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs
-index f611d8e44af518aa1adc7505c5021235f55b49de..0eca20cb869626dfa6ace02acc00cad84462d54f 100644
+index cfc232f038cd491db32e0c0ca35622360dd9dec5..4806a725ddefa9d0709020d537eae5464249a294 100644
--- a/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs
+++ b/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs
-@@ -78,22 +78,26 @@ export class UrlbarProviderHeuristicFallback extends UrlbarProvider {
+@@ -77,22 +77,26 @@ export class UrlbarProviderHeuristicFallback extends UrlbarProvider {
// Since we can't tell if this is a real URL and whether the user wants
// to visit or search for it, we provide an alternative searchengine
// match if the string looks like an alphanumeric origin or an e-mail.
@@ -17,7 +17,7 @@ index f611d8e44af518aa1adc7505c5021235f55b49de..0eca20cb869626dfa6ace02acc00cad8
- lazy.UrlUtils.REGEXP_COMMON_EMAIL.test(str))
- ) {
+ let trimmedSearchString = queryContext.trimmedSearchString;
-+ let [searchCandidate] = UrlbarUtils.stripPrefixAndTrim(
++ let [searchCandidate] = lazy.UrlbarShared.stripPrefixAndTrim(
+ trimmedSearchString,
+ {
+ trimSlash: true,
diff --git a/src/browser/components/urlbar/UrlbarUtils-sys-mjs.patch b/src/browser/components/urlbar/UrlbarUtils-sys-mjs.patch
index 630553ff9..b3833b83b 100644
--- a/src/browser/components/urlbar/UrlbarUtils-sys-mjs.patch
+++ b/src/browser/components/urlbar/UrlbarUtils-sys-mjs.patch
@@ -1,41 +1,16 @@
diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs
-index 78c76bde00b6b581184cee1cd0e1756e5f2da6a4..94c713470b1240729ef2350d090b0449f3801af4 100644
+index 178cd5e84fda74a28b2de59b671b3399cc28fac4..b03c6e06de4ee9a95dcb4d33d7ac9f0b053be4e1 100644
--- a/browser/components/urlbar/UrlbarUtils.sys.mjs
+++ b/browser/components/urlbar/UrlbarUtils.sys.mjs
-@@ -126,6 +126,8 @@ export var UrlbarUtils = {
- SEMANTIC_HISTORY: "semanticHistory",
- SUGGESTED_INDEX: "suggestedIndex",
- TAIL_SUGGESTION: "tailSuggestion",
-+ ZEN_ACTION: "zenAction",
-+ ZEN_WORKSPACE: "zenWorkspace",
- }),
-
- // Defines provider types.
-@@ -274,6 +276,14 @@ export var UrlbarUtils = {
- telemetryLabel: "actions",
- uiLabel: "urlbar-searchmode-actions3",
- },
-+ {
-+ source: UrlbarShared.RESULT_SOURCE.WORKSPACES,
-+ restrict: UrlbarShared.RESTRICT_TOKENS.WORKSPACE,
-+ icon: "chrome://browser/skin/zen-icons/selectable/layers.svg",
-+ pref: "shortcuts.workspaces",
-+ telemetryLabel: "workspaces",
-+ uiLabel: "urlbar-search-mode-workspaces",
-+ },
- ]);
- },
-
-@@ -566,6 +576,12 @@ export var UrlbarUtils = {
- return this.RESULT_GROUP.HEURISTIC_FALLBACK;
+@@ -214,6 +214,11 @@ export var UrlbarUtils = {
+ return UrlbarShared.RESULT_GROUP.HEURISTIC_FALLBACK;
case "UrlbarProviderHistoryUrlHeuristic":
- return this.RESULT_GROUP.HEURISTIC_HISTORY_URL;
+ return UrlbarShared.RESULT_GROUP.HEURISTIC_HISTORY_URL;
+ case "ZenUrlbarProviderGlobalActions":
+ if (result.source == UrlbarShared.RESULT_SOURCE.WORKSPACES) {
-+ return this.RESULT_GROUP.ZEN_WORKSPACE;
-+ } else {
-+ return this.RESULT_GROUP.ZEN_ACTION;
++ return UrlbarShared.RESULT_GROUP.ZEN_WORKSPACE;
+ }
++ return UrlbarShared.RESULT_GROUP.ZEN_ACTION;
case "UrlbarProviderOmnibox":
- return this.RESULT_GROUP.HEURISTIC_OMNIBOX;
+ return UrlbarShared.RESULT_GROUP.HEURISTIC_OMNIBOX;
case "UrlbarProviderRestrictKeywordsAutofill":
diff --git a/src/browser/components/urlbar/content/UrlbarChildController-mjs.patch b/src/browser/components/urlbar/content/UrlbarChildController-mjs.patch
index a7751f7ca..cd89c299e 100644
--- a/src/browser/components/urlbar/content/UrlbarChildController-mjs.patch
+++ b/src/browser/components/urlbar/content/UrlbarChildController-mjs.patch
@@ -1,8 +1,8 @@
diff --git a/browser/components/urlbar/content/UrlbarChildController.mjs b/browser/components/urlbar/content/UrlbarChildController.mjs
-index a8e9d3093d046612650a17970330a8279f55833f..063a3e8adf009d6c0ddc60430bc0e7485e812b8a 100644
+index a17a4de079b69d2818388b33a2c1746cc6148c73..4fee63a22919972af08716db2d2de1c7549ff100 100644
--- a/browser/components/urlbar/content/UrlbarChildController.mjs
+++ b/browser/components/urlbar/content/UrlbarChildController.mjs
-@@ -258,7 +258,6 @@ export class UrlbarChildController {
+@@ -438,7 +438,6 @@ export class UrlbarChildController {
const isMac = AppConstants.platform == "macosx";
// Handle readline/emacs-style navigation bindings on Mac.
if (
@@ -10,7 +10,7 @@ index a8e9d3093d046612650a17970330a8279f55833f..063a3e8adf009d6c0ddc60430bc0e748
this.view.isOpen &&
event.ctrlKey &&
(event.key == "n" || event.key == "p")
-@@ -450,6 +449,8 @@ export class UrlbarChildController {
+@@ -630,6 +629,8 @@ export class UrlbarChildController {
});
}
event.preventDefault();
@@ -19,3 +19,25 @@ index a8e9d3093d046612650a17970330a8279f55833f..063a3e8adf009d6c0ddc60430bc0e748
}
break;
}
+@@ -934,6 +935,10 @@ export class UrlbarChildController {
+ }
+ reuseEmpty = true;
+ }
++ if (this.#input.hasAttribute?.("zen-newtab")) {
++ where = "tab";
++ reuseEmpty = true;
++ }
+ // The browser window exists only in chrome; a content-process input has no
+ // tab to reuse, so `gBrowser` is absent and the reuse is skipped.
+ if (
+@@ -943,6 +948,10 @@ export class UrlbarChildController {
+ ) {
+ where = "current";
+ }
++ if (this.window.gBrowser?.selectedTab.hasAttribute("zen-empty-tab")) {
++ // Always open in a new tab if the current tab is our empty tab.
++ return "tab";
++ }
+ return where;
+ }
+
diff --git a/src/browser/components/urlbar/content/UrlbarInput-mjs.patch b/src/browser/components/urlbar/content/UrlbarInput-mjs.patch
index b6b5daebe..535cd52bf 100644
--- a/src/browser/components/urlbar/content/UrlbarInput-mjs.patch
+++ b/src/browser/components/urlbar/content/UrlbarInput-mjs.patch
@@ -1,8 +1,8 @@
diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs
-index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c396406a247e9 100644
+index 319fb6870ec18d87b9f1263bd960edaa39b0447c..554cc90fb2307450ddc68033968cefd1a95e28b8 100644
--- a/browser/components/urlbar/content/UrlbarInput.mjs
+++ b/browser/components/urlbar/content/UrlbarInput.mjs
-@@ -101,6 +101,13 @@ const lazy = XPCOMUtils.declareLazy({
+@@ -88,6 +88,13 @@ const lazy = XPCOMUtils.declareLazy({
logger: () => UrlbarShared.getLogger({ prefix: "Input" }),
});
@@ -16,7 +16,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
const UNLIMITED_MAX_RESULTS = 99;
let getBoundsWithoutFlushing = element =>
-@@ -769,7 +776,16 @@ ${
+@@ -771,7 +778,16 @@ ${
// See _on_select(). HTMLInputElement.select() dispatches a "select"
// event but does not set the primary selection.
this._suppressPrimaryAdjustment = true;
@@ -33,7 +33,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
this._suppressPrimaryAdjustment = false;
}
-@@ -843,6 +859,10 @@ ${
+@@ -846,6 +862,10 @@ ${
hideSearchTerms = false,
isSameDocument = false,
} = {}) {
@@ -44,7 +44,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
if (!this.#isAddressbar) {
throw new Error(
"Cannot set URI for UrlbarInput that is not an address bar"
-@@ -1137,7 +1157,16 @@ ${
+@@ -1143,7 +1163,16 @@ ${
this.searchModeSwitcher?.updateSearchIcon();
}
@@ -61,7 +61,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
}
/**
-@@ -1626,7 +1655,11 @@ ${
+@@ -1726,7 +1755,11 @@ ${
openParams.avoidBrowserFocus = keepViewOpen;
if (!this.#providesSearchMode(result) && !keepViewOpen) {
@@ -74,7 +74,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
}
if (isCanonized) {
-@@ -2974,6 +3007,42 @@ ${
+@@ -3016,6 +3049,42 @@ ${
await this.#updateLayoutBreakoutDimensions();
}
@@ -117,7 +117,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
startLayoutExtend() {
if (!this.#allowBreakout || this.hasAttribute("breakout-extend")) {
// Do not expand if the Urlbar does not support being expanded or it is
-@@ -2988,6 +3057,13 @@ ${
+@@ -3030,6 +3099,13 @@ ${
this.toggleAttribute("breakout-extend", true);
this.#updateTextboxPosition();
@@ -131,7 +131,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
// Enable the animation only after the first extend call to ensure it
// doesn't run when opening a new window.
if (!this.hasAttribute("breakout-extend-animate")) {
-@@ -3011,6 +3087,29 @@ ${
+@@ -3053,6 +3129,29 @@ ${
return;
}
@@ -161,7 +161,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
this.toggleAttribute("breakout-extend", false);
this.#updateTextboxPosition();
}
-@@ -3049,7 +3148,7 @@ ${
+@@ -3091,7 +3190,7 @@ ${
forceUnifiedSearchButtonAvailable = false
) {
let prevState = this.getAttribute("pageproxystate");
@@ -170,7 +170,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
this.setAttribute("pageproxystate", state);
this._inputContainer.setAttribute("pageproxystate", state);
this._identityBox?.setAttribute("pageproxystate", state);
-@@ -3332,10 +3431,12 @@ ${
+@@ -3355,10 +3454,12 @@ ${
}
this.style.top = px(
@@ -183,7 +183,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
);
}
-@@ -3394,9 +3495,10 @@ ${
+@@ -3417,9 +3518,10 @@ ${
return;
}
@@ -193,9 +193,9 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
- px(getBoundsWithoutFlushing(this.parentNode).height)
+ px(getBoundsWithoutFlushing(this.parentNode).height + 8)
);
- this.style.setProperty(
- "--urlbar-height",
-@@ -3902,6 +4004,7 @@ ${
+
+ if (this.#breakoutBlockerCount) {
+@@ -3929,6 +4031,7 @@ ${
}
_toggleActionOverride(event) {
@@ -203,47 +203,33 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
if (
event.keyCode == KeyEvent.DOM_VK_SHIFT ||
event.keyCode == KeyEvent.DOM_VK_ALT ||
-@@ -4014,8 +4117,8 @@ ${
+@@ -4027,9 +4130,10 @@ ${
if (!this.#isAddressbar) {
return val;
}
-- let trimmedValue = lazy.UrlbarPrefs.get("trimURLs")
+- let trimmedValue = UrlbarPrefs.get("trimURLs")
- ? lazy.BrowserUIUtils.trimURL(val)
-+ let trimmedValue = lazy.UrlbarPrefs.get("trimURLs") && this._zenTrimURL
-+ ? this._zenTrimURL(val)
- : val;
+- : val;
++ let trimmedValue =
++ UrlbarPrefs.get("trimURLs") && this._zenTrimURL
++ ? this._zenTrimURL(val)
++ : val;
// Only trim value if the directionality doesn't change to RTL and we're not
// showing a strikeout https protocol.
-@@ -4317,6 +4420,7 @@ ${
- browser = this.window.gBrowser.selectedBrowser,
- keepViewOpen = false
- ) {
-+ openUILinkWhere = this.window.gZenUIManager.getOpenUILinkWhere(url, browser, openUILinkWhere);
- if (this.#isAddressbar) {
- this.#prepareAddressbarLoad(
- url,
-@@ -4430,6 +4534,10 @@ ${
- }
- reuseEmpty = true;
- }
-+ if (this.hasAttribute("zen-newtab")) {
-+ where = "tab";
-+ reuseEmpty = true;
-+ }
- if (
- where == "tab" &&
- reuseEmpty &&
-@@ -4437,6 +4545,9 @@ ${
- ) {
- where = "current";
- }
-+ if (this.window.gBrowser.selectedTab.hasAttribute("zen-empty-tab")) {
-+ return "tab"; // Always open in a new tab if the current tab is "our empty tab".
-+ }
- return where;
- }
-
-@@ -4691,6 +4802,7 @@ ${
+ return this.controller.isTextDirectionRTL(trimmedValue) ||
+@@ -4235,6 +4339,11 @@ ${
+ keepViewOpen = false,
+ browserId = null,
+ }) {
++ where = this.window.gZenUIManager.getOpenUILinkWhere(
++ url,
++ this.window.gBrowser.selectedBrowser,
++ where
++ );
+ let userTypedValue;
+ if (this.#isAddressbar && where == "current") {
+ // Make sure URL is formatted properly (don't show punycode).
+@@ -4576,6 +4685,7 @@ ${
this.setResultForCurrentValue(null);
this.handleCommand();
this.controller.clearLastQueryContextCache();
@@ -251,7 +237,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
this._suppressStartQuery = false;
});
-@@ -4698,7 +4810,6 @@ ${
+@@ -4583,7 +4693,6 @@ ${
contextMenu.addEventListener("popupshowing", () => {
// Close the results pane when the input field contextual menu is open,
// because paste and go doesn't want a result selection.
@@ -259,7 +245,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
let controller =
this.document.commandDispatcher.getControllerForCommand("cmd_paste");
-@@ -4979,7 +5090,11 @@ ${
+@@ -4850,7 +4959,11 @@ ${
if (!engineName && !source && !this.hasAttribute("searchmode")) {
return;
}
@@ -272,7 +258,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
if (this._searchModeIndicatorTitle) {
this._searchModeIndicatorTitle.textContent = "";
this._searchModeIndicatorTitle.removeAttribute("data-l10n-id");
-@@ -5297,6 +5412,7 @@ ${
+@@ -5149,6 +5262,7 @@ ${
this.document.l10n.setAttributes(
this.inputField,
@@ -280,20 +266,20 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
l10nId,
l10nId == "urlbar-placeholder-with-name"
? { name: engineName }
-@@ -5347,6 +5463,12 @@ ${
+@@ -5199,6 +5313,12 @@ ${
}
lazy.logger.debug("Blur Event");
+ if (
+ this.document.commandDispatcher.focusedElement == this.inputField &&
-+ !lazy.UrlbarPrefs.get("closeOnWindowBlur")
++ !UrlbarPrefs.get("closeOnWindowBlur")
+ ) {
+ return;
+ }
// We cannot count every blur events after a missed engagement as abandoment
// because the user may have clicked on some view element that executes
// a command causing a focus change. For example opening preferences from
-@@ -5424,6 +5546,11 @@ ${
+@@ -5276,6 +5396,11 @@ ${
}
_on_click(event) {
@@ -305,7 +291,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
switch (event.target) {
case this.inputField:
case this._inputContainer:
-@@ -5513,10 +5640,11 @@ ${
+@@ -5371,10 +5496,11 @@ ${
}
if (untrim) {
this.setValue(this._untrimmedValue);
@@ -318,7 +304,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
this.view.autoOpen({ event });
} else {
if (this._untrimOnFocusAfterKeydown) {
-@@ -5556,9 +5684,16 @@ ${
+@@ -5414,9 +5540,16 @@ ${
}
_on_mousedown(event) {
@@ -336,7 +322,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
if (
event.composedTarget != this.inputField &&
event.composedTarget != this._inputContainer
-@@ -5568,6 +5703,10 @@ ${
+@@ -5426,6 +5559,10 @@ ${
this.focusedViaMousedown = !this.focused;
this.#preventClickSelectsAll = this.focused;
@@ -347,7 +333,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
// Keep the focus status, since the attribute may be changed
// upon calling this.focus().
-@@ -5605,7 +5744,7 @@ ${
+@@ -5463,7 +5600,7 @@ ${
// view open on tab switch, and the TabSelect event arrived earlier.
// Also ignore mousedown on the urlbarView context menu: opening/closing the
// view is already handled by the result opening flow.
@@ -356,12 +342,12 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c3964
break;
}
-@@ -5894,7 +6033,7 @@ ${
+@@ -5754,7 +5891,7 @@ ${
// When we are in actions search mode we can show more results so
// increase the limit.
let maxResults =
- this.searchMode?.source != UrlbarShared.RESULT_SOURCE.ACTIONS
+ this.searchMode?.source != UrlbarShared.RESULT_SOURCE.ZEN_ACTIONS
- ? lazy.UrlbarPrefs.get("maxRichResults")
+ ? UrlbarPrefs.get("maxRichResults")
: UNLIMITED_MAX_RESULTS;
let options = {
diff --git a/src/browser/components/urlbar/content/UrlbarShared-mjs.patch b/src/browser/components/urlbar/content/UrlbarShared-mjs.patch
index 6a676136c..a36136808 100644
--- a/src/browser/components/urlbar/content/UrlbarShared-mjs.patch
+++ b/src/browser/components/urlbar/content/UrlbarShared-mjs.patch
@@ -1,8 +1,8 @@
diff --git a/browser/components/urlbar/content/UrlbarShared.mjs b/browser/components/urlbar/content/UrlbarShared.mjs
-index 5b45afb747bfcdbd06782186737669aa2c4c634d..f68bd009f70415b5fb513abdfe19b4427ceefbfc 100644
+index 4e67dad66ae006842abc6eed547e4a244320d57a..393b61b4d2543c73e60afd334a353f36119ae73e 100644
--- a/browser/components/urlbar/content/UrlbarShared.mjs
+++ b/browser/components/urlbar/content/UrlbarShared.mjs
-@@ -47,6 +47,7 @@ export const UrlbarShared = {
+@@ -100,6 +100,7 @@ export const UrlbarShared = {
// `looksLikeOrigin()` returned `LOOKS_LIKE_ORIGIN.OTHER` for this token.
// It may or may not be an origin.
POSSIBLE_ORIGIN_BUT_SEARCH_ALLOWED: 12,
@@ -10,7 +10,7 @@ index 5b45afb747bfcdbd06782186737669aa2c4c634d..f68bd009f70415b5fb513abdfe19b442
}),
/**
-@@ -67,6 +68,7 @@ export const UrlbarShared = {
+@@ -120,6 +121,7 @@ export const UrlbarShared = {
TITLE: "#",
URL: "$",
ACTION: ">",
@@ -18,7 +18,7 @@ index 5b45afb747bfcdbd06782186737669aa2c4c634d..f68bd009f70415b5fb513abdfe19b442
}),
// Defines UrlbarResult types.
-@@ -112,6 +114,8 @@ export const UrlbarShared = {
+@@ -165,6 +167,8 @@ export const UrlbarShared = {
OTHER_NETWORK: 6,
ADDON: 7,
ACTIONS: 8,
@@ -26,9 +26,33 @@ index 5b45afb747bfcdbd06782186737669aa2c4c634d..f68bd009f70415b5fb513abdfe19b442
+ WORKSPACES: 10,
}),
- /**
-@@ -128,6 +132,7 @@ export const UrlbarShared = {
- if (lazy.UrlbarPrefs.get("scotchBonnet.enableOverride")) {
+ // Results are categorized into groups to help the muxer compose them. See
+@@ -200,6 +204,8 @@ export const UrlbarShared = {
+ SEMANTIC_HISTORY: "semanticHistory",
+ SUGGESTED_INDEX: "suggestedIndex",
+ TAIL_SUGGESTION: "tailSuggestion",
++ ZEN_ACTION: "zenAction",
++ ZEN_WORKSPACE: "zenWorkspace",
+ }),
+
+ // Defines provider types.
+@@ -385,6 +391,14 @@ export const UrlbarShared = {
+ telemetryLabel: "actions",
+ uiLabel: "urlbar-searchmode-actions3",
+ },
++ {
++ source: this.RESULT_SOURCE.WORKSPACES,
++ restrict: this.RESTRICT_TOKENS.WORKSPACE,
++ icon: "chrome://browser/skin/zen-icons/selectable/layers.svg",
++ pref: "shortcuts.workspaces",
++ telemetryLabel: "workspaces",
++ uiLabel: "urlbar-search-mode-workspaces",
++ },
+ ]);
+ },
+
+@@ -402,6 +416,7 @@ export const UrlbarShared = {
+ if (UrlbarPrefs.get("scotchBonnet.enableOverride")) {
keys.push(this.RESTRICT_TOKENS.ACTION);
}
+ keys.push(this.RESTRICT_TOKENS.WORKSPACE);
diff --git a/src/browser/components/urlbar/content/UrlbarView-mjs.patch b/src/browser/components/urlbar/content/UrlbarView-mjs.patch
index 7225a807e..1ef7cf0a8 100644
--- a/src/browser/components/urlbar/content/UrlbarView-mjs.patch
+++ b/src/browser/components/urlbar/content/UrlbarView-mjs.patch
@@ -1,8 +1,8 @@
diff --git a/browser/components/urlbar/content/UrlbarView.mjs b/browser/components/urlbar/content/UrlbarView.mjs
-index e465806c4ae4fc372d9aa2b71aa7f0e74c65d026..c0b3781901298bedee2736819a93edbf545507a7 100644
+index 9a2c787cfa4d70bd587d49e017075f905c90f626..1c3b341e5d520ec9541c4021aaedce98751b6993 100644
--- a/browser/components/urlbar/content/UrlbarView.mjs
+++ b/browser/components/urlbar/content/UrlbarView.mjs
-@@ -803,7 +803,7 @@ export class UrlbarView {
+@@ -822,7 +822,7 @@ export class UrlbarView {
!this.input.value ||
this.input.getAttribute("pageproxystate") == "valid"
) {
@@ -11,7 +11,7 @@ index e465806c4ae4fc372d9aa2b71aa7f0e74c65d026..c0b3781901298bedee2736819a93edbf
// Try to reuse the cached top-sites context. If it's not cached, then
// there will be a gap of time between when the input is focused and
// when the view opens that can be perceived as flicker.
-@@ -941,10 +941,6 @@ export class UrlbarView {
+@@ -963,10 +963,6 @@ export class UrlbarView {
}
// If search mode isn't active, close the view.
@@ -22,7 +22,7 @@ index e465806c4ae4fc372d9aa2b71aa7f0e74c65d026..c0b3781901298bedee2736819a93edbf
// Search mode is active. If the one-offs should be shown, make sure they
// are enabled and show the view.
-@@ -3203,6 +3199,8 @@ export class UrlbarView {
+@@ -3306,6 +3302,8 @@ export class UrlbarView {
if (row?.hasAttribute("row-selectable")) {
row?.toggleAttribute("selected", true);
}
@@ -31,7 +31,7 @@ index e465806c4ae4fc372d9aa2b71aa7f0e74c65d026..c0b3781901298bedee2736819a93edbf
if (element != row) {
row?.toggleAttribute("descendant-selected", true);
}
-@@ -3715,7 +3713,7 @@ export class UrlbarView {
+@@ -3972,7 +3970,7 @@ export class UrlbarView {
}
#enableOrDisableRowWrap() {
@@ -40,13 +40,13 @@ index e465806c4ae4fc372d9aa2b71aa7f0e74c65d026..c0b3781901298bedee2736819a93edbf
this.#rows.toggleAttribute("wrap", wrap);
this.oneOffSearchButtons?.container.toggleAttribute("wrap", wrap);
}
-@@ -4144,6 +4142,12 @@ export class UrlbarView {
+@@ -4387,6 +4385,12 @@ export class UrlbarView {
}
on_blur() {
+ if (
+ this.document.commandDispatcher.focusedElement == this.input.inputField &&
-+ !lazy.UrlbarPrefs.get("closeOnWindowBlur")
++ !UrlbarPrefs.get("closeOnWindowBlur")
+ ) {
+ return;
+ }
diff --git a/src/browser/themes/linux/browser-css.patch b/src/browser/themes/linux/browser-css.patch
index 4331385b9..3079f7f20 100644
--- a/src/browser/themes/linux/browser-css.patch
+++ b/src/browser/themes/linux/browser-css.patch
@@ -1,11 +1,11 @@
diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css
-index e735ec80b56e1a2da4bc5bec29136ce60353277b..330ea62d220d9acd96a95a605888839388e97452 100644
+index f276db1d456703493ee713370691352985021708..619ee6724eec1e6a25adcb257af0d02eb7a29d34 100644
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -14,7 +14,6 @@
@media (-moz-gtk-theme-family) {
--tabs-navbar-separator-style: none;
- @media (prefers-color-scheme: light) and (not -moz-pref("browser.nova.enabled")) {
+ @media (prefers-color-scheme: light) {
- --urlbar-box-background-color: #fafafa;
}
}
diff --git a/src/browser/themes/shared/tabbrowser/content-area-css.patch b/src/browser/themes/shared/tabbrowser/content-area-css.patch
index 678dc33ec..d8db6cb91 100644
--- a/src/browser/themes/shared/tabbrowser/content-area-css.patch
+++ b/src/browser/themes/shared/tabbrowser/content-area-css.patch
@@ -1,16 +1,16 @@
diff --git a/browser/themes/shared/tabbrowser/content-area.css b/browser/themes/shared/tabbrowser/content-area.css
-index 3acdec2b8e043fe27919cfa54e73f4d3c491bfc8..267a1087134464a8193a824a2d7f8ac15ff8ad60 100644
+index 2374ed38bd9361d6c7e1886117099c6761e403b8..72f1f52a13096f19ca4009037ad0cdc880775ff7 100644
--- a/browser/themes/shared/tabbrowser/content-area.css
+++ b/browser/themes/shared/tabbrowser/content-area.css
-@@ -157,7 +157,6 @@
- min-height: 0;
+@@ -239,7 +239,6 @@
+ min-width: 0;
/* We want to be able to show the frame color behind the clipped radiused corner */
- background: var(--tabpanel-background-color);
- @media -moz-pref("sidebar.revamp") {
- outline: 0.01px solid var(--chrome-content-separator-color);
-@@ -216,7 +215,6 @@
+ display: grid;
+ /**
+@@ -425,7 +424,6 @@
}
browser:is([blank], [pendingpaint]) {
@@ -18,7 +18,7 @@ index 3acdec2b8e043fe27919cfa54e73f4d3c491bfc8..267a1087134464a8193a824a2d7f8ac1
}
/* Exclude browsers with smartwindow-content attribute which inherit
-@@ -553,7 +551,7 @@ split-view-footer {
+@@ -865,7 +863,7 @@ split-view-footer {
.dialogStack {
z-index: var(--browser-stack-z-index-dialog-stack);
@@ -27,12 +27,12 @@ index 3acdec2b8e043fe27919cfa54e73f4d3c491bfc8..267a1087134464a8193a824a2d7f8ac1
inset: 0;
/* --browser-with-dialog set on browser[tabDialogShowing], we want to position the overlay
only on the top of the element so it doesn't overlap the DevTools toolbox */
-@@ -718,7 +716,7 @@ split-view-footer {
+@@ -1034,7 +1032,7 @@ split-view-footer {
.dialogOverlay[topmost="true"],
#window-modal-dialog::backdrop {
- background-color: var(--background-color-overlay);
+ background-color: light-dark(rgba(255, 255, 255, .3), rgba(0, 0, 0, .3));
- }
+ border-radius: var(--chrome-block-radius);
- .dialogOverlay[hideContent="true"][topmost="true"] {
+ .browserSidebarContainer.responsive-mode & {
diff --git a/src/browser/themes/shared/tabbrowser/tabs-css.patch b/src/browser/themes/shared/tabbrowser/tabs-css.patch
index 28160af66..810751b0b 100644
--- a/src/browser/themes/shared/tabbrowser/tabs-css.patch
+++ b/src/browser/themes/shared/tabbrowser/tabs-css.patch
@@ -1,17 +1,17 @@
diff --git a/browser/themes/shared/tabbrowser/tabs.css b/browser/themes/shared/tabbrowser/tabs.css
-index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f253804576e 100644
+index 61c0b8382c78f63ad9113af55a6236c4a29bc45b..0ab463c768d4fb3f45d22e753f408c46b61c0480 100644
--- a/browser/themes/shared/tabbrowser/tabs.css
+++ b/browser/themes/shared/tabbrowser/tabs.css
-@@ -45,7 +45,7 @@
+@@ -46,7 +46,7 @@
--tab-group-line-thickness: 2px;
--tab-group-line-toolbar-border-distance: 1px;
/* Collapsed tabs should be square, so set width to match the min height */
- --tab-collapsed-background-width: var(--tab-min-height);
+ --tab-collapsed-background-width: 48px;
--tab-collapsed-width: calc(var(--tab-collapsed-background-width) + 2 * var(--tab-inner-inline-margin));
+ --tab-collapsed-split-pill-offset: 0px;
--tab-pinned-min-width-expanded: calc(var(--tab-pinned-expanded-background-width) + 2 * var(--tab-pinned-margin-inline-expanded));
- --tab-note-icon-end-margin: var(--dimension-4);
-@@ -295,7 +295,6 @@ tab-split-view-wrapper[dragtarget] {
+@@ -308,7 +308,6 @@ tab-split-view-wrapper[dragtarget] {
}
:root:not([uidensity="compact"], [sidebar-expand-on-hover]) &[pinned] {
@@ -19,7 +19,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
}
&:is([selected], [multiselected]) {
-@@ -309,6 +308,7 @@ tab-split-view-wrapper[dragtarget] {
+@@ -322,6 +321,7 @@ tab-split-view-wrapper[dragtarget] {
border-radius: inherit;
position: relative;
overflow: hidden;
@@ -27,7 +27,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
&::before {
position: absolute;
-@@ -506,10 +506,6 @@ tab-split-view-wrapper[dragtarget] {
+@@ -519,10 +519,6 @@ tab-split-view-wrapper[dragtarget] {
@media -moz-pref("browser.tabs.fadeOutUnloadedTabs") {
&[pending] {
@@ -38,7 +38,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
opacity: 0.5;
/* Fade the favicon out */
transition-property: filter, opacity;
-@@ -525,10 +521,6 @@ tab-split-view-wrapper[dragtarget] {
+@@ -538,10 +534,6 @@ tab-split-view-wrapper[dragtarget] {
@media -moz-pref("browser.tabs.fadeOutExplicitlyUnloadedTabs") {
&[pending][discarded] {
@@ -49,7 +49,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
opacity: 0.5;
/* Fade the favicon out */
transition-property: filter, opacity;
-@@ -602,7 +594,7 @@ tab-split-view-wrapper[dragtarget] {
+@@ -615,7 +607,7 @@ tab-split-view-wrapper[dragtarget] {
}
#tabbrowser-tabs[orient="vertical"] & {
@@ -58,7 +58,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
}
&[crashed] {
-@@ -610,7 +602,7 @@ tab-split-view-wrapper[dragtarget] {
+@@ -623,7 +615,7 @@ tab-split-view-wrapper[dragtarget] {
}
#tabbrowser-tabs[orient="vertical"]:not([expanded]) &:not([crashed]),
@@ -67,7 +67,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
&[soundplaying] {
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-playing-small.svg");
}
-@@ -664,7 +656,7 @@ tab-split-view-wrapper[dragtarget] {
+@@ -677,7 +669,7 @@ tab-split-view-wrapper[dragtarget] {
}
}
@@ -76,7 +76,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
&[crashed] {
display: revert;
}
-@@ -824,7 +816,7 @@ tab-split-view-wrapper[dragtarget] {
+@@ -837,7 +829,7 @@ tab-split-view-wrapper[dragtarget] {
has not been added to root. There are certain scenarios when that attribute is temporarily
removed from root such as when toggling the sidebar to expand with the toolbar button. */
#tabbrowser-tabs[orient="horizontal"] &:not([pinned]):not([crashed]),
@@ -85,7 +85,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
&:is([soundplaying], [muted], [activemedia-blocked]) {
display: flex;
}
-@@ -1044,7 +1036,6 @@ tab-split-view-wrapper[dragtarget] {
+@@ -1151,7 +1143,6 @@ tab-split-view-wrapper[dragtarget] {
.tabbrowser-tab:is([image], [pinned]) > .tab-stack > .tab-content[attention]:not([selected]),
.tabbrowser-tab > .tab-stack > .tab-content[pinned][titlechanged]:not([selected]),
#tabbrowser-tabs[orient="vertical"] .tabbrowser-tab > .tab-stack > .tab-content[titlechanged]:not([selected]) {
@@ -93,7 +93,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
background-position: center bottom 6.5px;
background-size: 4px 4px;
background-repeat: no-repeat;
-@@ -1797,7 +1788,7 @@ tab-group {
+@@ -1904,7 +1895,7 @@ tab-group {
}
#tabbrowser-tabs[orient="vertical"][expanded] {
@@ -102,7 +102,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
&[movingtab][movingtab-addToGroup]:not([movingtab-group], [movingtab-ungroup]) .tabbrowser-tab:is(:active, [multiselected]) {
margin-inline-start: var(--space-medium);
}
-@@ -2351,7 +2342,7 @@ tab-group {
+@@ -2504,7 +2495,7 @@ tab-group {
}
}
@@ -111,8 +111,8 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
#vertical-tabs-newtab-button {
appearance: none;
min-height: var(--tab-min-height);
-@@ -2362,7 +2353,7 @@ tab-group {
- margin-inline: var(--tab-inner-inline-margin);
+@@ -2524,7 +2515,7 @@ tab-group {
+ }
#tabbrowser-tabs[orient="vertical"]:not([expanded]) & > .toolbarbutton-text {
- display: none;
@@ -120,7 +120,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
}
&:hover {
-@@ -2386,7 +2377,7 @@ tab-group {
+@@ -2548,7 +2539,7 @@ tab-group {
* flex container. #tabs-newtab-button is a child of the arrowscrollbox where
* we don't want a gap (between tabs), so we have to add some margin.
*/
@@ -129,7 +129,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
margin-block: var(--tab-margin-block);
}
-@@ -2576,7 +2567,6 @@ tab-group {
+@@ -2740,7 +2731,6 @@ tab-group {
&:not([expanded]) {
.tabbrowser-tab[pinned] {
@@ -137,7 +137,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
}
.tab-background {
-@@ -2615,8 +2605,8 @@ tab-group {
+@@ -2779,8 +2769,8 @@ tab-group {
display: block;
position: absolute;
inset: auto;
@@ -148,7 +148,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
&:-moz-window-inactive {
background-image:
-@@ -2714,9 +2704,6 @@ tab-group {
+@@ -2878,9 +2868,6 @@ tab-group {
~ #tabbrowser-tabs[orient="horizontal"]::before {
display: flex;
content: "";
@@ -158,7 +158,7 @@ index 0820fa040642ee05c2a24f5d2cfa58da5c926647..2d93abcce1729744e5963f1c6ad47f25
}
}
-@@ -2749,7 +2736,6 @@ toolbar:not(#TabsToolbar) #firefox-view-button {
+@@ -2913,7 +2900,6 @@ toolbar:not(#TabsToolbar) #firefox-view-button {
list-style-image: url(chrome://global/skin/icons/plus.svg);
}
diff --git a/src/browser/themes/shared/urlbar-searchbar-css.patch b/src/browser/themes/shared/urlbar-searchbar-css.patch
index fdc1f148d..74b873f46 100644
--- a/src/browser/themes/shared/urlbar-searchbar-css.patch
+++ b/src/browser/themes/shared/urlbar-searchbar-css.patch
@@ -1,19 +1,19 @@
diff --git a/browser/themes/shared/urlbar-searchbar.css b/browser/themes/shared/urlbar-searchbar.css
-index 22d624d718a289dd7b84ffc2f04dd35893dfbe95..4f57f7853f3043e3ad01d893aa928b9d2e6f3c16 100644
+index 149f123e26cd79e89432dfeb504ca7218ee04810..7e0e6d981d6c2aa53bc2a76d82d2078419659065 100644
--- a/browser/themes/shared/urlbar-searchbar.css
+++ b/browser/themes/shared/urlbar-searchbar.css
@@ -19,8 +19,8 @@
@media not -moz-pref("browser.nova.enabled") {
- --urlbar-basic-component-size: calc(var(--urlbar-min-height) - 2 * (var(--urlbar-input-container-border-width) + var(--urlbar-input-container-padding)));
-- --urlbar-icon-padding: calc((var(--urlbar-min-height) - 2px /* border */ - 2px /* padding */ - 16px /* icon */) / 2);
+ --urlbar-basic-component-size: calc(var(--urlbar-height) - 2 * (var(--urlbar-input-container-border-width) + var(--urlbar-input-container-padding)));
+- --urlbar-icon-padding: calc((var(--urlbar-height) - 2px /* border */ - 2px /* padding */ - 16px /* icon */) / 2);
- --urlbar-input-container-padding: round(up, 1px, env(hairline));
-+ --urlbar-icon-padding: calc((var(--urlbar-min-height) - 2px /* border */ - 14px /* icon */) / 2);
++ --urlbar-icon-padding: calc((var(--urlbar-height) - 2px /* border */ - 14px /* icon */) / 2);
+ --urlbar-input-container-padding: 2px;
}
@media (max-width: 770px) {
--urlbar-container-min-width: 240px;
-@@ -126,7 +126,7 @@ toolbar[inactive="true"] .urlbar,
+@@ -127,7 +127,7 @@ toolbar[inactive="true"] .urlbar,
.urlbar:not([usertyping]) > .urlbar-input-container > .urlbar-go-button,
.urlbar:not(#searchbar-new, [focused]) > .urlbar-input-container > .urlbar-go-button,
#urlbar-revert-button-container {
@@ -22,7 +22,7 @@ index 22d624d718a289dd7b84ffc2f04dd35893dfbe95..4f57f7853f3043e3ad01d893aa928b9d
}
/* Document Picture-in-Picture API window */
-@@ -403,6 +403,10 @@ toolbar[inactive="true"] .urlbar,
+@@ -405,6 +405,10 @@ toolbar[inactive="true"] .urlbar,
.urlbar:not([focused])[textoverflow="left"][domaindir="rtl"] > .urlbar-input-container > .urlbar-input-box > & {
mask-image: linear-gradient(to right, transparent var(--urlbar-scheme-size), black calc(var(--urlbar-scheme-size) + 3ch));
}
@@ -33,16 +33,18 @@ index 22d624d718a289dd7b84ffc2f04dd35893dfbe95..4f57f7853f3043e3ad01d893aa928b9d
}
#urlbar-scheme {
-@@ -492,10 +496,14 @@ toolbar[inactive="true"] .urlbar,
+@@ -476,10 +480,15 @@ toolbar[inactive="true"] .urlbar,
+ }
.urlbar[breakout][breakout-extend] {
- height: auto;
+- margin-left: calc(-1 * var(--urlbar-margin-inline));
+ align-items: center;
-+ :root:not([zen-single-toolbar='true']) & {
- margin-left: calc(-1 * var(--urlbar-margin-inline));
-+ }
width: calc(var(--urlbar-width) + 2 * var(--urlbar-margin-inline));
++ :root:not([zen-single-toolbar="true"]) & {
++ margin-left: calc(-1 * var(--urlbar-margin-inline));
++ }
++
> .urlbar-input-container {
+ align-items: center;
height: var(--urlbar-container-height);
diff --git a/src/external-patches/firefox/allow_backdrop_to_work_on_transparency.patch b/src/external-patches/firefox/allow_backdrop_to_work_on_transparency.patch
index 20ebb535c..2c7e5bfe1 100644
--- a/src/external-patches/firefox/allow_backdrop_to_work_on_transparency.patch
+++ b/src/external-patches/firefox/allow_backdrop_to_work_on_transparency.patch
@@ -31,12 +31,13 @@ diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src
// Ensure the WR profiler callbacks are hooked up to the Gecko profiler.
set_profiler_hooks(Some(&PROFILER_HOOKS));
-@@ -2164,10 +2165,11 @@
+@@ -2164,11 +2165,12 @@
texture_cache_config,
reject_software_rasterizer,
low_quality_pinch_zoom,
max_shared_surface_size,
enable_dithering,
+ enable_shared_instance_buffer,
+ opaque_backdrop_fallback,
..Default::default()
};
@@ -73,8 +74,9 @@ diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/render
+ /// See explanation of `gfx.webrender.opaque-backdrop-fallback`.
+ pub opaque_backdrop_fallback: bool,
}
-@@ -277,9 +279,10 @@
+@@ -277,10 +279,11 @@
enable_instancing: true,
+ enable_shared_instance_buffer: false,
reject_software_rasterizer: false,
low_quality_pinch_zoom: false,
max_shared_surface_size: 2048,
@@ -84,12 +86,13 @@ diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/render
}
}
-@@ -802,10 +805,11 @@
+@@ -802,11 +805,12 @@
allocated_native_surfaces: FastHashSet::default(),
debug_overlay_state: DebugOverlayState::new(),
buffer_damage_tracker: BufferDamageTracker::default(),
max_primitive_instance_count,
enable_instancing: options.enable_instancing,
+ use_shared_instance_buffer,
+ opaque_backdrop_fallback: options.opaque_backdrop_fallback,
consecutive_oom_frames: 0,
target_frame_publish_id: None,
@@ -99,11 +102,14 @@ diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/render
diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs
--- a/gfx/wr/webrender/src/renderer/mod.rs
+++ b/gfx/wr/webrender/src/renderer/mod.rs
-@@ -867,10 +867,12 @@
+@@ -867,13 +867,15 @@
buffer_damage_tracker: BufferDamageTracker,
max_primitive_instance_count: usize,
enable_instancing: bool,
+ /// If true, stream instance data into large buffers shared by multiple
+ /// draws, rather than allocating a new instance buffer for each draw.
+ use_shared_instance_buffer: bool,
+ opaque_backdrop_fallback: bool,
+
diff --git a/src/external-patches/firefox/bug_2011236.patch b/src/external-patches/firefox/bug_2011236.patch
index a8764a3d4..1a46a9c03 100644
--- a/src/external-patches/firefox/bug_2011236.patch
+++ b/src/external-patches/firefox/bug_2011236.patch
@@ -137,7 +137,7 @@ diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/Sessi
: mURI(aLoadState->URI()),
mOriginalURI(aLoadState->OriginalURI()),
mResultPrincipalURI(aLoadState->ResultPrincipalURI()),
-@@ -59,9 +67,10 @@
+@@ -59,11 +67,12 @@
mLoadReplace(aLoadState->LoadReplace()),
mHasUserActivation(aLoadState->HasValidUserGestureActivation()),
mSharedState(SharedState::Create(
@@ -148,6 +148,8 @@ diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/Sessi
+ aLoadState->PolicyContainer()),
/* FIXME Is this correct? */
aLoadState->TypeHint())) {
+ MOZ_DIAGNOSTIC_ASSERT(!mURI->SchemeIs("javascript"));
+
// Pull the upload stream off of the channel instead of the load state, as
@@ -124,11 +133,12 @@
loadInfo->GetPrincipalToInherit(
diff --git a/src/external-patches/firefox/css_corner_shape_rendering.patch b/src/external-patches/firefox/css_corner_shape_rendering.patch
index b4e70bbe1..3d26a5fbf 100644
--- a/src/external-patches/firefox/css_corner_shape_rendering.patch
+++ b/src/external-patches/firefox/css_corner_shape_rendering.patch
@@ -1,351 +1,51 @@
-diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp
-index 4a72a0eecf..31092b3c05 100644
---- a/gfx/webrender_bindings/WebRenderAPI.cpp
-+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
-@@ -1448,11 +1448,12 @@ void DisplayListBuilder::PushRoundedRect(const wr::LayoutRect& aBounds,
- wr::LayoutSideOffsets widths = {v, h, v, h};
- wr::BorderRadius radii = {{h, v}, {h, v}, {h, v}, {h, v},
- 1.0f, 1.0f, 1.0f, 1.0f};
-+ wr::LayoutSideOffsets inset = EmptyLayoutSideOffsets();
-
- // Anti-aliased borders are required for rounded borders.
- wr_dp_push_border(mWrState, aBounds, aClip, aIsBackfaceVisible,
- &mCurrentSpaceAndClipChain, wr::AntialiasBorder::Yes,
-- widths, side, side, side, side, radii);
-+ widths, side, side, side, side, radii, inset);
- }
-
- void DisplayListBuilder::PushHitTest(
-@@ -1668,20 +1669,18 @@ void DisplayListBuilder::PushIFrame(const LayoutDeviceRect& aDevPxBounds,
- aIgnoreMissingPipeline);
- }
-
--void DisplayListBuilder::PushBorder(const wr::LayoutRect& aBounds,
-- const wr::LayoutRect& aClip,
-- bool aIsBackfaceVisible,
-- const wr::LayoutSideOffsets& aWidths,
-- const Range& aSides,
-- const wr::BorderRadius& aRadius,
-- wr::AntialiasBorder aAntialias) {
-+void DisplayListBuilder::PushBorder(
-+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
-+ bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
-+ const Range& aSides, const wr::BorderRadius& aRadius,
-+ const wr::LayoutSideOffsets& aInset, wr::AntialiasBorder aAntialias) {
- MOZ_ASSERT(aSides.length() == 4);
- if (aSides.length() != 4) {
- return;
- }
- wr_dp_push_border(mWrState, aBounds, aClip, aIsBackfaceVisible,
- &mCurrentSpaceAndClipChain, aAntialias, aWidths, aSides[0],
-- aSides[1], aSides[2], aSides[3], aRadius);
-+ aSides[1], aSides[2], aSides[3], aRadius, aInset);
- }
-
- void DisplayListBuilder::PushBorderImage(const wr::LayoutRect& aBounds,
-diff --git a/gfx/webrender_bindings/WebRenderAPI.h b/gfx/webrender_bindings/WebRenderAPI.h
-index 30d4da889a..f464c2a626 100644
---- a/gfx/webrender_bindings/WebRenderAPI.h
-+++ b/gfx/webrender_bindings/WebRenderAPI.h
-@@ -753,11 +753,13 @@ class DisplayListBuilder final {
-
- // XXX WrBorderSides are passed with Range.
- // It is just to bypass compiler bug. See Bug 1357734.
-- void PushBorder(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
-- bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
-- const Range& aSides,
-- const wr::BorderRadius& aRadius,
-- wr::AntialiasBorder = wr::AntialiasBorder::Yes);
-+ void PushBorder(
-+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
-+ bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
-+ const Range& aSides,
-+ const wr::BorderRadius& aRadius,
-+ const wr::LayoutSideOffsets& aInset = EmptyLayoutSideOffsets(),
-+ wr::AntialiasBorder = wr::AntialiasBorder::Yes);
-
- void PushBorderImage(const wr::LayoutRect& aBounds,
- const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
-diff --git a/gfx/webrender_bindings/WebRenderTypes.h b/gfx/webrender_bindings/WebRenderTypes.h
-index 5799ed46cf..db7dc93faa 100644
---- a/gfx/webrender_bindings/WebRenderTypes.h
-+++ b/gfx/webrender_bindings/WebRenderTypes.h
-@@ -485,40 +485,6 @@ static inline wr::BorderRadius ToBorderRadius(const nsRectCornerRadii& aRadii,
- return br;
- }
-
--static inline wr::ComplexClipRegion ToComplexClipRegion(
-- const nsRect& aRect, const nsRectCornerRadii& aRadii,
-- int32_t aAppUnitsPerDevPixel) {
-- wr::ComplexClipRegion ret;
-- ret.rect =
-- ToLayoutRect(LayoutDeviceRect::FromAppUnits(aRect, aAppUnitsPerDevPixel));
-- ret.radii = ToBorderRadius(aRadii, aAppUnitsPerDevPixel);
-- ret.mode = ClipMode::Clip;
-- return ret;
--}
--
--static inline wr::ComplexClipRegion ToComplexClipRegion(
-- const gfx::RoundedRect& rect) {
-- wr::ComplexClipRegion ret;
-- ret.rect = ToLayoutRect(rect.rect);
-- ret.radii = ToBorderRadius(rect.corners);
-- ret.mode = wr::ClipMode::Clip;
-- return ret;
--}
--
--static inline wr::ComplexClipRegion SimpleRadii(const wr::LayoutRect& aRect,
-- float aRadii) {
-- wr::ComplexClipRegion ret;
-- wr::LayoutSize radii{aRadii, aRadii};
-- ret.rect = aRect;
-- ret.radii = EmptyBorderRadius();
-- ret.radii.top_left = radii;
-- ret.radii.top_right = radii;
-- ret.radii.bottom_left = radii;
-- ret.radii.bottom_right = radii;
-- ret.mode = wr::ClipMode::Clip;
-- return ret;
--}
--
- static inline wr::DeviceIntSideOffsets ToDeviceIntSideOffsets(int32_t top,
- int32_t right,
- int32_t bottom,
-@@ -548,6 +514,18 @@ static inline wr::LayoutSideOffsets ToLayoutSideOffsets(
- aMargin.left);
- }
-
-+static inline wr::LayoutSideOffsets ToLayoutSideOffsets(
-+ const nsMargin& aMargin, int32_t aAppUnitsPerDevPixel) {
-+ return ToLayoutSideOffsets(aMargin.top / float(aAppUnitsPerDevPixel),
-+ aMargin.right / float(aAppUnitsPerDevPixel),
-+ aMargin.bottom / float(aAppUnitsPerDevPixel),
-+ aMargin.left / float(aAppUnitsPerDevPixel));
-+}
-+
-+static inline wr::LayoutSideOffsets EmptyLayoutSideOffsets() {
-+ return ToLayoutSideOffsets(0.0f, 0.0f, 0.0f, 0.0f);
-+}
-+
- static inline wr::LayoutSideOffsets ToBorderWidths(float top, float right,
- float bottom, float left) {
- return ToLayoutSideOffsets(top, right, bottom, left);
-@@ -559,6 +537,55 @@ static inline wr::LayoutSideOffsets ToBorderWidths(const gfx::Margin& aMargin) {
-
- wr::RepeatMode ToRepeatMode(StyleBorderImageRepeatKeyword);
-
-+static inline wr::ComplexClipRegion SimpleRadii(const wr::LayoutRect& aRect,
-+ float aRadii) {
-+ wr::ComplexClipRegion ret;
-+ wr::LayoutSize radii{aRadii, aRadii};
-+ ret.rect = aRect;
-+ ret.radii = EmptyBorderRadius();
-+ ret.radii.top_left = radii;
-+ ret.radii.top_right = radii;
-+ ret.radii.bottom_left = radii;
-+ ret.radii.bottom_right = radii;
-+ ret.inset = EmptyLayoutSideOffsets();
-+ ret.mode = wr::ClipMode::Clip;
-+ return ret;
-+}
-+
-+static inline wr::ComplexClipRegion ToComplexClipRegion(
-+ const gfx::RoundedRect& rect) {
-+ wr::ComplexClipRegion ret;
-+ ret.rect = ToLayoutRect(rect.rect);
-+ ret.radii = ToBorderRadius(rect.corners);
-+ ret.inset = EmptyLayoutSideOffsets();
-+ ret.mode = wr::ClipMode::Clip;
-+ return ret;
-+}
-+
-+static inline wr::ComplexClipRegion ToComplexClipRegion(
-+ const nsRect& aRect, const nsRectCornerRadii& aRadii,
-+ int32_t aAppUnitsPerDevPixel) {
-+ wr::ComplexClipRegion ret;
-+ ret.rect =
-+ ToLayoutRect(LayoutDeviceRect::FromAppUnits(aRect, aAppUnitsPerDevPixel));
-+ ret.radii = ToBorderRadius(aRadii, aAppUnitsPerDevPixel);
-+ ret.inset = EmptyLayoutSideOffsets();
-+ ret.mode = ClipMode::Clip;
-+ return ret;
-+}
-+
-+static inline wr::ComplexClipRegion ToComplexClipRegion(
-+ const nsRect& aRect, const nsRectCornerRadii& aRadii,
-+ const nsMargin& aInset, int32_t aAppUnitsPerDevPixel) {
-+ wr::ComplexClipRegion ret;
-+ ret.rect =
-+ ToLayoutRect(LayoutDeviceRect::FromAppUnits(aRect, aAppUnitsPerDevPixel));
-+ ret.radii = ToBorderRadius(aRadii, aAppUnitsPerDevPixel);
-+ ret.inset = ToLayoutSideOffsets(aInset, aAppUnitsPerDevPixel);
-+ ret.mode = ClipMode::Clip;
-+ return ret;
-+}
-+
- template
- static inline wr::WrTransformProperty ToWrTransformProperty(
- uint64_t id, const gfx::Matrix4x4Typed& transform) {
-diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs
-index f76367a1f9..5f0b02097c 100644
---- a/gfx/webrender_bindings/src/bindings.rs
-+++ b/gfx/webrender_bindings/src/bindings.rs
-@@ -4037,6 +4037,7 @@ pub extern "C" fn wr_dp_push_border(
- bottom: BorderSide,
- left: BorderSide,
- radius: BorderRadius,
-+ inset: LayoutSideOffsets,
- ) {
- debug_assert!(unsafe { is_in_main_thread() });
-
-@@ -4046,6 +4047,7 @@ pub extern "C" fn wr_dp_push_border(
- top,
- bottom,
- radius,
-+ inset,
- do_aa: do_aa == AntialiasBorder::Yes,
- });
-
-diff --git a/gfx/wr/examples/animation.rs b/gfx/wr/examples/animation.rs
-index 11fcfe647c..55f688bc52 100644
---- a/gfx/wr/examples/animation.rs
-+++ b/gfx/wr/examples/animation.rs
-@@ -85,6 +85,7 @@ impl App {
- let complex_clip = ComplexClipRegion {
- rect: clip_bounds,
- radii: BorderRadius::uniform(30.0),
-+ inset: LayoutSideOffsets::zero(),
- mode: ClipMode::Clip,
- };
- let clip_id = builder.define_clip_rounded_rect(
-diff --git a/gfx/wr/examples/basic.rs b/gfx/wr/examples/basic.rs
-index df2f322a43..1dfbef8d53 100644
---- a/gfx/wr/examples/basic.rs
-+++ b/gfx/wr/examples/basic.rs
-@@ -52,6 +52,7 @@ impl Example for App {
- let complex = ComplexClipRegion::new(
- (50, 50).to(150, 150),
- BorderRadius::uniform(20.0),
-+ LayoutSideOffsets::zero(),
- ClipMode::Clip
- );
- let clip_id = builder.define_clip_rounded_rect(
-@@ -88,6 +89,7 @@ impl Example for App {
- bottom: border_side,
- left: border_side,
- radius: BorderRadius::uniform(20.0),
-+ inset: LayoutSideOffsets::zero(),
- do_aa: true,
- });
-
-diff --git a/gfx/wr/swgl/src/glsl.h b/gfx/wr/swgl/src/glsl.h
-index b9e7982948..533d891aaa 100644
---- a/gfx/wr/swgl/src/glsl.h
-+++ b/gfx/wr/swgl/src/glsl.h
-@@ -645,6 +645,9 @@ SI vec2 min(vec2 a, Float b) { return vec2(min(a.x, b), min(a.y, b)); }
- SI vec2_scalar min(vec2_scalar a, vec2_scalar b) {
- return vec2_scalar{min(a.x, b.x), min(a.y, b.y)};
- }
-+SI vec2_scalar min(vec2_scalar a, float b) {
-+ return vec2_scalar{min(a.x, b), min(a.y, b)};
-+}
-
- SI vec2 if_then_else(I32 c, vec2 t, vec2 e) {
- return vec2(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y));
-@@ -1743,6 +1746,9 @@ SI vec3 min(vec3 a, Float b) {
- SI vec3_scalar min(vec3_scalar a, vec3_scalar b) {
- return vec3_scalar{min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)};
- }
-+SI vec3_scalar min(vec3_scalar a, float b) {
-+ return vec3_scalar{min(a.x, b), min(a.y, b), min(a.z, b)};
-+}
-
- SI vec3 max(vec3 a, vec3 b) {
- return vec3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z));
-@@ -1753,6 +1759,9 @@ SI vec3 max(vec3 a, Float b) {
- SI vec3_scalar max(vec3_scalar a, vec3_scalar b) {
- return vec3_scalar{max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)};
- }
-+SI vec3_scalar max(vec3_scalar a, float b) {
-+ return vec3_scalar{max(a.x, b), max(a.y, b), max(a.z, b)};
-+}
-
- vec3 pow(vec3 x, vec3 y) {
- return vec3(pow(x.x, y.x), pow(x.y, y.y), pow(x.z, y.z));
-@@ -2278,6 +2287,32 @@ vec4_scalar step(vec4_scalar edge, vec4_scalar x) {
- step(edge.w, x.w));
- }
-
-+SI vec4 min(vec4 a, vec4 b) {
-+ return vec4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w));
-+}
-+SI vec4 min(vec4 a, Float b) {
-+ return vec4(min(a.x, b), min(a.y, b), min(a.z, b), min(a.w, b));
-+}
-+SI vec4_scalar min(vec4_scalar a, vec4_scalar b) {
-+ return vec4_scalar{min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)};
-+}
-+SI vec4_scalar min(vec4_scalar a, float b) {
-+ return vec4_scalar{min(a.x, b), min(a.y, b), min(a.z, b), min(a.w, b)};
-+}
-+
-+SI vec4 max(vec4 a, vec4 b) {
-+ return vec4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w));
-+}
-+SI vec4 max(vec4 a, Float b) {
-+ return vec4(max(a.x, b), max(a.y, b), max(a.z, b), max(a.w, b));
-+}
-+SI vec4_scalar max(vec4_scalar a, vec4_scalar b) {
-+ return vec4_scalar{max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)};
-+}
-+SI vec4_scalar max(vec4_scalar a, float b) {
-+ return vec4_scalar{max(a.x, b), max(a.y, b), max(a.z, b), max(a.w, b)};
-+}
-+
- template
- auto lessThanEqual(T x, T y) -> decltype(x <= y) {
- return x <= y;
diff --git a/gfx/wr/webrender/res/border_shared.glsl b/gfx/wr/webrender/res/border_shared.glsl
-index 5981e53bde..313b51bcb0 100644
+index f385449fa7..76eba024bd 100644
--- a/gfx/wr/webrender/res/border_shared.glsl
+++ b/gfx/wr/webrender/res/border_shared.glsl
-@@ -21,6 +21,8 @@ PER_INSTANCE in int aGpuDataAddress;
+@@ -21,31 +21,39 @@ PER_INSTANCE in int aGpuDataAddress;
PER_INSTANCE in vec4 aClipParams1;
PER_INSTANCE in vec4 aClipParams2;
-+#endif
-+
++#endif
++
struct BorderInstanceGpuData {
vec4 rect;
vec4 color0;
-@@ -28,20 +30,22 @@ struct BorderInstanceGpuData {
+ vec4 color1;
vec2 widths;
vec2 radii;
++#ifdef WR_FEATURE_SUPERELLIPSE
float shape;
-+ vec2 shape_offset;
-+ vec2 inset;
+ vec2 shape_offset;
+ vec2 inset;
++#endif
};
BorderInstanceGpuData fetch_gpu_data(int index) {
BorderInstanceGpuData data;
-- vec4 texels[5] = fetch_from_gpu_buffer_5f(index);
-+ vec4 texels[6] = fetch_from_gpu_buffer_6f(index);
++#ifdef WR_FEATURE_SUPERELLIPSE
+ vec4 texels[6] = fetch_from_gpu_buffer_6f(index);
++#else
++ vec4 texels[4] = fetch_from_gpu_buffer_4f(index);
++#endif
data.rect = texels[0];
data.color0 = texels[1];
data.color1 = texels[2];
data.widths = texels[3].xy;
data.radii = texels[3].zw;
++#ifdef WR_FEATURE_SUPERELLIPSE
data.shape = texels[4].x;
-+ data.shape_offset = texels[4].yz;
-+ data.inset = texels[5].xy;
+ data.shape_offset = texels[4].yz;
+ data.inset = texels[5].xy;
++#endif
return data;
}
-
-#endif
diff --git a/gfx/wr/webrender/res/cs_border_segment.glsl b/gfx/wr/webrender/res/cs_border_segment.glsl
-index 066f63f62a..f9998badd6 100644
+index 6a94117405..51c9318b31 100644
--- a/gfx/wr/webrender/res/cs_border_segment.glsl
+++ b/gfx/wr/webrender/res/cs_border_segment.glsl
@@ -15,10 +15,10 @@ flat varying mediump vec4 vColor11;
@@ -361,15 +61,17 @@ index 066f63f62a..f9998badd6 100644
// x, y: styles, z, w: edge axes
// We cast these to/from floats rather than using an ivec (and bitshifting)
// due to a driver bug on Adreno 3xx. See bug 1730458.
-@@ -32,6 +32,7 @@ flat varying highp vec4 vClipCenter_Sign;
+@@ -32,6 +32,9 @@ flat varying highp vec4 vClipCenter_Sign;
// An outer and inner elliptical radii for border
// corner clipping.
flat varying highp vec4 vClipRadii;
++#ifdef WR_FEATURE_SUPERELLIPSE
+flat varying highp vec4 vClipOffsets;
++#endif
// Reference point for determine edge clip lines.
flat varying highp vec4 vEdgeReference;
-@@ -181,7 +182,7 @@ void main(void) {
+@@ -181,7 +184,7 @@ void main(void) {
break;
}
@@ -378,19 +80,25 @@ index 066f63f62a..f9998badd6 100644
vStyleEdgeAxis = vec4(float(style0), float(style1), float(edge_axis.x), float(edge_axis.y));
vPartialWidths = vec4(data.widths / 3.0, data.widths / 2.0);
-@@ -193,13 +194,23 @@ void main(void) {
+@@ -193,13 +196,31 @@ void main(void) {
vec4[2] color1 = get_colors_for_side(data.color1, style1);
vColor10 = color1[0];
vColor11 = color1[1];
-- vClipCenter_Sign = vec4(outer + clip_sign * data.radii, clip_sign);
++#ifdef WR_FEATURE_SUPERELLIPSE
+ vClipCenter_Sign = vec4(outer + clip_sign * (data.radii + data.shape_offset), clip_sign);
++#else
+ vClipCenter_Sign = vec4(outer + clip_sign * data.radii, clip_sign);
++#endif
vClipRadii = vec4(data.radii, max(data.radii - data.widths, 0.0));
++#ifdef WR_FEATURE_SUPERELLIPSE
+ vClipOffsets = vec4(0.0);
++#endif
vColorLine = vec4(outer, data.widths.y * -clip_sign.y, data.widths.x * clip_sign.x);
vEdgeReference = vec4(edge_reference, edge_reference + data.widths);
vClipParams1 = aClipParams1;
vClipParams2 = aClipParams2;
++#ifdef WR_FEATURE_SUPERELLIPSE
+ if (data.shape != 1.0)
+ {
+ vec2 reference_radii = (data.radii == vec2(0.0)) ? vec2(0.0) : data.radii + data.inset;
@@ -399,11 +107,12 @@ index 066f63f62a..f9998badd6 100644
+ vClipOffsets = vec4(contour1.xy, contour2.xy);
+ vClipRadii = vec4(contour1.zw, contour2.zw);
+ }
++#endif
+
// For the case of dot and dash clips, optimize the number of pixels that
// are hit to just include the dot itself.
if (clip_mode == CLIP_DOT) {
-@@ -231,6 +242,7 @@ void main(void) {
+@@ -231,6 +252,7 @@ void main(void) {
#ifdef WR_FRAGMENT_SHADER
vec4 evaluate_color_for_style_in_corner(
@@ -411,7 +120,7 @@ index 066f63f62a..f9998badd6 100644
vec2 clip_relative_pos,
int style,
vec4 color0,
-@@ -246,24 +258,61 @@ vec4 evaluate_color_for_style_in_corner(
+@@ -246,24 +268,69 @@ vec4 evaluate_color_for_style_in_corner(
// also 0.67 of the radii. Use these to form a
// SDF subtraction which will clip out the inside
// third of the rounded edge.
@@ -425,7 +134,9 @@ index 066f63f62a..f9998badd6 100644
- );
+ float d_radii_a;
+ float d_radii_b;
++#ifdef WR_FEATURE_SUPERELLIPSE
+ if (data.shape == 1.0) {
++#endif
+ d_radii_a = distance_to_ellipse(
+ clip_relative_pos,
+ clip_radii.xy - vPartialWidths.xy
@@ -434,6 +145,7 @@ index 066f63f62a..f9998badd6 100644
+ clip_relative_pos,
+ clip_radii.xy - 2.0 * vPartialWidths.xy
+ );
++#ifdef WR_FEATURE_SUPERELLIPSE
+ } else {
+ d_radii_a = distance_to_superellipse(
+ clip_relative_pos - mix(vClipOffsets.xy, vClipOffsets.zw, 1.0 / 3.0),
@@ -453,6 +165,7 @@ index 066f63f62a..f9998badd6 100644
+ vec2 included_region_b = data.radii - 2.0 * vPartialWidths.xy - clip_relative_pos;
+ d_radii_b = max(d_radii_b, -min(included_region_b.x, included_region_b.y));
+ }
++#endif
+
float d = min(-d_radii_a, d_radii_b);
+
@@ -466,11 +179,14 @@ index 066f63f62a..f9998badd6 100644
- clip_radii.xy - vPartialWidths.zw
- );
+ float d;
++#ifdef WR_FEATURE_SUPERELLIPSE
+ if (data.shape == 1.0) {
++#endif
+ d = distance_to_ellipse(
+ clip_relative_pos,
+ clip_radii.xy - vPartialWidths.zw
+ );
++#ifdef WR_FEATURE_SUPERELLIPSE
+ } else {
+ d = distance_to_superellipse(
+ clip_relative_pos - mix(vClipOffsets.xy, vClipOffsets.zw, 0.5),
@@ -482,10 +198,11 @@ index 066f63f62a..f9998badd6 100644
+ vec2 included_region = data.radii - vPartialWidths.zw - clip_relative_pos;
+ d = max(d, -min(included_region.x, included_region.y));
+ }
++#endif
float alpha = distance_aa(aa_range, d);
float swizzled_factor;
switch (segment) {
-@@ -328,11 +377,14 @@ void main(void) {
+@@ -328,11 +395,14 @@ void main(void) {
float aa_range = compute_aa_range(vPos);
vec4 color0, color1;
@@ -502,7 +219,7 @@ index 066f63f62a..f9998badd6 100644
float mix_factor = 0.0;
if (edge_axis.x != edge_axis.y) {
float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
-@@ -382,12 +434,27 @@ void main(void) {
+@@ -382,12 +452,31 @@ void main(void) {
}
if (in_clip_region) {
@@ -511,9 +228,12 @@ index 066f63f62a..f9998badd6 100644
+ float d_radii_a;
+ float d_radii_b;
+
++#ifdef WR_FEATURE_SUPERELLIPSE
+ if (data.shape == 1.0) {
++#endif
+ d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy);
+ d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw);
++#ifdef WR_FEATURE_SUPERELLIPSE
+ } else {
+ clip_relative_pos = abs(clip_relative_pos) - data.shape_offset;
+ d_radii_a = distance_to_superellipse(clip_relative_pos - vClipOffsets.xy, vClipRadii.xy, data.shape);
@@ -523,6 +243,7 @@ index 066f63f62a..f9998badd6 100644
+ vec2 included_region = data.radii - data.widths - clip_relative_pos;
+ d_radii_b = max(d_radii_b, -min(included_region.x, included_region.y));
+ }
++#endif
+
float d_radii = max(d_radii_a, -d_radii_b);
d = max(d, d_radii);
@@ -533,7 +254,7 @@ index 066f63f62a..f9998badd6 100644
clip_relative_pos,
style.x,
vColor00,
-@@ -398,6 +465,7 @@ void main(void) {
+@@ -398,6 +487,7 @@ void main(void) {
aa_range
);
color1 = evaluate_color_for_style_in_corner(
@@ -542,7 +263,7 @@ index 066f63f62a..f9998badd6 100644
style.y,
vColor10,
diff --git a/gfx/wr/webrender/res/cs_border_solid.glsl b/gfx/wr/webrender/res/cs_border_solid.glsl
-index 90584346a4..a3ff0e887e 100644
+index 90584346a4..05d314571e 100644
--- a/gfx/wr/webrender/res/cs_border_solid.glsl
+++ b/gfx/wr/webrender/res/cs_border_solid.glsl
@@ -8,6 +8,10 @@
@@ -556,32 +277,44 @@ index 90584346a4..a3ff0e887e 100644
// For edges, the colors are the same. For corners, these
// are the colors of each edge making up the corner.
flat varying mediump vec4 vColor0;
-@@ -29,6 +33,7 @@ flat varying highp vec4 vClipCenter_Sign;
+@@ -29,6 +33,9 @@ flat varying highp vec4 vClipCenter_Sign;
// An outer and inner elliptical radii for border
// corner clipping.
flat varying highp vec4 vClipRadii;
++#ifdef WR_FEATURE_SUPERELLIPSE
+flat varying highp vec4 vClipOffsets;
++#endif
// Position, scale, and radii of horizontally and vertically adjacent corner clips.
flat varying highp vec4 vHorizontalClipCenter_Sign;
-@@ -68,6 +73,7 @@ vec2 get_outer_corner_scale(int segment) {
+@@ -68,9 +75,10 @@ vec2 get_outer_corner_scale(int segment) {
void main(void) {
BorderInstanceGpuData data = fetch_gpu_data(aGpuDataAddress);
+ vGpuDataAddress.x = aGpuDataAddress;
int segment = aFlags & 0xff;
- bool do_aa = ((aFlags >> 24) & 0xf0) != 0;
-@@ -96,10 +102,20 @@ void main(void) {
+- bool do_aa = ((aFlags >> 24) & 0xf0) != 0;
++ bool do_aa = ((aFlags >> 28) & 0x1) != 0;
+
+ vec2 outer_scale = get_outer_corner_scale(segment);
+ vec2 size = data.rect.zw - data.rect.xy;
+@@ -96,10 +104,28 @@ void main(void) {
vColor0 = data.color0;
vColor1 = data.color1;
-- vClipCenter_Sign = vec4(outer + clip_sign * data.radii, clip_sign);
++#ifdef WR_FEATURE_SUPERELLIPSE
+ vClipCenter_Sign = vec4(outer + clip_sign * (data.radii + data.shape_offset), clip_sign);
++#else
+ vClipCenter_Sign = vec4(outer + clip_sign * data.radii, clip_sign);
++#endif
vClipRadii = vec4(data.radii, max(data.radii - data.widths, 0.0));
++#ifdef WR_FEATURE_SUPERELLIPSE
+ vClipOffsets = vec4(0.0);
++#endif
vColorLine = vec4(outer, data.widths.y * -clip_sign.y, data.widths.x * clip_sign.x);
++#ifdef WR_FEATURE_SUPERELLIPSE
+ if (data.shape != 1.0)
+ {
+ vec2 reference_radii = (data.radii == vec2(0.0)) ? vec2(0.0) : data.radii + data.inset;
@@ -590,11 +323,12 @@ index 90584346a4..a3ff0e887e 100644
+ vClipOffsets = vec4(contour1.xy, contour2.xy);
+ vClipRadii = vec4(contour1.zw, contour2.zw);
+ }
++#endif
+
vec2 horizontal_clip_sign = vec2(-clip_sign.x, clip_sign.y);
vHorizontalClipCenter_Sign = vec4(aClipParams1.xy +
horizontal_clip_sign * aClipParams1.zw,
-@@ -118,6 +134,8 @@ void main(void) {
+@@ -118,6 +144,8 @@ void main(void) {
#ifdef WR_FRAGMENT_SHADER
void main(void) {
@@ -603,7 +337,7 @@ index 90584346a4..a3ff0e887e 100644
float aa_range = compute_aa_range(vPos);
bool do_aa = vMixColors.x != MIX_NO_AA;
-@@ -137,8 +155,22 @@ void main(void) {
+@@ -137,8 +165,26 @@ void main(void) {
float d = -1.0;
if (in_clip_region) {
@@ -612,9 +346,12 @@ index 90584346a4..a3ff0e887e 100644
+ float d_radii_a;
+ float d_radii_b;
+
++#ifdef WR_FEATURE_SUPERELLIPSE
+ if (data.shape == 1.0) {
++#endif
+ d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy);
+ d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw);
++#ifdef WR_FEATURE_SUPERELLIPSE
+ } else {
+ clip_relative_pos = abs(clip_relative_pos) - data.shape_offset;
+ d_radii_a = distance_to_superellipse(clip_relative_pos - vClipOffsets.xy, vClipRadii.xy, data.shape);
@@ -624,28 +361,19 @@ index 90584346a4..a3ff0e887e 100644
+ vec2 included_region = data.radii - data.widths - clip_relative_pos;
+ d_radii_b = max(d_radii_b, -min(included_region.x, included_region.y));
+ }
++#endif
+
d = max(d_radii_a, -d_radii_b);
}
diff --git a/gfx/wr/webrender/res/ellipse.glsl b/gfx/wr/webrender/res/ellipse.glsl
-index 4c33057214..7ce1bcacb4 100644
+index cb7d5d89af..7ce1bcacb4 100644
--- a/gfx/wr/webrender/res/ellipse.glsl
+++ b/gfx/wr/webrender/res/ellipse.glsl
-@@ -17,6 +17,184 @@ vec2 inverse_radii(vec2 radii) {
- return 1.0 / max(radii, 1.0e-3);
- }
+@@ -27,9 +27,27 @@ float compute_superellipse_half_corner(float shape) {
+ float n = exp2(shape);
+ float convex_half_corner = pow(0.5, 1.0 / n);
-+// Spec: https://drafts.csswg.org/css-borders/#normalized-superellipse-half-corner
-+//
-+// Contrary to the spec, the symmetric case (shape < 0) is handled in
-+// compute_contoured_superellipse() instead of in this function.
-+float compute_superellipse_half_corner(float shape) {
-+ shape = min(1.0, abs(shape));
-+
-+ float n = exp2(shape);
-+ float convex_half_corner = pow(0.5, 1.0 / n);
-+
+ // Ensure the curve tends towards 0.75 (which will produce axis-aligned
+ // normals in compute_superellipse_normals()) when shape goes to 1 (round).
+ // This makes the curve continuous from subellipses (shape < 1) to
@@ -656,9 +384,9 @@ index 4c33057214..7ce1bcacb4 100644
+ float falloff = shape * shape; // quadratic falloff
+ convex_half_corner += delta * falloff;
+
-+ return convex_half_corner;
-+}
-+
+ return convex_half_corner;
+ }
+
+// Super cheap approximation of the function above: the delta with quadratic
+// falloff actually makes it almost locally linear for shape values between
+// zero and one.
@@ -667,94 +395,15 @@ index 4c33057214..7ce1bcacb4 100644
+ return 0.25 * shape + 0.5;
+}
+
-+#if 0
-+
-+// Alternate method to find the normals of a superellipse
-+// at the start and end points or the curve.
-+//
-+// Using implicit differentiation [1, 2], we find that for the superellipse
-+// defined with this implicit formula:
-+//
-+// S(x, y) = (x/a)^n + (y/a)^n - 1 = 0
-+//
-+// where (a, b) are the radii and n is the order,
-+// we find that the gradient at the point (x0, y0) is:
-+//
-+// dy/dx = -b^n * x0^(n-1) / a^n * y0^(n-1)
-+//
-+// Introducing an intermediate variable q, this simplifies to:
-+//
-+// let q = [(x0 / a)^(n-1); (y0 / b)^(n-1)]
-+//
-+// dy/dx = -b*qx / a*qy
-+//
-+// And, symmetrically:
-+//
-+// dx/dy = -a*qy / b*qx
-+//
-+// Using both derivatives is useful for numerical stabillty: as the two
-+// normals (at start and end) tend towards orthogonality when the exponent
-+// approches 2 (= circle), one of them is likely to go towards infinity.
-+//
-+// The exact start and end of the curve are discontinuities, so we pick
-+// points that are technically not on the curve but very close to start
-+// and end points, at a distance of 5% of the radii:
-+//
-+// Start gradient evaluated at: (0.05*a, b)
-+// End gradient evaluated at: (a, 0.05*b)
-+//
-+// Using ratios of the radii simplifies the computation further:
-+//
-+// let x0 = 0.05*a
-+// let y0 = b
-+//
-+// qx = (x0 / a)^(n-1)
-+// = (0.05*a / a)^(n-1)
-+// = 0.05^(n-1)
-+//
-+// qy = (b / b)^(n-1)
-+// = 1
-+//
-+// The same simplification applies symmetrically for the dx/dy calculation
-+// at the end point.
-+//
-+// In the final form, we can compute a single q value (instead of a vector),
-+// and derive the gradients from this and the radii ratios (see code below):
-+//
-+// let q = 0.05^(n-1)
-+// dy/dx = -q * b / a
-+// dx/dy = -q * a / b
-+//
-+// The tangents are then defined from the gradients:
-+// let t1 = (1, dy/dx)
-+// let t2 = (dx/dy, 1)
-+//
-+// And the normals are obtained by a simple 90° rotation of the tangents:
-+// let n1 = (t1.y, -t1.x) = (dy/dx, -1)
-+// let n2 = (-t2.y, t2.x) = (-1, dx/dy)
-+//
-+// Returns start and end normals in (x, y) and (z, w) respectively
-+//
-+// [1] https://www.kristakingmath.com/blog/equation-of-the-tangent-line-with-implicit-differentiation
-+// [2] https://www.khanacademy.org/math/ap-calculus-bc/bc-differentiation-2-new/bc-3-2/v/implicit-differentiation-1
-+//
-+// Note: the points where we compute the gradients are technically not
-+// on the curve. In practice, we found that it is not an issue unless the
-+// curve is concave (n < 1). As the spec defines this case to be implemented
-+// as a geometrical symmetry from the convex case (n > 1), it is never encountered
-+// in this implementation.
-+//
-+// For reference, here is the full form that reprojects the point on the curve,
-+// given a fixed x0:
-+//
-+// qx = (x0 / a)^(n-1) # Same as before
-+// qy = (1 - x0 / a * qx)^((n-1) / n) # Project the y coord on the curve
-+//
-+// Which yields:
-+//
-+// dy/dx = -b / a * qx * (1 - x0 / a * qx)^((1-n) / n) # Careful, exponent flipped
-+//
-+vec4 compute_superellipse_normals(vec2 radii, float shape) {
+ #if 0
+
+ // Alternate method to find the normals of a superellipse
+@@ -118,8 +136,15 @@ float compute_superellipse_half_corner(float shape) {
+ // dy/dx = -b / a * qx * (1 - x0 / a * qx)^((1-n) / n) # Careful, exponent flipped
+ //
+ vec4 compute_superellipse_normals(vec2 radii, float shape) {
+- float n = exp2(abs(shape));
+- float q = pow(0.05, n - 1.0);
+ shape = min(1.0, abs(shape));
+
+ float n = exp2(shape);
@@ -764,1660 +413,960 @@ index 4c33057214..7ce1bcacb4 100644
+ // of 1 and above, the normals should always be axis-aligned)
+ float damping_factor = (1.0 - shape * shape);
+ float q = pow(0.05 * damping_factor, n - 1.0);
-+
-+ // x: dy/dx at (0.05 * radii.x, radii.y)
-+ // y: dx/dy at (radii.x, 0.05 * radii.y)
-+ vec2 grad = -q * radii.yx / max(radii.xy, 0.1);
-+
-+ vec2 n1 = normalize(vec2(grad.x, -1.0));
-+ vec2 n2 = normalize(vec2(-1.0, grad.y));
-+
-+ return vec4(n1, n2);
-+}
-+
-+#else
-+
-+// Spec: https://drafts.csswg.org/css-borders/#contour-path
-+//
-+// Returns start and end normals in (x, y) and (z, w) respectively
-+vec4 compute_superellipse_normals(vec2 radii, float shape) {
+
+ // x: dy/dx at (0.05 * radii.x, radii.y)
+ // y: dx/dy at (radii.x, 0.05 * radii.y)
+@@ -137,8 +162,7 @@ vec4 compute_superellipse_normals(vec2 radii, float shape) {
+ //
+ // Returns start and end normals in (x, y) and (z, w) respectively
+ vec4 compute_superellipse_normals(vec2 radii, float shape) {
+-
+- float convex_half_corner = compute_superellipse_half_corner(shape);
+ float convex_half_corner = compute_superellipse_half_corner_approx(shape);
-+ vec2 tangent = vec2(0.5 - 2.0 * convex_half_corner, 2.0 * convex_half_corner - 1.5);
-+
-+ // Avoid dividing by zero
-+ radii = max(radii, 0.1);
-+
-+ vec2 n1 = normalize(radii.yx * tangent.yx);
-+ vec2 n2 = normalize(radii.yx * tangent.xy);
-+
-+ return vec4(n1, n2);
-+}
-+
-+#endif
-+
-+// Spec: https://drafts.csswg.org/css-borders/#contour-path
-+//
-+// Returns an offset (x,y), and new radii (zw)
-+vec4 compute_contoured_superellipse(vec2 radii, float shape, vec2 inset) {
-+ vec4 normals = compute_superellipse_normals(radii, shape);
-+
-+ // Flip x/y for symmetry
-+ if (shape < 0.0) {
-+ normals = normals.zwxy;
-+ }
-+
-+ // Compute delta by extruding inset along the normals
-+ vec4 delta = normals * inset.yyxx;
-+
-+ vec2 offset = vec2(delta.x, delta.w);
-+ vec2 contourRadii = max(radii + vec2(delta.z, delta.y) - offset, 0.1);
-+ return vec4(offset, contourRadii);
-+}
-+
- #ifdef WR_FRAGMENT_SHADER
+ vec2 tangent = vec2(0.5 - 2.0 * convex_half_corner, 2.0 * convex_half_corner - 1.5);
- // One iteration of Newton's method on the 2D equation of an ellipse:
-@@ -134,8 +312,7 @@ float distance_to_superellipse_approx(vec2 p, vec2 inv_radii, float k) {
- // +x,+y projection so the line distance matches geometry inside
- // the box (where p has negative components).
- if (k == 0.0) {
-- vec2 pp = max(p, vec2(0.0));
-- float g = dot(pp, inv_radii) - 1.0;
-+ float g = dot(p, inv_radii) - 1.0;
- return g * inversesqrt(dot(inv_radii, inv_radii));
- }
-
-@@ -159,6 +336,21 @@ float distance_to_superellipse_approx(vec2 p, vec2 inv_radii, float k) {
- // Divide by radii: normalize the position to [0, 1]
- vec2 q = p * inv_radii;
-
-+ // If position is outside the shaped region, extend along the curve tangents
-+ if (any(lessThanEqual(q, vec2(0.0)))) {
-+ vec2 radii = 1.0 / inv_radii;
-+
-+ vec4 normals = compute_superellipse_normals(radii, k);
-+
-+ float d1 = dot(p, normals.xy) - normals.y * radii.y;
-+ float d2 = dot(p, normals.zw) - normals.z * radii.x;
-+ if (k >= 0.0) {
-+ return max(-d1, -d2);
-+ } else {
-+ return min(d1, d2);
-+ }
-+ }
-+
- // Compute the superellipse function
- vec2 qn = pow(q, vec2(n));
- qn = clamp(qn, 0.0, 1.0e3); // Clamp to avoid numerical overflow
-@@ -183,9 +375,21 @@ float distance_to_superellipse_approx(vec2 p, vec2 inv_radii, float k) {
- }
-
- float distance_to_superellipse(vec2 p, vec2 radii, float k) {
-+ if (any(lessThan(radii, vec2(1.0)))) {
-+ return -1.0;
-+ }
-+
- return distance_to_superellipse_approx(p, inverse_radii(radii), k);
- }
-
-+float distance_to_shaped_corner(vec2 pos, vec2 inv_radii, float shape) {
-+ if (shape == 1.0) {
-+ return distance_to_ellipse_approx(pos, inv_radii, 1.0);
-+ } else {
-+ return distance_to_superellipse_approx(pos, inv_radii, shape);
-+ }
-+}
-+
- // Same as distance_to_rounded_rect but with per-corner shape values.
- float distance_to_shaped_rect(
- vec2 pos,
-@@ -194,56 +398,34 @@ float distance_to_shaped_rect(
- vec4 center_radius_br,
- vec4 center_radius_bl,
- vec4 rect_bounds,
-- vec4 corner_shapes
-+ vec4 corner_shapes,
-+ vec4 clamped_inset // always negative (i.e outset-only)
- ) {
-- vec2 corner_p = vec2(1.0e-6);
-- vec2 corner_inv = vec2(1.0);
-- float corner_k = 1.0;
--
-- float in_corner = 0.0;
-+ float d = signed_distance_rect(pos, rect_bounds.xy, rect_bounds.zw);
-
- vec2 p_tl = center_radius_tl.xy - pos;
- vec2 p_tr = (center_radius_tr.xy - pos) * vec2(-1.0, 1.0);
- vec2 p_br = pos - center_radius_br.xy;
- vec2 p_bl = (center_radius_bl.xy - pos) * vec2(1.0, -1.0);
-
-- if (p_tl.x >= 0.0 && p_tl.y >= 0.0) {
-- corner_p = p_tl;
-- corner_inv = center_radius_tl.zw;
-- corner_k = corner_shapes.x;
-- in_corner = 1.0;
-- }
-- if (p_tr.x >= 0.0 && p_tr.y >= 0.0) {
-- corner_p = p_tr;
-- corner_inv = center_radius_tr.zw;
-- corner_k = corner_shapes.y;
-- in_corner = 1.0;
-- }
-- if (p_br.x >= 0.0 && p_br.y >= 0.0) {
-- corner_p = p_br;
-- corner_inv = center_radius_br.zw;
-- corner_k = corner_shapes.z;
-- in_corner = 1.0;
-- }
-- if (p_bl.x >= 0.0 && p_bl.y >= 0.0) {
-- corner_p = p_bl;
-- corner_inv = center_radius_bl.zw;
-- corner_k = corner_shapes.w;
-- in_corner = 1.0;
-- }
--
-- float d_corner;
-- if (in_corner == 0.0) {
-- // Not in any corner. let the rect SDF decide. Use a strongly
-- // negative value so max() defers to the rect.
-- d_corner = -1.0e6;
-- } else if (corner_k == 1.0) {
-- d_corner = distance_to_ellipse_approx(corner_p, corner_inv, 1.0);
-- } else {
-- d_corner = distance_to_superellipse_approx(corner_p, corner_inv, corner_k);
-+ vec2 i_tl = (corner_shapes.x == 1.0) ? vec2(0.0) : clamped_inset.xw;
-+ vec2 i_tr = (corner_shapes.y == 1.0) ? vec2(0.0) : clamped_inset.xy;
-+ vec2 i_br = (corner_shapes.z == 1.0) ? vec2(0.0) : clamped_inset.zy;
-+ vec2 i_bl = (corner_shapes.w == 1.0) ? vec2(0.0) : clamped_inset.zw;
-+
-+ if (p_tl.x >= i_tl.x && p_tl.y >= i_tl.y) {
-+ d = max(d, distance_to_shaped_corner(p_tl, center_radius_tl.zw, corner_shapes.x));
-+ }
-+ if (p_tr.x >= i_tr.x && p_tr.y >= i_tr.y) {
-+ d = max(d, distance_to_shaped_corner(p_tr, center_radius_tr.zw, corner_shapes.y));
-+ }
-+ if (p_br.x >= i_br.x && p_br.y >= i_br.y) {
-+ d = max(d, distance_to_shaped_corner(p_br, center_radius_br.zw, corner_shapes.z));
-+ }
-+ if (p_bl.x >= i_bl.x && p_bl.y >= i_bl.y) {
-+ d = max(d, distance_to_shaped_corner(p_bl, center_radius_bl.zw, corner_shapes.w));
- }
-
-- return max(d_corner,
-- signed_distance_rect(pos, rect_bounds.xy, rect_bounds.zw));
-+ return d;
- }
- #endif
-diff --git a/gfx/wr/webrender/res/gpu_buffer.glsl b/gfx/wr/webrender/res/gpu_buffer.glsl
-index b6f55a2c02..c649ab1807 100644
---- a/gfx/wr/webrender/res/gpu_buffer.glsl
-+++ b/gfx/wr/webrender/res/gpu_buffer.glsl
-@@ -44,6 +44,17 @@ vec4[5] fetch_from_gpu_buffer_5f_direct(ivec2 uv) {
- );
- }
-
-+vec4[6] fetch_from_gpu_buffer_6f_direct(ivec2 uv) {
-+ return vec4[6](
-+ TEXEL_FETCH(sGpuBufferF, uv, 0, ivec2(0, 0)),
-+ TEXEL_FETCH(sGpuBufferF, uv, 0, ivec2(1, 0)),
-+ TEXEL_FETCH(sGpuBufferF, uv, 0, ivec2(2, 0)),
-+ TEXEL_FETCH(sGpuBufferF, uv, 0, ivec2(3, 0)),
-+ TEXEL_FETCH(sGpuBufferF, uv, 0, ivec2(4, 0)),
-+ TEXEL_FETCH(sGpuBufferF, uv, 0, ivec2(5, 0))
-+ );
-+}
-+
- vec4 fetch_from_gpu_buffer_1f(HIGHP_FS_ADDRESS int address) {
- ivec2 uv = get_gpu_buffer_uv(address);
- return texelFetch(sGpuBufferF, uv, 0);
-@@ -69,6 +80,11 @@ vec4[5] fetch_from_gpu_buffer_5f(HIGHP_FS_ADDRESS int address) {
- return fetch_from_gpu_buffer_5f_direct(uv);
- }
-
-+vec4[6] fetch_from_gpu_buffer_6f(HIGHP_FS_ADDRESS int address) {
-+ ivec2 uv = get_gpu_buffer_uv(address);
-+ return fetch_from_gpu_buffer_6f_direct(uv);
-+}
-+
- vec4 fetch_from_gpu_buffer_1f_direct(ivec2 uv) {
- return texelFetch(sGpuBufferF, uv, 0);
- }
+ // Avoid dividing by zero
diff --git a/gfx/wr/webrender/res/ps_quad_box_shadow.glsl b/gfx/wr/webrender/res/ps_quad_box_shadow.glsl
-index 3fb233cb17..baf1d92e7c 100644
+index 1153b21193..73514b7c4e 100644
--- a/gfx/wr/webrender/res/ps_quad_box_shadow.glsl
+++ b/gfx/wr/webrender/res/ps_quad_box_shadow.glsl
-@@ -181,7 +181,8 @@ vec4 pattern_fragment(vec4 base_color) {
- vec4(c_br, elem_shape.z == 1.0 ? inverse_radii_squared(r_br) : inverse_radii(r_br)),
- vec4(c_bl, elem_shape.w == 1.0 ? inverse_radii_squared(r_bl) : inverse_radii(r_bl)),
- elem_bounds,
-- elem_shape
-+ elem_shape,
-+ vec4(0.0)
+@@ -51,7 +51,9 @@ flat varying highp vec4 vElemCenter_Radius_TR;
+ flat varying highp vec4 vElemCenter_Radius_BR;
+ flat varying highp vec4 vElemCenter_Radius_BL;
+
++#ifdef WR_FEATURE_SUPERELLIPSE
+ flat varying highp vec4 vElemShape;
++#endif
+
+ #ifdef WR_VERTEX_SHADER
+
+@@ -62,7 +64,9 @@ void pattern_vertex(PrimitiveInfo info) {
+ vec4 data3 = fetch_from_gpu_buffer_1f(info.pattern_input.x + 3);
+ vec4 data4 = fetch_from_gpu_buffer_1f(info.pattern_input.x + 4);
+ vec4 data5 = fetch_from_gpu_buffer_1f(info.pattern_input.x + 5);
++#ifdef WR_FEATURE_SUPERELLIPSE
+ vec4 data6 = fetch_from_gpu_buffer_1f(info.pattern_input.x + 6);
++#endif
+
+ vec2 alloc_size = data0.xy;
+ vec2 dest_rect_size = data0.zw;
+@@ -86,7 +90,7 @@ void pattern_vertex(PrimitiveInfo info) {
+ // allocation is rounded up from content_device_size, so this edge always lies
+ // inside the entry; using it keeps the mapping stable to sub-texel precision as
+ // the blur animates (bug 2002194).
+- vec2 content_device_size = data6.xy;
++ vec2 content_device_size = data5.xy;
+ vec2 texture_size = vec2(TEX_SIZE(sColor0));
+ vec2 uv_p0 = info.segment.uv_rect.p0;
+ vec2 uv_p1 = uv_p0 + content_device_size;
+@@ -112,7 +116,9 @@ void pattern_vertex(PrimitiveInfo info) {
+ vElemCenter_Radius_BR = vec4(elem_p1 - r_br, r_br);
+ vElemCenter_Radius_BL = vec4(elem_p0.x + r_bl.x, elem_p1.y - r_bl.y, r_bl);
+
+- vElemShape = data5;
++#ifdef WR_FEATURE_SUPERELLIPSE
++ vElemShape = data6;
++#endif
+ }
+
+ #endif
+@@ -172,10 +178,11 @@ vec4 pattern_fragment(vec4 base_color) {
+ // Reconstruct the element rect bounds from the TL and BR corner data.
+ vec4 elem_bounds = vec4(c_tl - r_tl, c_br + r_br);
+
+- vec4 elem_shape = vElemShape;
+-
+ float elem_dist;
++#ifdef WR_FEATURE_SUPERELLIPSE
++ vec4 elem_shape = vElemShape;
+ if (elem_shape == vec4(1.0)) {
++#endif
+ elem_dist = distance_to_rounded_rect(
+ local_pos,
+ elem_plane_tl, vec4(c_tl, inverse_radii_squared(r_tl)),
+@@ -184,6 +191,7 @@ vec4 pattern_fragment(vec4 base_color) {
+ elem_plane_bl, vec4(c_bl, inverse_radii_squared(r_bl)),
+ elem_bounds
+ );
++#ifdef WR_FEATURE_SUPERELLIPSE
+ } else {
+ elem_dist = distance_to_shaped_rect(
+ local_pos,
+@@ -196,6 +204,7 @@ vec4 pattern_fragment(vec4 base_color) {
+ vec4(0.0)
);
}
++#endif
+ // Outset (inset=0): dist < 0 = inside element → should be clipped out → use -elem_dist.
+ // Inset (inset=1): dist < 0 = inside element → should be kept → use elem_dist.
diff --git a/gfx/wr/webrender/res/ps_quad_mask.glsl b/gfx/wr/webrender/res/ps_quad_mask.glsl
-index 47e79f7c4d..e065fff4f7 100644
+index 6949a343a1..351f300fbc 100644
--- a/gfx/wr/webrender/res/ps_quad_mask.glsl
+++ b/gfx/wr/webrender/res/ps_quad_mask.glsl
-@@ -22,6 +22,7 @@ flat varying highp vec4 vClipPlane_B;
+@@ -21,8 +21,10 @@ flat varying highp vec4 vClipPlane_A;
+ flat varying highp vec4 vClipPlane_B;
flat varying highp vec4 vClipPlane_C;
++#ifdef WR_FEATURE_SUPERELLIPSE
flat varying highp vec4 vClipShape;
-+flat varying highp vec4 vClipClampedInset;
+ flat varying highp vec4 vClipClampedInset;
++#endif
#endif
flat varying highp vec2 vClipMode;
-@@ -42,6 +43,7 @@ struct Clip {
+@@ -42,8 +44,10 @@ struct Clip {
+ vec4 radii_top;
vec4 radii_bottom;
++#ifdef WR_FEATURE_SUPERELLIPSE
vec4 shape;
-+ vec4 inset;
+ vec4 inset;
++#endif
#endif
float mode;
int space;
-@@ -58,23 +60,30 @@ Clip fetch_clip(int index) {
+@@ -60,18 +64,25 @@ Clip fetch_clip(int index) {
clip.radii = texels[1];
clip.mode = texels[2].x;
#else
-- vec4 texels[5] = fetch_from_gpu_buffer_5f(index);
-+ vec4 texels[6] = fetch_from_gpu_buffer_6f(index);
++#ifdef WR_FEATURE_SUPERELLIPSE
+ vec4 texels[6] = fetch_from_gpu_buffer_6f(index);
++#else
++ vec4 texels[4] = fetch_from_gpu_buffer_4f(index);
++#endif
clip.rect = RectWithEndpoint(texels[0].xy, texels[0].zw);
clip.radii_top = texels[1];
clip.radii_bottom = texels[2];
clip.mode = texels[3].x;
++#ifdef WR_FEATURE_SUPERELLIPSE
clip.shape = texels[4];
-+ clip.inset = texels[5];
+ clip.inset = texels[5];
++#endif
#endif
return clip;
}
--vec4 precalc_corner(vec2 center, vec2 radii, float k) {
-+vec4 precalc_corner(vec2 center, vec2 radii, vec2 inset, vec2 clip_sign, float k) {
++#ifdef WR_FEATURE_SUPERELLIPSE
+ vec4 precalc_corner(vec2 center, vec2 radii, vec2 inset, vec2 clip_sign, float k) {
if (k == 1.0) {
// round/ellipse corner, precalc the ellipse parameters
- return vec4(center, inverse_radii_squared(radii));
- } else {
- // superellipse, precalc the superellipse parameters
-+ if (k < 1.0) {
-+ vec2 reference_radii = (radii == vec2(0.0)) ? vec2(0.0) : radii + inset;
-+ vec4 offset_radii = compute_contoured_superellipse(reference_radii, k, inset);
-+ center += offset_radii.xy * clip_sign;
-+ radii = offset_radii.zw;
-+ }
+@@ -87,6 +98,7 @@ vec4 precalc_corner(vec2 center, vec2 radii, vec2 inset, vec2 clip_sign, float k
return vec4(center, inverse_radii(radii));
}
}
-@@ -116,20 +125,28 @@ void pattern_vertex(PrimitiveInfo prim_info) {
++#endif
+ void pattern_vertex(PrimitiveInfo prim_info) {
+
+@@ -123,6 +135,7 @@ void pattern_vertex(PrimitiveInfo prim_info) {
+ vec2 r_br = clip.radii_bottom.zw;
+ vec2 r_bl = clip.radii_bottom.xy;
+
++#ifdef WR_FEATURE_SUPERELLIPSE
vClipCenter_Radius_TL = precalc_corner(clip.rect.p0 + r_tl,
r_tl,
-+ clip.inset.wx,
-+ vec2(-1.0, -1.0),
- clip.shape.x);
-
- vClipCenter_Radius_TR = precalc_corner(vec2(clip.rect.p1.x - r_tr.x,
- clip.rect.p0.y + r_tr.y),
- r_tr,
-+ clip.inset.yx,
-+ vec2(1.0, -1.0),
- clip.shape.y);
-
- vClipCenter_Radius_BR = precalc_corner(clip.rect.p1 - r_br,
- r_br,
-+ clip.inset.yz,
-+ vec2(1.0, 1.0),
- clip.shape.z);
-
- vClipCenter_Radius_BL = precalc_corner(vec2(clip.rect.p0.x + r_bl.x,
- clip.rect.p1.y - r_bl.y),
- r_bl,
-+ clip.inset.wz,
-+ vec2(-1.0, 1.0),
+ clip.inset.wx,
+@@ -148,6 +161,21 @@ void pattern_vertex(PrimitiveInfo prim_info) {
+ clip.inset.wz,
+ vec2(-1.0, 1.0),
clip.shape.w);
++#else
++ vClipCenter_Radius_TL = vec4(clip.rect.p0 + r_tl,
++ inverse_radii_squared(r_tl));
++
++ vClipCenter_Radius_TR = vec4(clip.rect.p1.x - r_tr.x,
++ clip.rect.p0.y + r_tr.y,
++ inverse_radii_squared(r_tr));
++
++ vClipCenter_Radius_BR = vec4(clip.rect.p1 - r_br,
++ inverse_radii_squared(r_br));
++
++ vClipCenter_Radius_BL = vec4(clip.rect.p0.x + r_bl.x,
++ clip.rect.p1.y - r_bl.y,
++ inverse_radii_squared(r_bl));
++#endif
// We need to know the half-spaces of the corners separate from the center
-@@ -156,6 +173,7 @@ void pattern_vertex(PrimitiveInfo prim_info) {
+ // and radius. We compute a point that falls on the diagonal (which is just
+@@ -172,9 +200,11 @@ void pattern_vertex(PrimitiveInfo prim_info) {
+ vClipPlane_B = vec4(tr.y, tr.z, br.x, br.y);
vClipPlane_C = vec4(br.z, bl.x, bl.y, bl.z);
++#ifdef WR_FEATURE_SUPERELLIPSE
vClipShape = clip.shape;
-+ vClipClampedInset = min(clip.inset, 0.0);
+ vClipClampedInset = min(clip.inset, 0.0);
#endif
++#endif
}
-@@ -210,7 +228,8 @@ vec4 pattern_fragment(vec4 _base_color) {
- vClipCenter_Radius_BR,
+ #endif
+@@ -207,7 +237,9 @@ vec4 pattern_fragment(vec4 _base_color) {
+ vec3 plane_bl = vec3(vClipPlane_C.y, vClipPlane_C.z, vClipPlane_C.w);
+
+ float dist;
++#ifdef WR_FEATURE_SUPERELLIPSE
+ if (vClipShape == vec4(1.0)) {
++#endif
+ dist = distance_to_rounded_rect(
+ clip_local_pos,
+ plane_tl,
+@@ -220,6 +252,7 @@ vec4 pattern_fragment(vec4 _base_color) {
vClipCenter_Radius_BL,
- vTransformBounds,
-- vClipShape
-+ vClipShape,
-+ vClipClampedInset
+ vTransformBounds
+ );
++#ifdef WR_FEATURE_SUPERELLIPSE
+ } else {
+ dist = distance_to_shaped_rect(
+ clip_local_pos,
+@@ -232,6 +265,7 @@ vec4 pattern_fragment(vec4 _base_color) {
+ vClipClampedInset
);
}
++#endif
#endif
+
+ // Compute AA for the given dist and range.
+@@ -286,8 +320,26 @@ void swgl_drawSpanR8() {
+ vec4 clip_dist =
+ mix(clip_rect, clip_rect.zwxy, lessThan(local_step, vec2(0.0)).xyxy)
+ - local_pos0.xyxy;
++
++ // Half-width, in local units, of the band around an edge over which
++ // distance_aa() produces partial coverage.
++ float aa_band = 0.5 * recip(aa_range);
++ // An axis the span does not step along has the same distance to its two
++ // edges for every pixel of the span, so the inside/outside test below
++ // cannot resolve an edge that runs parallel to the span and coverage ends
++ // up quantized to whole spans. Grow the rect by the AA band so that a span
++ // landing inside the band counts as inside, which leaves the corner
++ // selection below unchanged, and remember that the span is only partially
++ // covered so the opaque run can be dropped further down.
++ // `inset` is the distance from the span to the nearer of an axis' two
++ // edges, positive when the span is between them.
++ vec2 inset = min(-clip_dist.xy, clip_dist.zw);
++ bvec2 flat_axis = equal(local_step, vec2(0.0));
++ bvec2 partial = lessThan(inset, vec2(aa_band));
++ bool aa_span = (flat_axis.x && partial.x) || (flat_axis.y && partial.y);
++
+ clip_dist =
+- mix(1.0e6 * step(0.0, clip_dist),
++ mix(1.0e6 * step(0.0, clip_dist - vec4(aa_band, aa_band, -aa_band, -aa_band)),
+ clip_dist * recip(local_step).xyxy,
+ notEqual(local_step, vec2(0.0)).xyxy);
+
+@@ -342,9 +394,9 @@ void swgl_drawSpanR8() {
+ #else
+ // The span fast path only handles elliptical corners. For superellipse
+ // shapes, bail out and let SWGL fall back to the fragment shader.
+- if (vClipShape != vec4(1.0)) {
++ #ifdef WR_FEATURE_SUPERELLIPSE
+ return;
+- }
++ #endif
+
+ // Unpack the corner half-spaces packed into vClipPlane_A/B/C.
+ vec3 plane_tl = vec3(vClipPlane_A.x, vClipPlane_A.y, vClipPlane_A.z);
+@@ -372,6 +424,12 @@ void swgl_drawSpanR8() {
+ aa_start -= aa_margin;
+ aa_end += aa_margin;
+
++ if (aa_span) {
++ // No pixel of the span is fully covered, so collapse the opaque run and
++ // let the anti-aliased runs on either side of it cover the whole span.
++ opaque_end = min(opaque_end, opaque_start);
++ }
++
+ ivec4 steps = ivec4(clamp(
+ swgl_SpanLength -
+ swgl_StepSize *
+diff --git a/gfx/wr/webrender/src/batch.rs b/gfx/wr/webrender/src/batch.rs
+index e137afd807..eb5de7a9fb 100644
+--- a/gfx/wr/webrender/src/batch.rs
++++ b/gfx/wr/webrender/src/batch.rs
+@@ -1225,9 +1225,11 @@ impl BatchBuilder {
+ #[cfg_attr(feature = "replay", derive(Deserialize))]
+ pub struct ClipMaskInstanceList {
+ pub mask_instances_fast: FrameVec,
++ pub mask_instances_superellipse: FrameVec,
+ pub mask_instances_slow: FrameVec,
+
+ pub mask_instances_fast_with_scissor: FastHashMap>,
++ pub mask_instances_superellipse_with_scissor: FastHashMap>,
+ pub mask_instances_slow_with_scissor: FastHashMap>,
+
+ pub image_mask_instances: FastHashMap>,
+@@ -1238,8 +1240,10 @@ impl ClipMaskInstanceList {
+ pub fn new(memory: &FrameMemory) -> Self {
+ ClipMaskInstanceList {
+ mask_instances_fast: memory.new_vec(),
++ mask_instances_superellipse: memory.new_vec(),
+ mask_instances_slow: memory.new_vec(),
+ mask_instances_fast_with_scissor: FastHashMap::default(),
++ mask_instances_superellipse_with_scissor: FastHashMap::default(),
+ mask_instances_slow_with_scissor: FastHashMap::default(),
+ image_mask_instances: FastHashMap::default(),
+ image_mask_instances_with_scissor: FastHashMap::default(),
+@@ -1251,16 +1255,20 @@ impl ClipMaskInstanceList {
+ // a new member is added.
+ let ClipMaskInstanceList {
+ mask_instances_fast,
++ mask_instances_superellipse,
+ mask_instances_slow,
+ mask_instances_fast_with_scissor,
++ mask_instances_superellipse_with_scissor,
+ mask_instances_slow_with_scissor,
+ image_mask_instances,
+ image_mask_instances_with_scissor,
+ } = self;
+
+ mask_instances_fast.is_empty()
++ && mask_instances_superellipse.is_empty()
+ && mask_instances_slow.is_empty()
+ && mask_instances_fast_with_scissor.is_empty()
++ && mask_instances_superellipse_with_scissor.is_empty()
+ && mask_instances_slow_with_scissor.is_empty()
+ && image_mask_instances.is_empty()
+ && image_mask_instances_with_scissor.is_empty()
diff --git a/gfx/wr/webrender/src/border.rs b/gfx/wr/webrender/src/border.rs
-index dc456e85c3..2a591da08f 100644
+index eb9381f7d1..b6158fde38 100644
--- a/gfx/wr/webrender/src/border.rs
+++ b/gfx/wr/webrender/src/border.rs
-@@ -55,6 +55,8 @@ pub struct BorderSegmentCacheKey {
- pub size: LayoutSizeAu,
- pub radius: LayoutSizeAu,
- pub shape: u32,
-+ pub shape_offset: LayoutSizeAu,
-+ pub inset: LayoutSizeAu,
- pub side0: BorderSideAu,
- pub side1: BorderSideAu,
- pub segment: BorderSegment,
-@@ -560,22 +562,79 @@ pub fn create_border_segments(
- widths.left - overlap.width / 2.0,
- );
+@@ -857,10 +857,13 @@ fn add_segment(
+ v_adjacent_corner_radius: DeviceSize,
+ gpu_buffer_builder: &mut GpuBufferBuilderF,
+ ) {
++ let superellipse = shape != 1.0;
++
+ let base_flags = (segment as i32) |
+ ((style0 as i32) << 8) |
+ ((style1 as i32) << 16) |
+- ((do_aa as i32) << 28);
++ ((do_aa as i32) << 28) |
++ ((superellipse as i32) << 29);
-+ let inset_tl = LayoutSize::new(border.inset.left, border.inset.top);
-+ let inset_tr = LayoutSize::new(border.inset.right, border.inset.top);
-+ let inset_br = LayoutSize::new(border.inset.right, border.inset.bottom);
-+ let inset_bl = LayoutSize::new(border.inset.left, border.inset.bottom);
-+
-+ let max_shape_offsets = LayoutSideOffsets::new(
-+ rect.width() - border.radius.top_left.width - border.radius.top_right.width,
-+ rect.height() - border.radius.top_right.height - border.radius.bottom_right.height,
-+ rect.width() - border.radius.bottom_left.width - border.radius.bottom_right.width,
-+ rect.height() - border.radius.top_left.height - border.radius.bottom_left.height,
-+ );
-+
-+ let shape_offset_tl = if border.radius.shape_top_left < 1.0 {
-+ LayoutSize::new(
-+ non_overlapping_widths.top + inset_tl.height.max(0.0),
-+ non_overlapping_widths.left + inset_tl.width.max(0.0),
-+ ).min(LayoutSize::new(
-+ max_shape_offsets.top,
-+ max_shape_offsets.left,
-+ ))
-+ } else {
-+ LayoutSize::zero()
-+ };
-+ let shape_offset_tr = if border.radius.shape_top_right < 1.0 {
-+ LayoutSize::new(
-+ non_overlapping_widths.top + inset_tr.height.max(0.0),
-+ non_overlapping_widths.right + inset_tr.width.max(0.0),
-+ ).min(LayoutSize::new(
-+ max_shape_offsets.top,
-+ max_shape_offsets.right,
-+ ))
-+ } else {
-+ LayoutSize::zero()
-+ };
-+ let shape_offset_br = if border.radius.shape_bottom_right < 1.0 {
-+ LayoutSize::new(
-+ non_overlapping_widths.bottom + inset_br.height.max(0.0),
-+ non_overlapping_widths.right + inset_br.width.max(0.0),
-+ ).min(LayoutSize::new(
-+ max_shape_offsets.bottom,
-+ max_shape_offsets.right,
-+ ))
-+ } else {
-+ LayoutSize::zero()
-+ };
-+ let shape_offset_bl = if border.radius.shape_bottom_left < 1.0 {
-+ LayoutSize::new(
-+ non_overlapping_widths.bottom + inset_bl.height.max(0.0),
-+ non_overlapping_widths.left + inset_bl.width.max(0.0),
-+ ).min(LayoutSize::new(
-+ max_shape_offsets.bottom,
-+ max_shape_offsets.left,
-+ ))
-+ } else {
-+ LayoutSize::zero()
-+ };
-+
- let local_size_tl = LayoutSize::new(
- border.radius.top_left.width.max(widths.left),
- border.radius.top_left.height.max(widths.top),
-- );
-+ ) + shape_offset_tl;
- let local_size_tr = LayoutSize::new(
- border.radius.top_right.width.max(widths.right),
- border.radius.top_right.height.max(widths.top),
-- );
-+ ) + shape_offset_tr;
- let local_size_br = LayoutSize::new(
- border.radius.bottom_right.width.max(widths.right),
- border.radius.bottom_right.height.max(widths.bottom),
-- );
-+ ) + shape_offset_br;
- let local_size_bl = LayoutSize::new(
- border.radius.bottom_left.width.max(widths.left),
- border.radius.bottom_left.height.max(widths.bottom),
-- );
-+ ) + shape_offset_bl;
-
- let top_edge_info = get_edge_info(
- border.top.style,
-@@ -678,6 +737,8 @@ pub fn create_border_segments(
- LayoutSize::new(widths.left, widths.top),
- border.radius.top_left,
- border.radius.shape_top_left,
-+ shape_offset_tl,
-+ inset_tl,
- BorderSegment::TopLeft,
- EdgeMask::TOP | EdgeMask::LEFT,
- rect.top_right(),
-@@ -705,6 +766,8 @@ pub fn create_border_segments(
- LayoutSize::new(widths.right, widths.top),
- border.radius.top_right,
- border.radius.shape_top_right,
-+ shape_offset_tr,
-+ inset_tr,
- BorderSegment::TopRight,
- EdgeMask::TOP | EdgeMask::RIGHT,
- rect.min,
-@@ -732,6 +795,8 @@ pub fn create_border_segments(
- LayoutSize::new(widths.right, widths.bottom),
- border.radius.bottom_right,
- border.radius.shape_bottom_right,
-+ shape_offset_br,
-+ inset_br,
- BorderSegment::BottomRight,
- EdgeMask::BOTTOM | EdgeMask::RIGHT,
- rect.bottom_left(),
-@@ -759,6 +824,8 @@ pub fn create_border_segments(
- LayoutSize::new(widths.left, widths.bottom),
- border.radius.bottom_left,
- border.radius.shape_bottom_left,
-+ shape_offset_bl,
-+ inset_bl,
- BorderSegment::BottomLeft,
- EdgeMask::BOTTOM | EdgeMask::LEFT,
- rect.max,
-@@ -781,6 +848,8 @@ fn add_segment(
- widths: DeviceSize,
- radius: DeviceSize,
- shape: f32,
-+ shape_offset: DeviceSize,
-+ inset: DeviceSize,
- do_aa: bool,
- h_adjacent_corner_outer: DevicePoint,
- h_adjacent_corner_radius: DeviceSize,
-@@ -799,7 +868,9 @@ fn add_segment(
- color1: color1.premultiplied(),
- widths,
- radius,
-- shape
-+ shape,
-+ shape_offset,
-+ inset,
+ let instance_gpu_data = BorderInstanceGpuData {
+ local_rect: task_rect,
+@@ -877,7 +880,7 @@ fn add_segment(
+ task_origin: DevicePoint::zero(),
+ flags: base_flags,
+ clip_params: [0.0; 8],
+- gpu_data_address: instance_gpu_data.write(gpu_buffer_builder)
++ gpu_data_address: instance_gpu_data.write(superellipse, gpu_buffer_builder)
};
- let base_instance = BorderInstance {
-@@ -925,6 +996,8 @@ fn add_corner_segment(
- widths: LayoutSize,
- radius: LayoutSize,
- shape: f32,
-+ shape_offset: LayoutSize,
-+ inset: LayoutSize,
- segment: BorderSegment,
- edge_flags: EdgeMask,
- h_adjacent_corner_outer: LayoutPoint,
-@@ -1035,6 +1108,8 @@ fn add_corner_segment(
- segment,
- radius: radius.to_au(),
- shape: shape.to_bits(),
-+ shape_offset: shape_offset.to_au(),
-+ inset: inset.to_au(),
- size: widths.to_au(),
- h_adjacent_corner_outer: (h_corner_outer - image_rect.min).to_point().to_au(),
- h_adjacent_corner_radius: h_corner_radius.to_au(),
-@@ -1105,7 +1180,9 @@ fn add_edge_segment(
- side0: side.into(),
- side1: side.into(),
- radius: LayoutSizeAu::zero(),
-- shape: 0,
-+ shape: 1.0f32.to_bits(),
-+ shape_offset: LayoutSizeAu::zero(),
-+ inset: LayoutSizeAu::zero(),
- size: size.to_au(),
- segment,
- h_adjacent_corner_outer: LayoutPointAu::zero(),
-@@ -1156,12 +1233,20 @@ pub fn build_border_instances(
- let widths = (LayoutSize::from_au(cache_key.size) * scale).ceil();
- let radius = (LayoutSize::from_au(cache_key.radius) * scale).ceil();
- let shape = f32::from_bits(cache_key.shape);
-+ let shape_offset = (LayoutSize::from_au(cache_key.shape_offset) * scale).ceil();
-+ let inset = (LayoutSize::from_au(cache_key.inset) * scale).ceil();
-
- let h_corner_outer = (LayoutPoint::from_au(cache_key.h_adjacent_corner_outer) * scale).round();
- let h_corner_radius = (LayoutSize::from_au(cache_key.h_adjacent_corner_radius) * scale).ceil();
- let v_corner_outer = (LayoutPoint::from_au(cache_key.v_adjacent_corner_outer) * scale).round();
- let v_corner_radius = (LayoutSize::from_au(cache_key.v_adjacent_corner_radius) * scale).ceil();
-
-+ let shape_offset = if shape < 1.0 {
-+ radius.max(widths) + shape_offset
-+ } else {
-+ DeviceSize::zero()
-+ };
-+
- add_segment(
- DeviceRect::from_size(cache_size.to_f32()),
- style0,
-@@ -1173,6 +1258,8 @@ pub fn build_border_instances(
- widths,
- radius,
- shape,
-+ shape_offset,
-+ inset,
- border.do_aa,
- h_corner_outer,
- h_corner_radius,
-diff --git a/gfx/wr/webrender/src/box_shadow.rs b/gfx/wr/webrender/src/box_shadow.rs
-index 2e83acb366..27e9f9b9e1 100644
---- a/gfx/wr/webrender/src/box_shadow.rs
-+++ b/gfx/wr/webrender/src/box_shadow.rs
-@@ -121,6 +121,7 @@ pub struct BoxShadowCacheKey {
- pub shape_bottom_left: u32,
- pub shape_bottom_right: u32,
- pub device_pixel_scale: Au,
-+ pub spread_amount: u32,
- }
-
- impl<'a> SceneBuilder<'a> {
-diff --git a/gfx/wr/webrender/src/clip.rs b/gfx/wr/webrender/src/clip.rs
-index 11e279eab9..d26d421b5d 100644
---- a/gfx/wr/webrender/src/clip.rs
-+++ b/gfx/wr/webrender/src/clip.rs
-@@ -879,7 +879,7 @@ impl ClipTreeBuilder {
-
- match clip_info.key.kind {
- ClipItemKeyKind::Rectangle(ClipMode::Clip) => {}
-- ClipItemKeyKind::RoundedRectangle(_, ClipMode::Clip) => {
-+ ClipItemKeyKind::RoundedRectangle(_, _, ClipMode::Clip) => {
- if !spatial_tree.is_root_coord_system(clip_entry.spatial_node_index) {
- return false;
- }
-@@ -1123,9 +1123,10 @@ impl From for ClipNode {
- ClipItemKeyKind::Rectangle(mode) => {
- ClipItemKind::Rectangle { mode }
- }
-- ClipItemKeyKind::RoundedRectangle(radius, mode) => {
-+ ClipItemKeyKind::RoundedRectangle(radius, inset, mode) => {
- ClipItemKind::RoundedRectangle {
- radius: radius.into(),
-+ inset: LayoutSideOffsets::from_au(inset),
- mode,
- }
- }
-@@ -1651,10 +1652,10 @@ impl ClipStore {
- // Normal Clip rects are already handled by the clip-chain pic_coverage_rect,
- // no need to do anything here
- ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {}
-- ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, radius } => {
-+ ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, radius, inset } => {
- // Get an inner rect for the rounded-rect clip
- let radius = clamped_radius(&radius, clip_instance.clip_rect.size());
-- let local_inner_rect = match extract_inner_rect_safe(&clip_instance.clip_rect, &radius) {
-+ let local_inner_rect = match extract_inner_rect_safe(&clip_instance.clip_rect, &radius, &inset) {
- Some(rect) => rect,
- None => return None,
- };
-@@ -1881,7 +1882,7 @@ impl Default for ClipStore {
- #[cfg_attr(feature = "replay", derive(Deserialize))]
- pub enum ClipItemKeyKind {
- Rectangle(ClipMode),
-- RoundedRectangle(BorderRadiusAu, ClipMode),
-+ RoundedRectangle(BorderRadiusAu, LayoutSideOffsetsAu, ClipMode),
- ImageMask(ImageKey, Option),
- }
-
-@@ -1890,12 +1891,13 @@ impl ClipItemKeyKind {
- ClipItemKeyKind::Rectangle(mode)
- }
-
-- pub fn rounded_rect(radii: BorderRadius, mode: ClipMode) -> Self {
-+ pub fn rounded_rect(radii: BorderRadius, inset: LayoutSideOffsets, mode: ClipMode) -> Self {
- if radii.is_zero() {
- ClipItemKeyKind::rectangle(mode)
- } else {
- ClipItemKeyKind::RoundedRectangle(
- radii.into(),
-+ inset.to_au(),
- mode,
- )
- }
-@@ -1953,6 +1955,7 @@ pub enum ClipItemKind {
- },
- RoundedRectangle {
- radius: BorderRadius,
-+ inset: LayoutSideOffsets,
- mode: ClipMode,
- },
- Image {
-@@ -2024,9 +2027,9 @@ impl ClipItemKind {
- ClipItemKind::Rectangle { mode } => {
- (clip_rect, Some(clip_rect), mode)
- }
-- ClipItemKind::RoundedRectangle { ref radius, mode } => {
-+ ClipItemKind::RoundedRectangle { ref radius, ref inset, mode } => {
- let clamped = clamped_radius(radius, clip_rect.size());
-- let inner_clip_rect = extract_inner_rect_safe(&clip_rect, &clamped);
-+ let inner_clip_rect = extract_inner_rect_safe(&clip_rect, &clamped, &inset);
- (clip_rect, inner_clip_rect, mode)
- }
- ClipItemKind::Image { .. } => {
-@@ -2104,7 +2107,7 @@ impl ClipItemKind {
- }
- }
- }
-- ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip } => {
-+ ClipItemKind::RoundedRectangle { ref radius, inset: _, mode: ClipMode::Clip } => {
- let rect = clip_rect;
- let radius = clamped_radius(radius, rect.size());
- // TODO(gw): Consider caching this in the ClipNode
-@@ -2122,7 +2125,7 @@ impl ClipItemKind {
- }
- }
- }
-- ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::ClipOut } => {
-+ ClipItemKind::RoundedRectangle { ref radius, inset: _, mode: ClipMode::ClipOut } => {
- let rect = clip_rect;
- let radius = clamped_radius(radius, rect.size());
- // TODO(gw): Consider caching this in the ClipNode
-diff --git a/gfx/wr/webrender/src/composite.rs b/gfx/wr/webrender/src/composite.rs
-index a69214fb4a..ca899dea4b 100644
---- a/gfx/wr/webrender/src/composite.rs
-+++ b/gfx/wr/webrender/src/composite.rs
-@@ -8,7 +8,7 @@ use api::ColorDepth;
- use crate::image_source::resolve_image;
- use crate::picture::ResolvedSurfaceTexture;
- use crate::renderer::GpuBufferBuilderF;
--use euclid::Box2D;
-+use euclid::{Box2D, SideOffsets2D};
- use crate::gpu_types::{ZBufferId, ZBufferIdGenerator};
- use crate::internal_types::{FrameAllocator, FrameMemory, FrameVec, TextureSource};
- use crate::invalidation::compare::ImageDependency;
-@@ -854,6 +854,7 @@ impl CompositeState {
- let inner_rect = match extract_inner_rect_safe(
- &clip.rect,
- &clip.radius,
-+ &SideOffsets2D::::zero()
- ) {
- Some(rect) => rect,
- None => return,
+ match segment {
diff --git a/gfx/wr/webrender/src/gpu_types.rs b/gfx/wr/webrender/src/gpu_types.rs
-index 5e949621c2..6303bb749b 100644
+index a53058b470..6373884471 100644
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
-@@ -192,16 +192,19 @@ pub struct BorderInstanceGpuData {
- pub widths: DeviceSize,
- pub radius: DeviceSize,
- pub shape: f32,
-+ pub shape_offset: DeviceSize,
-+ pub inset: DeviceSize,
+@@ -194,14 +194,17 @@ pub struct BorderInstanceGpuData {
}
impl BorderInstanceGpuData {
- pub fn write(&self, gpu_buffer_builder: &mut GpuBufferBuilderF) -> GpuBufferAddress {
-- let mut writer = gpu_buffer_builder.write_blocks(5);
-+ let mut writer = gpu_buffer_builder.write_blocks(6);
+- pub fn write(&self, gpu_buffer_builder: &mut GpuBufferBuilderF) -> GpuBufferAddress {
+- let mut writer = gpu_buffer_builder.write_blocks(6);
++ pub fn write(&self, superellipse: bool, gpu_buffer_builder: &mut GpuBufferBuilderF) -> GpuBufferAddress {
++ let block_count = if superellipse { 6 } else { 4 };
++ let mut writer = gpu_buffer_builder.write_blocks(block_count);
writer.push_one(self.local_rect);
writer.push_one(self.color0);
writer.push_one(self.color1);
writer.push_one([self.widths.width, self.widths.height, self.radius.width, self.radius.height]);
-- writer.push_one([self.shape, 0.0, 0.0, 0.0]);
-+ writer.push_one([self.shape, self.shape_offset.width, self.shape_offset.height, 0.0]);
-+ writer.push_one([self.inset.width, self.inset.height, 0.0, 0.0]);
+- writer.push_one([self.shape, self.shape_offset.width, self.shape_offset.height, 0.0]);
+- writer.push_one([self.inset.width, self.inset.height, 0.0, 0.0]);
++ if superellipse {
++ writer.push_one([self.shape, self.shape_offset.width, self.shape_offset.height, 0.0]);
++ writer.push_one([self.inset.width, self.inset.height, 0.0, 0.0]);
++ }
writer.finish()
}
-diff --git a/gfx/wr/webrender/src/hit_test.rs b/gfx/wr/webrender/src/hit_test.rs
-index 625bd74eae..81456200d6 100644
---- a/gfx/wr/webrender/src/hit_test.rs
-+++ b/gfx/wr/webrender/src/hit_test.rs
-@@ -87,7 +87,8 @@ impl HitTestClipNode {
- ClipItemKeyKind::Rectangle(mode) => {
- HitTestRegion::Rectangle(clip_rect, mode)
- }
-- ClipItemKeyKind::RoundedRectangle(radius, mode) => {
-+ ClipItemKeyKind::RoundedRectangle(radius, _, mode) => {
-+ // TODO(wsmind): implement hit-testing for corner-shape
- HitTestRegion::RoundedRectangle(clip_rect, radius.into(), mode)
- }
- ClipItemKeyKind::ImageMask(_, polygon_handle) => {
-diff --git a/gfx/wr/webrender/src/prepare.rs b/gfx/wr/webrender/src/prepare.rs
-index b21fa8d7a5..bb2993d303 100644
---- a/gfx/wr/webrender/src/prepare.rs
-+++ b/gfx/wr/webrender/src/prepare.rs
-@@ -434,15 +434,22 @@ fn prepare_prim_for_render(
-
- let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
-
-- let max_corner_width = shadow_radius.top_left.width
-+ let mut max_corner_width = shadow_radius.top_left.width
- .max(shadow_radius.bottom_left.width)
- .max(shadow_radius.top_right.width)
- .max(shadow_radius.bottom_right.width);
-- let max_corner_height = shadow_radius.top_left.height
-+ let mut max_corner_height = shadow_radius.top_left.height
- .max(shadow_radius.bottom_left.height)
- .max(shadow_radius.top_right.height)
- .max(shadow_radius.bottom_right.height);
-
-+ if shadow_data.clip_mode == BoxShadowClipMode::Inset && !shadow_radius.shapes_all_round() {
-+ // Add one extra pixel to avoid stretching the antialiasing tail
-+ // in extreme cases (e.g corner-shape: notch with a blur radius of 1px).
-+ max_corner_width -= shadow_data.spread_amount - 1.0;
-+ max_corner_height -= shadow_data.spread_amount - 1.0;
-+ }
+diff --git a/gfx/wr/webrender/src/pattern/box_shadow.rs b/gfx/wr/webrender/src/pattern/box_shadow.rs
+index 1cee87e980..6d0cb1797d 100644
+--- a/gfx/wr/webrender/src/pattern/box_shadow.rs
++++ b/gfx/wr/webrender/src/pattern/box_shadow.rs
+@@ -43,7 +43,10 @@ impl PatternBuilder for BoxShadowPatternData {
+ _ctx: &PatternBuilderContext,
+ state: &mut PatternBuilderState,
+ ) -> Pattern {
+- let mut writer = state.frame_gpu_data.f32.write_blocks(7);
++ let superellipse = !self.element_radius.shapes_all_round();
+
- let used_corner_width = max_corner_width.max(blur_region);
- let used_corner_height = max_corner_height.max(blur_region);
++ let block_count = if superellipse { 7 } else { 6 };
++ let mut writer = state.frame_gpu_data.f32.write_blocks(block_count);
+ writer.push_one([
+ self.shadow_rect_alloc_size.width,
+ self.shadow_rect_alloc_size.height,
+@@ -74,22 +77,24 @@ impl PatternBuilder for BoxShadowPatternData {
+ self.element_radius.bottom_left.width,
+ self.element_radius.bottom_left.height,
+ ]);
+- writer.push_one([
+- self.element_radius.shape_top_left,
+- self.element_radius.shape_top_right,
+- self.element_radius.shape_bottom_right,
+- self.element_radius.shape_bottom_left,
+- ]);
+ writer.push_one([
+ self.content_device_size.width,
+ self.content_device_size.height,
+ 0.0,
+ 0.0,
+ ]);
++ if superellipse {
++ writer.push_one([
++ self.element_radius.shape_top_left,
++ self.element_radius.shape_top_right,
++ self.element_radius.shape_bottom_right,
++ self.element_radius.shape_bottom_left,
++ ]);
++ }
+ let addr = writer.finish();
-@@ -514,6 +521,14 @@ fn prepare_prim_for_render(
- content_scale.0,
- );
+ Pattern {
+- kind: PatternKind::BoxShadow,
++ kind: if superellipse { PatternKind::BoxShadowSuperellipse } else { PatternKind::BoxShadow },
+ shader_input: PatternShaderInput(addr.as_int(), 0),
+ texture_input: PatternTextureInput::new(self.render_task),
+ base_color: self.color,
+diff --git a/gfx/wr/webrender/src/pattern/mod.rs b/gfx/wr/webrender/src/pattern/mod.rs
+index e3c1d9c771..8fb3d66f74 100644
+--- a/gfx/wr/webrender/src/pattern/mod.rs
++++ b/gfx/wr/webrender/src/pattern/mod.rs
+@@ -31,32 +31,33 @@ pub enum PatternKind {
-+ // We only need the spread amount (-inset) in the cache key when using other
-+ // corner shapes than 'round'
-+ let cache_key_spread_amount = if !shadow_radius.shapes_all_round() {
-+ shadow_data.spread_amount.to_bits()
-+ } else {
-+ 0
-+ };
-+
- let bs_cache_key = BoxShadowCacheKey {
- blur_radius_dp: Au::from_f32_px(blur_std_dev),
- clip_mode: shadow_data.clip_mode,
-@@ -527,6 +542,7 @@ fn prepare_prim_for_render(
- shape_bottom_right: shadow_radius.shape_bottom_right.to_bits(),
- shape_bottom_left: shadow_radius.shape_bottom_left.to_bits(),
- device_pixel_scale: Au::from_f32_px(content_scale.0),
-+ spread_amount: cache_key_spread_amount,
- };
+ Mask = 3,
+ BoxShadow = 4,
++ BoxShadowSuperellipse = 5,
+ // Variants of ColorOrTexture that use a non-default sampler type
+ // (samplerExternalOES / __samplerExternal2DY2YEXT / sampler2DRect). The
+ // quad shader is compiled in matching per-kind variants; see
+ // ps_quad_textured.glsl.
+- TextureExternal = 5,
+- TextureExternalBT709 = 6,
+- TextureRect = 7,
++ TextureExternal = 6,
++ TextureExternalBT709 = 7,
++ TextureRect = 8,
+ // Samples up to three planes (sColor0/1/2) and converts from YUV to RGB.
+ // Like ColorOrTexture, the YUV pattern comes in sampler-type-specific
+ // variants so that the planes are sampled with the matching sColor
+ // declaration; see ps_quad_yuv.glsl. `Yuv` is the default (TEXTURE_2D).
+- Yuv = 8,
+- YuvTextureExternal = 9,
+- YuvTextureExternalBT709 = 10,
+- YuvTextureRect = 11,
++ Yuv = 9,
++ YuvTextureExternal = 10,
++ YuvTextureExternalBT709 = 11,
++ YuvTextureRect = 12,
+ // Samples a captured backdrop texture using a (bilerp) 4-corner uv quad.
+- Backdrop = 12,
++ Backdrop = 13,
+ // Applies a filter to a sampled source texture; see ps_quad_blend.glsl.
+- Blend = 13,
++ Blend = 14,
+ // Composites a picture over its backdrop with a software mix-blend-mode;
+ // samples two textures (backdrop + source). See ps_quad_mix_blend.glsl.
+- MixBlend = 14,
++ MixBlend = 15,
+ // When adding patterns, don't forget to update the NUM_PATTERNS constant.
+ }
- // The shadow shape is offset by blur_region within the alloc task (local pixels).
-@@ -538,6 +554,8 @@ fn prepare_prim_for_render(
- );
- let device_pixel_scale_for_task = DevicePixelScale::new(content_scale.0);
+-pub const NUM_PATTERNS: u32 = 15;
++pub const NUM_PATTERNS: u32 = 16;
-+ let shadow_inset = LayoutSideOffsets::new_all_same(-shadow_data.spread_amount);
-+
- let task_id = frame_state.resource_cache.request_render_task(
- Some(RenderTaskCacheKey {
- origin: DeviceIntPoint::zero(),
-@@ -555,6 +573,7 @@ fn prepare_prim_for_render(
- RenderTaskKind::new_rounded_rect_mask(
- minimal_shadow_rect,
- shadow_radius,
-+ shadow_inset,
- ClipMode::Clip,
- device_pixel_scale_for_task,
- ),
-@@ -2323,20 +2342,20 @@ fn write_brush_segment_description(
- continue;
- }
-
-- let (local_clip_rect, radius, mode) = match clip_node.item.kind {
-- ClipItemKind::RoundedRectangle { radius, mode } => {
-+ let (local_clip_rect, radius, inset, mode) = match clip_node.item.kind {
-+ ClipItemKind::RoundedRectangle { radius, inset, mode } => {
- let radius = clamped_radius(&radius, clip_instance.clip_rect.size());
-- (clip_instance.clip_rect, Some(radius), mode)
-+ (clip_instance.clip_rect, Some(radius), Some(inset), mode)
- }
- ClipItemKind::Rectangle { mode } => {
-- (clip_instance.clip_rect, None, mode)
-+ (clip_instance.clip_rect, None, None, mode)
- }
- ClipItemKind::Image { .. } => {
- panic!("bug: masks not supported on old segment path");
- }
- };
-
-- segment_builder.push_clip_rect(local_clip_rect, radius, mode);
-+ segment_builder.push_clip_rect(local_clip_rect, radius, inset, mode);
- }
-
- true
-diff --git a/gfx/wr/webrender/src/prim_store/borders.rs b/gfx/wr/webrender/src/prim_store/borders.rs
-index 9e93764c31..f7cb517e4a 100644
---- a/gfx/wr/webrender/src/prim_store/borders.rs
-+++ b/gfx/wr/webrender/src/prim_store/borders.rs
-@@ -541,9 +541,9 @@ fn test_struct_sizes() {
- // test expectations and move on.
- // (b) You made a structure larger. This is not necessarily a problem, but should only
- // be done with care, and after checking if talos performance regresses badly.
-- assert_eq!(mem::size_of::(), 100, "NormalBorderPrim size changed");
-- assert_eq!(mem::size_of::(), 152, "NormalBorderTemplate size changed");
-- assert_eq!(mem::size_of::(), 104, "NormalBorderKey size changed");
-+ assert_eq!(mem::size_of::(), 116, "NormalBorderPrim size changed");
-+ assert_eq!(mem::size_of::(), 168, "NormalBorderTemplate size changed");
-+ assert_eq!(mem::size_of::(), 120, "NormalBorderKey size changed");
- assert_eq!(mem::size_of::(), 68, "ImageBorder size changed");
- assert_eq!(mem::size_of::(), 72, "ImageBorderTemplate size changed");
- assert_eq!(mem::size_of::(), 72, "ImageBorderKey size changed");
+ impl PatternKind {
+ pub fn from_u32(val: u32) -> Self {
diff --git a/gfx/wr/webrender/src/quad.rs b/gfx/wr/webrender/src/quad.rs
-index e07ec25550..fabe4ed46d 100644
+index 1a5d92bf6d..185f6e3ee6 100644
--- a/gfx/wr/webrender/src/quad.rs
+++ b/gfx/wr/webrender/src/quad.rs
-@@ -3,7 +3,7 @@
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
- use api::{BorderRadius, ClipMode, ColorF, units::*};
--use euclid::{Scale, point2};
-+use euclid::{Scale, SideOffsets2D, Size2D, point2};
-
- use crate::ItemUid;
- use crate::border::NinePatchDescriptorExt;
-@@ -1207,7 +1207,7 @@ fn prepare_tiles(
- let rect = transform.map_rect(&clip_instance.clip_rect);
- scratch.retained.quad_tile_classifier.add_clip_rect(rect, mode, applied_as_local_clip);
- }
-- ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, ref radius } => {
-+ ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, ref radius, ref inset } => {
- // For rounded-rects with Clip mode, we need a mask for each corner,
- // and to add the clip rect itself (to cull tiles outside that rect)
-
-@@ -1222,31 +1222,54 @@ fn prepare_tiles(
- let r_br = transform.map_size(&radius.bottom_right).abs();
- let r_bl = transform.map_size(&radius.bottom_left).abs();
-
-+ let inset: SideOffsets2D = transform.map_side_offsets(&inset);
-+
-+ let i_tl = if radius.shape_top_left < 1.0 {
-+ Size2D::new(inset.top, inset.left)
-+ } else {
-+ Size2D::zero()
-+ };
-+ let i_tr = if radius.shape_top_right < 1.0 {
-+ Size2D::new(inset.top, inset.right)
-+ } else {
-+ Size2D::zero()
-+ };
-+ let i_br = if radius.shape_bottom_right < 1.0 {
-+ Size2D::new(inset.bottom, inset.right)
-+ } else {
-+ Size2D::zero()
-+ };
-+ let i_bl = if radius.shape_bottom_left < 1.0 {
-+ Size2D::new(inset.bottom, inset.left)
-+ } else {
-+ Size2D::zero()
-+ };
-+
- // Construct the mask regions for each corner
- let c_tl = DeviceRect::from_origin_and_size(
- clip_device_rect.min,
-- r_tl,
-+ r_tl + i_tl,
- );
- let c_tr = DeviceRect::from_origin_and_size(
- DevicePoint::new(
- clip_device_rect.max.x - r_tr.width,
- clip_device_rect.min.y,
- ),
-- r_tr,
-+ r_tr + i_tr,
- );
- let c_br = DeviceRect::from_origin_and_size(
- DevicePoint::new(
- clip_device_rect.max.x - r_br.width,
- clip_device_rect.max.y - r_br.height,
- ),
-- r_br,
-+ r_br + i_br,
- );
- let c_bl = DeviceRect::from_origin_and_size(
- DevicePoint::new(
- clip_device_rect.min.x,
- clip_device_rect.max.y - r_bl.height,
- ),
-- r_bl,
-+ r_bl + i_bl,
- );
-
- scratch.retained.quad_tile_classifier.add_clip_rect(clip_device_rect, ClipMode::Clip, applied_as_local_clip);
-@@ -1255,11 +1278,11 @@ fn prepare_tiles(
- scratch.retained.quad_tile_classifier.add_mask_region(c_br);
- scratch.retained.quad_tile_classifier.add_mask_region(c_bl);
- }
-- ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, ref radius } => {
-+ ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, ref radius , ref inset } => {
- let radius = clamped_radius(radius, clip_instance.clip_rect.size());
- // Try to find an inner rect within the clip-out rounded rect that we can
- // use to cull inner tiles. If we can't, the entire rect needs to be masked
-- match extract_inner_rect_k(&clip_instance.clip_rect, &radius, 0.5) {
-+ match extract_inner_rect_k(&clip_instance.clip_rect, &radius, &inset, 0.5) {
- Some(ref inner_rect) => {
- let rect = transform.map_rect(inner_rect);
- scratch.retained.quad_tile_classifier.add_clip_rect(rect, ClipMode::ClipOut, false);
-@@ -1419,7 +1442,7 @@ fn get_prim_render_strategy(
- let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, 0);
- let clip_node = &interned_clips[clip_instance.handle];
-
-- if let ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip, .. } = clip_node.item.kind {
-+ if let ClipItemKind::RoundedRectangle { ref radius, ref inset, mode: ClipMode::Clip } = clip_node.item.kind {
- let size = clip_instance.clip_rect.size();
- let radius = clamped_radius(radius, size);
- let max_corner_width = radius.top_left.width
-@@ -1430,9 +1453,28 @@ fn get_prim_render_strategy(
- .max(radius.bottom_left.height)
- .max(radius.top_right.height)
- .max(radius.bottom_right.height);
-+
-+ let mut max_inset_width = 0.0f32;
-+ let mut max_inset_height = 0.0f32;
-+ if radius.shape_top_left < 1.0 {
-+ max_inset_width = max_inset_width.max(inset.top);
-+ max_inset_height = max_inset_height.max(inset.left);
-+ }
-+ if radius.shape_top_right < 1.0 {
-+ max_inset_width = max_inset_width.max(inset.top);
-+ max_inset_height = max_inset_height.max(inset.right);
-+ }
-+ if radius.shape_bottom_right < 1.0 {
-+ max_inset_width = max_inset_width.max(inset.bottom);
-+ max_inset_height = max_inset_height.max(inset.right);
-+ }
-+ if radius.shape_bottom_left < 1.0 {
-+ max_inset_width = max_inset_width.max(inset.bottom);
-+ max_inset_height = max_inset_height.max(inset.left);
-+ }
-
-- if max_corner_width <= 0.5 * size.width &&
-- max_corner_height <= 0.5 * size.height {
-+ if (max_corner_width + max_inset_width) <= 0.5 * size.width &&
-+ (max_corner_height + max_inset_height) <= 0.5 * size.height {
-
- let clip_prim_coords_match = spatial_tree.is_matching_coord_system(
- prim_spatial_node_index,
-@@ -1449,7 +1491,7 @@ fn get_prim_render_strategy(
-
- if let Some(clip_rect) = map_clip_to_prim.map(&clip_instance.clip_rect) {
- let radius = map_clip_to_prim.map_vector(
-- LayoutVector2D::new(max_corner_width, max_corner_height)
-+ LayoutVector2D::new(max_corner_width + max_inset_width, max_corner_height + max_inset_height)
- );
- return QuadRenderStrategy::NinePatch {
- radius,
-@@ -1776,6 +1818,7 @@ pub fn write_rounded_rect_clip_blocks(
- gpu_buffer: &mut GpuBufferBuilderF,
- clip_rect: LayoutRect,
+@@ -1809,7 +1809,7 @@ pub fn write_rounded_rect_clip_blocks(
radius: &BorderRadius,
-+ inset: LayoutSideOffsets,
+ inset: LayoutSideOffsets,
mode: ClipMode,
- ) -> (GpuBufferAddress, bool) {
+-) -> (GpuBufferAddress, bool) {
++) -> (GpuBufferAddress, bool, bool) {
let radius = clamped_radius(radius, clip_rect.size());
-@@ -1793,7 +1836,7 @@ pub fn write_rounded_rect_clip_blocks(
- (writer.finish(), true)
+ if radius.can_use_fast_path_in(&clip_rect) {
+@@ -1823,9 +1823,11 @@ pub fn write_rounded_rect_clip_blocks(
+ ]);
+ writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
+
+- (writer.finish(), true)
++ (writer.finish(), true, false)
} else {
-- let mut writer = gpu_buffer.write_blocks(5);
-+ let mut writer = gpu_buffer.write_blocks(6);
+- let mut writer = gpu_buffer.write_blocks(6);
++ let superellipse = !radius.shapes_all_round();
++ let block_count = if superellipse { 6 } else { 4 };
++ let mut writer = gpu_buffer.write_blocks(block_count);
writer.push_one(clip_rect);
writer.push_one([
radius.top_left.width,
-@@ -1814,6 +1857,7 @@ pub fn write_rounded_rect_clip_blocks(
- radius.shape_bottom_right,
- radius.shape_bottom_left,
+@@ -1840,15 +1842,17 @@ pub fn write_rounded_rect_clip_blocks(
+ radius.bottom_right.height,
]);
-+ writer.push_one(inset);
+ writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
+- writer.push_one([
+- radius.shape_top_left,
+- radius.shape_top_right,
+- radius.shape_bottom_right,
+- radius.shape_bottom_left,
+- ]);
+- writer.push_one(inset);
++ if superellipse {
++ writer.push_one([
++ radius.shape_top_left,
++ radius.shape_top_right,
++ radius.shape_bottom_right,
++ radius.shape_bottom_left,
++ ]);
++ writer.push_one(inset);
++ }
- (writer.finish(), false)
+- (writer.finish(), false)
++ (writer.finish(), false, superellipse)
}
-@@ -1835,11 +1879,12 @@ pub fn prepare_clip_task(
+ }
+
+@@ -1867,7 +1871,7 @@ pub fn prepare_clip_task(
+ rg_builder: &mut RenderTaskGraphBuilder,
sub_tasks: &mut SubTaskRange,
) {
- let (clip_address, fast_path) = match clip_item.kind {
-- ClipItemKind::RoundedRectangle { radius, mode } => {
-+ ClipItemKind::RoundedRectangle { radius, inset, mode } => {
+- let (clip_address, fast_path) = match clip_item.kind {
++ let (clip_address, fast_path, superellipse) = match clip_item.kind {
+ ClipItemKind::RoundedRectangle { radius, inset, mode } => {
write_rounded_rect_clip_blocks(
gpu_buffer,
- clip_instance.clip_rect,
- &radius,
-+ inset,
- mode,
- )
+@@ -1884,7 +1888,7 @@ pub fn prepare_clip_task(
+ writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
+ let clip_address = writer.finish();
+
+- (clip_address, true)
++ (clip_address, true, false)
}
+ ClipItemKind::Image { .. } => {
+ let transform_id = transforms.gpu.get_id_with_post_scale(
+@@ -2035,6 +2039,7 @@ pub fn prepare_clip_task(
+ clip_space,
+ needs_scissor_rect,
+ rounded_rect_fast_path: fast_path,
++ rounded_rect_superellipse: superellipse,
+ }),
+ );
+ }
diff --git a/gfx/wr/webrender/src/render_target.rs b/gfx/wr/webrender/src/render_target.rs
-index 6213555e12..cd6bd0c4cc 100644
+index d393c99a72..21853655a9 100644
--- a/gfx/wr/webrender/src/render_target.rs
+++ b/gfx/wr/webrender/src/render_target.rs
-@@ -451,6 +451,7 @@ impl RenderTarget {
+@@ -35,6 +35,7 @@ use crate::spatial_tree::SpatialNodeIndex;
+
+ const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16);
+ const STYLE_MASK: i32 = 0x00FF_FF00;
++const SUPERELLIPSE_MASK: i32 = 1 << 29;
+
+ /// A tag used to identify the output format of a `RenderTarget`.
+ #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+@@ -165,6 +166,8 @@ pub struct RenderTarget {
+
+ pub border_segments_complex: FrameVec,
+ pub border_segments_solid: FrameVec,
++ pub border_segments_complex_superellipse: FrameVec,
++ pub border_segments_solid_superellipse: FrameVec,
+ pub line_decorations: FrameVec,
+
+ // Clearing render targets has a fair amount of special cases.
+@@ -225,6 +228,8 @@ impl RenderTarget {
+ clip_masks: ClipMaskInstanceList::new(memory),
+ border_segments_complex: memory.new_vec(),
+ border_segments_solid: memory.new_vec(),
++ border_segments_complex_superellipse: memory.new_vec(),
++ border_segments_solid_superellipse: memory.new_vec(),
+ clears: memory.new_vec(),
+ line_decorations: memory.new_vec(),
+ }
+@@ -443,7 +448,7 @@ impl RenderTarget {
+
+ let device_rect = DeviceRect::from_size(target_rect.size().to_f32());
+
+- let (clip_address, fast_path) = quad::write_rounded_rect_clip_blocks(
++ let (clip_address, fast_path, superellipse) = quad::write_rounded_rect_clip_blocks(
&mut gpu_buffer_builder.f32,
region_task.clip_rect,
®ion_task.radius,
-+ region_task.inset,
- region_task.mode,
- );
+@@ -497,6 +502,8 @@ impl RenderTarget {
+ if fast_path {
+ self.clip_masks.mask_instances_fast.push(instance);
++ } else if superellipse {
++ self.clip_masks.mask_instances_superellipse.push(instance);
+ } else {
+ self.clip_masks.mask_instances_slow.push(instance);
+ }
+@@ -547,10 +554,19 @@ impl RenderTarget {
+ // TODO(gw): It may be better to store the task origin in
+ // the render task data instead of per instance.
+ instance.task_origin = task_origin;
++ let superellipse = (instance.flags & SUPERELLIPSE_MASK) != 0;
+ if instance.flags & STYLE_MASK == STYLE_SOLID {
+- self.border_segments_solid.push(instance);
++ if superellipse {
++ self.border_segments_solid_superellipse.push(instance);
++ } else {
++ self.border_segments_solid.push(instance);
++ }
+ } else {
+- self.border_segments_complex.push(instance);
++ if superellipse {
++ self.border_segments_complex_superellipse.push(instance);
++ } else {
++ self.border_segments_complex.push(instance);
++ }
+ }
+ }
+ }
+@@ -927,6 +943,11 @@ fn add_rect_clip_task_to_batch(
+ .entry(*target_rect)
+ .or_insert_with(|| memory.new_vec())
+ .push(instance);
++ } else if task.rounded_rect_superellipse {
++ results.mask_instances_superellipse_with_scissor
++ .entry(*target_rect)
++ .or_insert_with(|| memory.new_vec())
++ .push(instance);
+ } else {
+ results.mask_instances_slow_with_scissor
+ .entry(*target_rect)
+@@ -936,6 +957,8 @@ fn add_rect_clip_task_to_batch(
+ } else {
+ if task.rounded_rect_fast_path {
+ results.mask_instances_fast.push(instance);
++ } else if task.rounded_rect_superellipse {
++ results.mask_instances_superellipse.push(instance);
+ } else {
+ results.mask_instances_slow.push(instance);
+ }
diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs
-index 4759884e34..d97d6fa071 100644
+index 4501e2de8b..b4e1478192 100644
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
-@@ -170,6 +170,7 @@ pub struct ImageRequestTask {
- pub struct ClipRegionTask {
- pub clip_rect: LayoutRect,
- pub radius: BorderRadius,
-+ pub inset: LayoutSideOffsets,
- pub mode: ClipMode,
- pub device_pixel_scale: DevicePixelScale,
- }
-@@ -550,12 +551,14 @@ impl RenderTaskKind {
- pub fn new_rounded_rect_mask(
- clip_rect: LayoutRect,
- radius: BorderRadius,
-+ inset: LayoutSideOffsets,
- mode: ClipMode,
- device_pixel_scale: DevicePixelScale,
- ) -> Self {
- RenderTaskKind::ClipRegion(ClipRegionTask {
- clip_rect,
- radius,
-+ inset,
- mode,
- device_pixel_scale,
- })
-diff --git a/gfx/wr/webrender/src/renderer/composite.rs b/gfx/wr/webrender/src/renderer/composite.rs
-index bc59b72b37..276d7ad78c 100644
---- a/gfx/wr/webrender/src/renderer/composite.rs
-+++ b/gfx/wr/webrender/src/renderer/composite.rs
-@@ -806,6 +806,7 @@ impl Renderer {
- segment_builder.push_clip_rect(
- clip.rect.cast_unit(),
- Some(clip.radius),
-+ None,
- ClipMode::Clip,
- );
- segment_builder.build(|segment| {
-@@ -1060,6 +1061,7 @@ impl Renderer {
- segment_builder.push_clip_rect(
- clip.rect.cast_unit(),
- Some(clip.radius),
-+ None,
- ClipMode::Clip,
- );
- segment_builder.build(|segment| {
-diff --git a/gfx/wr/webrender/src/renderer/gpu_buffer.rs b/gfx/wr/webrender/src/renderer/gpu_buffer.rs
-index e20752544d..4cbedc3b4f 100644
---- a/gfx/wr/webrender/src/renderer/gpu_buffer.rs
-+++ b/gfx/wr/webrender/src/renderer/gpu_buffer.rs
-@@ -13,7 +13,7 @@ use crate::gpu_types::UvRectKind;
- use crate::internal_types::{FrameId, FrameMemory, FrameVec, TextureSource, TextureSourceExternal};
- use crate::renderer::MAX_VERTEX_TEXTURE_WIDTH;
- use crate::util::ScaleOffset;
--use api::units::{DeviceIntRect, DeviceIntSize, DevicePoint, DeviceRect, LayoutRect, PictureRect};
-+use api::units::{DeviceIntRect, DeviceIntSize, DevicePoint, DeviceRect, LayoutRect, LayoutSideOffsets, PictureRect};
- use api::{PremultipliedColorF, ImageFormat};
- use crate::device::Texel;
- use crate::render_task::{RenderTaskLocation, StaticRenderTaskSurface};
-@@ -196,6 +196,19 @@ impl Into for crate::quad::LayoutOrDeviceRect {
- }
+@@ -2242,6 +2242,7 @@ pub struct RectangleClipSubTask {
+ pub quad_flags: QuadFlags,
+ pub needs_scissor_rect: bool,
+ pub rounded_rect_fast_path: bool,
++ pub rounded_rect_superellipse: bool,
}
-+impl Into for LayoutSideOffsets {
-+ fn into(self) -> GpuBufferBlockF {
-+ GpuBufferBlockF {
-+ data: [
-+ self.top,
-+ self.right,
-+ self.bottom,
-+ self.left,
-+ ],
-+ }
-+ }
-+}
-+
- impl Into for ScaleOffset {
- fn into(self) -> GpuBufferBlockF {
- GpuBufferBlockF {
-diff --git a/gfx/wr/webrender/src/scene_building.rs b/gfx/wr/webrender/src/scene_building.rs
-index cb6f4c3ac6..71f058b90e 100644
---- a/gfx/wr/webrender/src/scene_building.rs
-+++ b/gfx/wr/webrender/src/scene_building.rs
-@@ -2710,6 +2710,7 @@ impl<'a> SceneBuilder<'a> {
- let item = ClipItemKey {
- kind: ClipItemKeyKind::rounded_rect(
- clip.radii,
-+ clip.inset,
- clip.mode,
- ),
- };
-diff --git a/gfx/wr/webrender/src/segment.rs b/gfx/wr/webrender/src/segment.rs
-index dda042f81f..b2e5d3e7c2 100644
---- a/gfx/wr/webrender/src/segment.rs
-+++ b/gfx/wr/webrender/src/segment.rs
-@@ -263,8 +263,8 @@ impl SegmentBuilder {
- self.inner_rect = inner_rect;
- self.bounding_rect = Some(local_rect);
-
-- self.push_clip_rect(local_rect, None, ClipMode::Clip);
-- self.push_clip_rect(local_clip_rect, None, ClipMode::Clip);
-+ self.push_clip_rect(local_rect, None, None, ClipMode::Clip);
-+ self.push_clip_rect(local_clip_rect, None, None, ClipMode::Clip);
-
- // This must be set after the push_clip_rect calls above, since we
- // want to skip segment building if those are the only clips.
-@@ -366,6 +366,7 @@ impl SegmentBuilder {
- &mut self,
- rect: LayoutRect,
- radius: Option,
-+ inset: Option,
- mode: ClipMode,
- ) {
- self.has_interesting_clips = true;
-@@ -383,7 +384,7 @@ impl SegmentBuilder {
- Some(radius) => {
- // For a rounded rect, try to create a nine-patch where there
- // is a clip item for each corner, inner and edge region.
-- match extract_inner_rect_safe(&rect, &radius) {
-+ match extract_inner_rect_safe(&rect, &radius, &inset.unwrap_or_default()) {
- Some(inner) => {
- let p0 = rect.min;
- let p1 = inner.min;
-@@ -709,7 +710,8 @@ fn emit_segment_if_needed(
- #[cfg(test)]
- mod test {
- use api::{BorderRadius, ClipMode};
-- use api::units::{LayoutPoint, LayoutRect};
-+ use api::units::{LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize};
-+
- use super::{Segment, SegmentBuilder, EdgeMask};
- use std::cmp;
-
-@@ -768,7 +770,7 @@ mod test {
- local_rect: LayoutRect,
- inner_rect: Option,
- local_clip_rect: LayoutRect,
-- clips: &[(LayoutRect, Option, ClipMode)],
-+ clips: &[(LayoutRect, Option, Option, ClipMode)],
- expected_segments: &mut [Segment]
- ) {
- let mut sb = SegmentBuilder::new();
-@@ -777,11 +779,11 @@ mod test {
- inner_rect,
- local_clip_rect,
- );
-- sb.push_clip_rect(local_rect, None, ClipMode::Clip);
-- sb.push_clip_rect(local_clip_rect, None, ClipMode::Clip);
-+ sb.push_clip_rect(local_rect, None, None, ClipMode::Clip);
-+ sb.push_clip_rect(local_clip_rect, None, None, ClipMode::Clip);
- let mut segments = Vec::new();
-- for &(rect, radius, mode) in clips {
-- sb.push_clip_rect(rect, radius, mode);
-+ for &(rect, radius, inset, mode) in clips {
-+ sb.push_clip_rect(rect, radius, inset, mode);
- }
- sb.build(|segment| {
- segments.push(Segment {
-@@ -926,8 +928,8 @@ mod test {
- None,
- rect(-1000.0, -1000.0, 1000.0, 1000.0),
- &[
-- (rect(20.0, 20.0, 40.0, 40.0), None, ClipMode::Clip),
-- (rect(40.0, 20.0, 60.0, 40.0), None, ClipMode::Clip),
-+ (rect(20.0, 20.0, 40.0, 40.0), None, None, ClipMode::Clip),
-+ (rect(40.0, 20.0, 60.0, 40.0), None, None, ClipMode::Clip),
- ],
- &mut [
- ],
-@@ -941,7 +943,7 @@ mod test {
- None,
- rect(-1000.0, -1000.0, 1000.0, 1000.0),
- &[
-- (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
-+ (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), None, ClipMode::Clip),
- ],
- &mut [
- // corners
-@@ -962,6 +964,45 @@ mod test {
- );
- }
-
-+ #[test]
-+ fn segment_rounded_clip_inset() {
-+ let radius = BorderRadius {
-+ top_left: LayoutSize::new(10.0, 10.0),
-+ top_right: LayoutSize::new(10.0, 10.0),
-+ bottom_left: LayoutSize::new(10.0, 10.0),
-+ bottom_right: LayoutSize::new(10.0, 10.0),
-+ shape_top_left: 2.0,
-+ shape_top_right: 1.0,
-+ shape_bottom_left: 0.0,
-+ shape_bottom_right: -1.0,
-+ };
-+ let inset = LayoutSideOffsets::new_all_same(5.0);
-+ seg_test(
-+ rect(0.0, 0.0, 100.0, 100.0),
-+ None,
-+ rect(-1000.0, -1000.0, 1000.0, 1000.0),
-+ &[
-+ (rect(20.0, 20.0, 60.0, 60.0), Some(radius), Some(inset), ClipMode::Clip),
-+ ],
-+ &mut [
-+ // corners
-+ seg(20.0, 20.0, 35.0, 30.0, true, Some(EdgeMask::LEFT | EdgeMask::TOP)),
-+ seg(20.0, 45.0, 35.0, 60.0, true, Some(EdgeMask::LEFT | EdgeMask::BOTTOM)),
-+ seg(45.0, 20.0, 60.0, 30.0, true, Some(EdgeMask::RIGHT | EdgeMask::TOP)),
-+ seg(45.0, 45.0, 60.0, 60.0, true, Some(EdgeMask::RIGHT | EdgeMask::BOTTOM)),
-+
-+ // inner
-+ seg(35.0, 30.0, 45.0, 45.0, false, None),
-+
-+ // edges
-+ seg(35.0, 20.0, 45.0, 30.0, false, Some(EdgeMask::TOP)),
-+ seg(35.0, 45.0, 45.0, 60.0, false, Some(EdgeMask::BOTTOM)),
-+ seg(20.0, 30.0, 35.0, 45.0, false, Some(EdgeMask::LEFT)),
-+ seg(45.0, 30.0, 60.0, 45.0, false, Some(EdgeMask::RIGHT)),
-+ ],
-+ );
-+ }
-+
- #[test]
- fn segment_clip_out() {
- seg_test(
-@@ -969,7 +1010,7 @@ mod test {
- None,
- rect(-1000.0, -1000.0, 2000.0, 2000.0),
- &[
-- (rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::ClipOut),
-+ (rect(20.0, 20.0, 60.0, 60.0), None, None, ClipMode::ClipOut),
- ],
- &mut [
- seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeMask::TOP | EdgeMask::LEFT)),
-@@ -993,7 +1034,7 @@ mod test {
- None,
- rect(-1000.0, -1000.0, 2000.0, 2000.0),
- &[
-- (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::ClipOut),
-+ (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), None, ClipMode::ClipOut),
- ],
- &mut [
- // top row
-@@ -1036,8 +1077,8 @@ mod test {
- None,
- rect(-1000.0, -1000.0, 2000.0, 2000.0),
- &[
-- (rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::Clip),
-- (rect(50.0, 50.0, 80.0, 80.0), None, ClipMode::ClipOut),
-+ (rect(20.0, 20.0, 60.0, 60.0), None, None, ClipMode::Clip),
-+ (rect(50.0, 50.0, 80.0, 80.0), None, None, ClipMode::ClipOut),
- ],
- &mut [
- seg(20.0, 20.0, 50.0, 50.0, false, Some(EdgeMask::LEFT | EdgeMask::TOP)),
-@@ -1054,8 +1095,8 @@ mod test {
- None,
- rect(0.0, 0.0, 100.0, 100.0),
- &[
-- (rect(0.0, 0.0, 10.0, 10.0), None, ClipMode::ClipOut),
-- (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
-+ (rect(0.0, 0.0, 10.0, 10.0), None, None, ClipMode::ClipOut),
-+ (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), None, ClipMode::Clip),
- ],
- &mut [
- // corners
-@@ -1082,8 +1123,8 @@ mod test {
- None,
- rect(0.0, 0.0, 100.0, 100.0),
- &[
-- (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip),
-- (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
-+ (rect(10.0, 10.0, 90.0, 90.0), None, None, ClipMode::Clip),
-+ (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), None, ClipMode::Clip),
- ],
- &mut [
- seg(10.0, 10.0, 90.0, 90.0, false,
-@@ -1104,8 +1145,8 @@ mod test {
- None,
- rect(0.0, 0.0, 100.0, 100.0),
- &[
-- (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip),
-- (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::ClipOut),
-+ (rect(10.0, 10.0, 90.0, 90.0), None, None, ClipMode::Clip),
-+ (rect(10.0, 10.0, 90.0, 90.0), None, None, ClipMode::ClipOut),
- ],
- &mut [
- ],
-@@ -1119,7 +1160,7 @@ mod test {
- None,
- rect(0.0, 0.0, 100.0, 100.0),
- &[
-- (rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut),
-+ (rect(0.0, 0.0, 100.0, 90.0), None, None, ClipMode::ClipOut),
- ],
- &mut [
- seg(0.0, 90.0, 100.0, 100.0, false, Some(
-@@ -1224,7 +1265,7 @@ mod test {
- Some(rect(20.0, 40.0, 60.0, 80.0)),
- rect(0.0, 0.0, 100.0, 100.0),
- &[
-- (rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut),
-+ (rect(0.0, 0.0, 100.0, 90.0), None, None, ClipMode::ClipOut),
- ],
- &mut [
- seg_region(
-@@ -1262,7 +1303,7 @@ mod test {
- Some(rect(20.0, 20.0, 80.0, 80.0)),
- rect(0.0, 0.0, 100.0, 100.0),
- &[
-- (rect(20.0, 20.0, 100.0, 100.0), None, ClipMode::ClipOut),
-+ (rect(20.0, 20.0, 100.0, 100.0), None, None, ClipMode::ClipOut),
- ],
- &mut [
- seg_region(
-@@ -1315,7 +1356,7 @@ mod test {
- Some(rect(20.0, 20.0, 80.0, 80.0)),
- rect(0.0, 0.0, 100.0, 100.0),
- &[
-- (rect(10.0, 10.0, 30.0, 30.0), None, ClipMode::Clip),
-+ (rect(10.0, 10.0, 30.0, 30.0), None, None, ClipMode::Clip),
- ],
- &mut [
- seg_region(
-diff --git a/gfx/wr/webrender/src/tile_cache/mod.rs b/gfx/wr/webrender/src/tile_cache/mod.rs
-index 3d08ff4dc3..927a8d465c 100644
---- a/gfx/wr/webrender/src/tile_cache/mod.rs
-+++ b/gfx/wr/webrender/src/tile_cache/mod.rs
-@@ -1141,7 +1141,7 @@ impl TileCacheInstance {
- .get_instance_from_range(&clip_chain.clips_range, i);
- let clip_node = &frame_state.data_stores.clip[clip_instance.handle];
-
-- if let ClipItemKind::RoundedRectangle { radius, mode } = clip_node.item.kind {
-+ if let ClipItemKind::RoundedRectangle { radius, inset: _, mode } = clip_node.item.kind {
- assert_eq!(mode, ClipMode::Clip);
-
- let radius = clamped_radius(&radius, clip_instance.clip_rect.size());
-diff --git a/gfx/wr/webrender/src/tile_cache/slice_builder.rs b/gfx/wr/webrender/src/tile_cache/slice_builder.rs
-index 595377d04d..4f29370636 100644
---- a/gfx/wr/webrender/src/tile_cache/slice_builder.rs
-+++ b/gfx/wr/webrender/src/tile_cache/slice_builder.rs
-@@ -569,11 +569,11 @@ fn create_tile_cache(
- match clip_node_data.key.kind {
- ClipItemKeyKind::ImageMask(..) |
- ClipItemKeyKind::Rectangle(ClipMode::ClipOut) |
-- ClipItemKeyKind::RoundedRectangle(_, ClipMode::ClipOut) => {
-+ ClipItemKeyKind::RoundedRectangle(_, _, ClipMode::ClipOut) => {
- // Has an image-mask or clip-out clip, we can't handle this as a shared clip
- false
- }
-- ClipItemKeyKind::RoundedRectangle(radius, ClipMode::Clip) => {
-+ ClipItemKeyKind::RoundedRectangle(radius, _, ClipMode::Clip) => {
- // The shader and CoreAnimation rely on certain constraints such
- // as uniform radii to be able to apply the clip during compositing.
- let br = clamped_radius(&BorderRadius::from(radius), node.unsnapped_clip_rect.size());
-@@ -611,7 +611,7 @@ fn create_tile_cache(
- let can_combine = match (accumulated_rounded_rect, clip_node_data.key.kind) {
- (
- Some((acc_rect, acc_radius)),
-- ClipItemKeyKind::RoundedRectangle(radius, ClipMode::Clip),
-+ ClipItemKeyKind::RoundedRectangle(radius, _, ClipMode::Clip),
- ) => {
- let radius = clamped_radius(&BorderRadius::from(radius), node.unsnapped_clip_rect.size());
- intersect_rounded_rects(
-@@ -632,7 +632,7 @@ fn create_tile_cache(
- // Can't combine, drop children and keep only this clip.
- shared_clip_node_id = current_node_id;
- rounded_rect_count = 1;
-- if let ClipItemKeyKind::RoundedRectangle(radius, ClipMode::Clip) = clip_node_data.key.kind {
-+ if let ClipItemKeyKind::RoundedRectangle(radius, _, ClipMode::Clip) = clip_node_data.key.kind {
- let radius = clamped_radius(&BorderRadius::from(radius), node.unsnapped_clip_rect.size());
- accumulated_rounded_rect = Some((node.unsnapped_clip_rect, radius));
- }
-diff --git a/gfx/wr/webrender/src/util.rs b/gfx/wr/webrender/src/util.rs
-index 811549d1cb..b2da95259e 100644
---- a/gfx/wr/webrender/src/util.rs
-+++ b/gfx/wr/webrender/src/util.rs
-@@ -4,7 +4,7 @@
-
- use api::BorderRadius;
- use api::units::*;
--use euclid::{Point2D, Rect, Box2D, Size2D, Vector2D, point2, point3};
-+use euclid::{Point2D, Rect, Box2D, Size2D, SideOffsets2D, Vector2D, point2, point3};
- use euclid::{default, Transform2D, Transform3D, Scale, approxeq::ApproxEq};
- use plane_split::{Clipper, Polygon};
- use std::{i32, f32, fmt, ptr};
-@@ -309,6 +309,15 @@ impl ScaleOffset {
- )
- }
-
-+ pub fn map_side_offsets(&self, side_offsets: &SideOffsets2D) -> SideOffsets2D {
-+ SideOffsets2D::new(
-+ side_offsets.top * self.scale.y.abs(),
-+ side_offsets.right * self.scale.x.abs(),
-+ side_offsets.bottom * self.scale.y.abs(),
-+ side_offsets.left * self.scale.x.abs(),
-+ )
-+ }
-+
- pub fn unmap_vector(&self, vector: &Vector2D) -> Vector2D {
- Vector2D::new(
- vector.x / self.scale.x,
-@@ -682,17 +691,49 @@ pub fn pack_as_float(value: u32) -> f32 {
- fn extract_inner_rect_impl(
- rect: &Box2D,
- radii: &BorderRadius,
-+ inset: &SideOffsets2D,
- k: f32,
- ) -> Option> {
- // `k` defines how much border is taken into account
- // We enforce the offsets to be rounded to pixel boundaries
- // by `ceil`-ing and `floor`-ing them
-
-- let xl = (k * radii.top_left.width.max(radii.bottom_left.width)).ceil();
-- let xr = (rect.width() - k * radii.top_right.width.max(radii.bottom_right.width)).floor();
-- let yt = (k * radii.top_left.height.max(radii.top_right.height)).ceil();
-+ // In case of a corner shape below "round" (superellipse parameter < 1),
-+ // we need to add the inset to the radii for correctness. This will slightly
-+ // overestimate the corner area for sub-ellipses (-1 < parameter < 1), but
-+ // keeps the computation cheap.
-+
-+ let mut top_left_width = radii.top_left.width;
-+ let mut top_left_height = radii.top_left.height;
-+ let mut top_right_width = radii.top_right.width;
-+ let mut top_right_height = radii.top_right.height;
-+ let mut bottom_left_width = radii.bottom_left.width;
-+ let mut bottom_left_height = radii.bottom_left.height;
-+ let mut bottom_right_width = radii.bottom_right.width;
-+ let mut bottom_right_height = radii.bottom_right.height;
-+
-+ if radii.shape_top_left < 1.0 {
-+ top_left_width += inset.top;
-+ top_left_height += inset.left;
-+ }
-+ if radii.shape_top_right < 1.0 {
-+ top_right_width += inset.top;
-+ top_right_height += inset.right;
-+ }
-+ if radii.shape_bottom_left < 1.0 {
-+ bottom_left_width += inset.bottom;
-+ bottom_left_height += inset.left;
-+ }
-+ if radii.shape_bottom_right < 1.0 {
-+ bottom_right_width += inset.bottom;
-+ bottom_right_height += inset.right;
-+ }
-+
-+ let xl = (k * top_left_width.max(bottom_left_width)).ceil();
-+ let xr = (rect.width() - k * top_right_width.max(bottom_right_width)).floor();
-+ let yt = (k * top_left_height.max(top_right_height)).ceil();
- let yb =
-- (rect.height() - k * radii.bottom_left.height.max(radii.bottom_right.height)).floor();
-+ (rect.height() - k * bottom_left_height.max(bottom_right_height)).floor();
-
- if xl <= xr && yt <= yb {
- Some(Box2D::from_origin_and_size(
-@@ -709,10 +750,11 @@ fn extract_inner_rect_impl(
- pub fn extract_inner_rect_safe(
- rect: &Box2D,
- radii: &BorderRadius,
-+ inset: &SideOffsets2D,
- ) -> Option> {
- // value of `k==1.0` is used for extraction of the corner rectangles
- // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
-- extract_inner_rect_impl(rect, radii, 1.0)
-+ extract_inner_rect_impl(rect, radii, inset, 1.0)
- }
-
- /// Return an aligned rectangle that is inside the clip region and doesn't intersect
-@@ -721,9 +763,18 @@ pub fn extract_inner_rect_safe(
- pub fn extract_inner_rect_k(
- rect: &Box2D,
- radii: &BorderRadius,
-+ inset: &SideOffsets2D,
- k: f32,
- ) -> Option> {
-- extract_inner_rect_impl(rect, radii, k)
-+ // When using corner shape, corners can go inside the shape and create
-+ // clipping issues, we need the 'safe' (k == 1.0) version in that case.
-+ // This could be refined by computing the superellipse half corners but
-+ // would make the calculation a bit more expensive.
-+ if radii.shapes_all_round() {
-+ extract_inner_rect_impl(rect, radii, inset, k)
-+ } else {
-+ extract_inner_rect_impl(rect, radii, inset, 1.0)
-+ }
- }
-
- #[cfg(test)]
-diff --git a/gfx/wr/webrender_api/src/display_item.rs b/gfx/wr/webrender_api/src/display_item.rs
-index cc8b80a049..32b0f625f7 100644
---- a/gfx/wr/webrender_api/src/display_item.rs
-+++ b/gfx/wr/webrender_api/src/display_item.rs
-@@ -418,6 +418,17 @@ pub struct NormalBorder {
- pub top: BorderSide,
- pub bottom: BorderSide,
- pub radius: BorderRadius,
-+
-+ /// Accumulated inset from the reference shape, in case of inflation/deflation.
-+ /// The inset is expected to be already baked in rect and radii, but is necessary
-+ /// for the correct computation of corner shapes.
-+ ///
-+ /// As borders have widths, this value should be calculated for the *outer* edge
-+ /// of the border. For instance, a regular shape with a border will have an inset
-+ /// of zero, but an outline of 5px, of which the outer edge is 5px away from the
-+ /// original border-box, will have an inset of -5px.
-+ pub inset: LayoutSideOffsets,
-+
- /// Whether to apply anti-aliasing on the border corners.
- ///
- /// Note that for this to be `false` and work, this requires the borders to
-@@ -2124,6 +2135,12 @@ pub struct ComplexClipRegion {
- pub rect: LayoutRect,
- /// Border radii of this rectangle.
- pub radii: BorderRadius,
-+ /// Accumulated inset from the reference shape, in case of inflation/deflation.
-+ /// The inset is expected to be already baked in rect and radii, but is necessary
-+ /// for the correct computation of corner shapes.
-+ /// Example: a shape with a border of 10px should have a 10px inset applied on its
-+ /// content mask.
-+ pub inset: LayoutSideOffsets,
- /// Whether we are clipping inside or outside
- /// the region.
- pub mode: ClipMode,
-@@ -2214,9 +2231,10 @@ impl ComplexClipRegion {
- pub fn new(
- rect: LayoutRect,
- radii: BorderRadius,
-+ inset: LayoutSideOffsets,
- mode: ClipMode,
- ) -> Self {
-- ComplexClipRegion { rect, radii, mode }
-+ ComplexClipRegion { rect, radii, inset, mode }
- }
- }
-
-diff --git a/gfx/wr/webrender_api/src/display_list.rs b/gfx/wr/webrender_api/src/display_list.rs
-index b676317c1f..c131db0eeb 100644
---- a/gfx/wr/webrender_api/src/display_list.rs
-+++ b/gfx/wr/webrender_api/src/display_list.rs
-@@ -1526,8 +1526,10 @@ impl DisplayListBuilder {
- .inflate(spread_amount, spread_amount);
- let spatial_id = common.spatial_id;
-
-+ let shadow_inset = LayoutSideOffsets::new_all_same(-spread_amount);
-+
- let mut clips: Vec = Vec::with_capacity(2);
-- let (final_prim_rect, clip_radius) = match clip_mode {
-+ let (final_prim_rect, clip_radius, clip_inset) = match clip_mode {
- BoxShadowClipMode::Outset => {
- if shadow_rect.is_empty() {
- return;
-@@ -1538,12 +1540,13 @@ impl DisplayListBuilder {
- ComplexClipRegion {
- rect: box_bounds,
- radii: border_radius,
-+ inset: LayoutSideOffsets::zero(),
- mode: ClipMode::ClipOut,
- },
- spread_radius,
- ));
-
-- (shadow_rect, shadow_radius)
-+ (shadow_rect, shadow_radius, shadow_inset)
+ /// An clip applied to a render task using the multiply blend mode on top of
+diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs
+index 024e1b63c4..d177263096 100644
+--- a/gfx/wr/webrender/src/renderer/mod.rs
++++ b/gfx/wr/webrender/src/renderer/mod.rs
+@@ -253,6 +253,7 @@ impl BatchKind {
+ BatchKind::Quad(PatternKind::Gradient) => GPU_TAG_GRADIENT,
+ BatchKind::Quad(PatternKind::Repeat) => GPU_TAG_REPEAT,
+ BatchKind::Quad(PatternKind::BoxShadow) => GPU_TAG_PRIMITIVE,
++ BatchKind::Quad(PatternKind::BoxShadowSuperellipse) => GPU_TAG_PRIMITIVE,
+ BatchKind::Quad(PatternKind::Yuv) => GPU_TAG_BRUSH_YUV_IMAGE,
+ BatchKind::Quad(PatternKind::YuvTextureExternal) => GPU_TAG_BRUSH_YUV_IMAGE,
+ BatchKind::Quad(PatternKind::YuvTextureExternalBT709) => GPU_TAG_BRUSH_YUV_IMAGE,
+@@ -2452,6 +2453,50 @@ impl Renderer {
+ self.device.disable_scissor();
}
- BoxShadowClipMode::Inset => {
- if !shadow_rect.is_empty() {
-@@ -1552,13 +1555,14 @@ impl DisplayListBuilder {
- ComplexClipRegion {
- rect: shadow_rect,
- radii: shadow_radius,
-+ inset: shadow_inset,
- mode: ClipMode::ClipOut,
- },
- spread_radius,
- ));
- }
-- (box_bounds, border_radius)
-+ (box_bounds, border_radius, LayoutSideOffsets::zero())
++ if !masks.mask_instances_superellipse.is_empty() {
++ self.shaders.borrow_mut().ps_mask_superellipse().bind(
++ &mut self.device,
++ projection,
++ None,
++ &mut self.renderer_errors,
++ &mut self.profile,
++ &mut self.command_log,
++ );
++
++ self.draw_instanced_batch(
++ &masks.mask_instances_superellipse,
++ VertexArrayKind::Mask,
++ &BatchTextures::empty(),
++ stats,
++ );
++ }
++
++ if !masks.mask_instances_superellipse_with_scissor.is_empty() {
++ self.shaders.borrow_mut().ps_mask_superellipse().bind(
++ &mut self.device,
++ projection,
++ None,
++ &mut self.renderer_errors,
++ &mut self.profile,
++ &mut self.command_log,
++ );
++
++ self.device.enable_scissor();
++
++ for (scissor_rect, instances) in &masks.mask_instances_superellipse_with_scissor {
++ self.device.set_scissor_rect(draw_target.to_framebuffer_rect(*scissor_rect));
++
++ self.draw_instanced_batch(
++ instances,
++ VertexArrayKind::Mask,
++ &BatchTextures::empty(),
++ stats,
++ );
++ }
++
++ self.device.disable_scissor();
++ }
++
+ if !masks.image_mask_instances.is_empty() {
+ self.shaders.borrow_mut().ps_quad_textured().bind(
+ &mut self.device,
+@@ -3351,7 +3396,9 @@ impl Renderer {
+
+ // Draw any borders for this target.
+ if !target.border_segments_solid.is_empty() ||
+- !target.border_segments_complex.is_empty()
++ !target.border_segments_complex.is_empty() ||
++ !target.border_segments_solid_superellipse.is_empty() ||
++ !target.border_segments_complex_superellipse.is_empty()
+ {
+ let _timer = self.gpu_profiler.start_timer(GPU_TAG_CACHE_BORDER);
+
+@@ -3394,6 +3441,42 @@ impl Renderer {
+ );
}
- };
-@@ -1568,6 +1572,7 @@ impl DisplayListBuilder {
- ComplexClipRegion {
- rect: final_prim_rect,
- radii: clip_radius,
-+ inset: clip_inset,
- mode: ClipMode::Clip,
- },
- 0.0,
-diff --git a/gfx/wr/webrender_api/src/key_types.rs b/gfx/wr/webrender_api/src/key_types.rs
-index c170551247..0b1be8f29f 100644
---- a/gfx/wr/webrender_api/src/key_types.rs
-+++ b/gfx/wr/webrender_api/src/key_types.rs
-@@ -13,7 +13,7 @@
- use crate::serde::{Serialize, Deserialize};
- use crate::{ColorU, BorderRadius, BorderSide, BorderStyle, NormalBorder, RepeatMode, GradientStop, PrimitiveFlags};
- use crate::{FillRule, GlyphIndex, POLYGON_CLIP_VERTEX_MAX};
--use crate::units::{LayoutVector2D, WorldVector2D, LayoutPoint, PicturePoint, WorldPoint};
-+use crate::units::{LayoutPoint, LayoutSideOffsetsAu, LayoutVector2D, PicturePoint, WorldPoint, WorldVector2D};
- use crate::units::{LayoutSize, LayoutSizeAu, LayoutPointAu, AuHelpers, LayoutSideOffsets, DeviceIntSideOffsets};
- use euclid::{Size2D, SideOffsets2D};
- use peek_poke::PeekPoke;
-@@ -198,6 +198,7 @@ pub struct NormalBorderAu {
- pub top: BorderSideAu,
- pub bottom: BorderSideAu,
- pub radius: BorderRadiusAu,
-+ pub inset: LayoutSideOffsetsAu,
- /// Whether to apply anti-aliasing on the border corners.
- ///
- /// Note that for this to be `false` and work, this requires the borders to
-@@ -225,6 +226,7 @@ impl From for NormalBorderAu {
- top: border.top.into(),
- bottom: border.bottom.into(),
- radius: border.radius.into(),
-+ inset: border.inset.to_au(),
- do_aa: border.do_aa,
++ if !target.border_segments_solid_superellipse.is_empty() {
++ self.shaders.borrow_mut().cs_border_solid_superellipse().bind(
++ &mut self.device,
++ &projection,
++ None,
++ &mut self.renderer_errors,
++ &mut self.profile,
++ &mut self.command_log,
++ );
++
++ self.draw_instanced_batch(
++ &target.border_segments_solid_superellipse,
++ VertexArrayKind::Border,
++ &BatchTextures::empty(),
++ stats,
++ );
++ }
++
++ if !target.border_segments_complex_superellipse.is_empty() {
++ self.shaders.borrow_mut().cs_border_segment_superellipse().bind(
++ &mut self.device,
++ &projection,
++ None,
++ &mut self.renderer_errors,
++ &mut self.profile,
++ &mut self.command_log,
++ );
++
++ self.draw_instanced_batch(
++ &target.border_segments_complex_superellipse,
++ VertexArrayKind::Border,
++ &BatchTextures::empty(),
++ stats,
++ );
++ }
++
+ self.set_blend(false, FramebufferKind::Other);
}
+
+diff --git a/gfx/wr/webrender/src/renderer/shade.rs b/gfx/wr/webrender/src/renderer/shade.rs
+index 0a88c76bf8..575275d3b4 100644
+--- a/gfx/wr/webrender/src/renderer/shade.rs
++++ b/gfx/wr/webrender/src/renderer/shade.rs
+@@ -67,6 +67,7 @@ pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 4] = [
+ const DITHERING_FEATURE: &str = "DITHERING";
+ const DUAL_SOURCE_FEATURE: &str = "DUAL_SOURCE_BLENDING";
+ const FAST_PATH_FEATURE: &str = "FAST_PATH";
++const SUPERELLIPSE_FEATURE: &str = "SUPERELLIPSE";
+
+ pub(crate) enum ShaderKind {
+ Primitive,
+@@ -414,6 +415,8 @@ pub struct Shaders {
+ cs_blur_rgba8: ShaderHandle,
+ cs_border_segment: ShaderHandle,
+ cs_border_solid: ShaderHandle,
++ cs_border_segment_superellipse: ShaderHandle,
++ cs_border_solid_superellipse: ShaderHandle,
+ cs_scale: Vec