Compare commits

..

4 Commits

Author SHA1 Message Date
mr. m
94b2a4b67e no-bug: Add border render support 2026-06-20 16:03:48 +02:00
mr. m
cf98127a5e Merge branch 'dev' into feat/9324
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-06-19 08:29:45 +02:00
mr. m
b7a8e79299 no-bug: Apply squircles to elements 2026-06-06 14:08:36 +02:00
mr. m
0048f21a52 no-bug: Squircles support 2026-06-04 01:37:46 +02:00
44 changed files with 6943 additions and 913 deletions

View File

@@ -34,8 +34,8 @@ 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 `152.0.3`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.3`!
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `152.0.1`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.1`!
### Contributing

View File

@@ -1 +1 @@
b223f3727a037a7b8a0f36f34bfe8c4622a7f387
2960f4c1ce58d289d3b9ec885695f0017d2636ab

View File

@@ -19,19 +19,13 @@ tab-context-zen-add-essential-badge = { $num } / { $max }
tab-context-zen-remove-essential =
.label = Remove from Essentials
.accesskey = R
tab-context-zen-edit-pinned-page =
tab-context-zen-replace-pinned-url-with-current =
.label =
{ $isEssential ->
[true] Edit Essential URL
*[false] Edit Pinned URL
[true] Replace Essential URL with Current
*[false] Replace Pinned URL with Current
}
.accesskey = P
tab-context-zen-replace-pinned-url-with-current =
.label = Replace with Current URL
.accesskey = C
tab-context-zen-edit-pinned-url =
.label = Edit...
.accesskey = E
tab-context-zen-edit-title =
.label = Change Label...
tab-context-zen-edit-icon =
@@ -61,10 +55,6 @@ zen-general-confirm =
.label = Confirm
zen-pinned-tab-replaced = Pinned tab URL has been replaced with the current URL!
zen-pinned-tab-url-edited = Pinned tab URL has been updated!
zen-pinned-tab-url-invalid = That doesn't look like a valid URL.
zen-pinned-tab-edit-url-title = Edit Pinned URL
zen-pinned-tab-edit-url-label = Enter the URL this pinned tab should point to:
zen-tabs-renamed = Tab has been successfully renamed!
zen-background-tab-opened-toast = New background tab opened!
zen-workspace-renamed-toast = Workspace has been successfully renamed!

View File

@@ -25,9 +25,3 @@ zen-space-routing-open-in = Open In
zen-space-routing-url = URL
zen-space-routing-tab-routed-toast = New tab opened in { $targetWorkspace }
tab-context-zen-add-domain-to-sr =
.label =
{ $tabCount ->
[one] Add Route for Domain
*[other] Add Route for Domains
}

View File

@@ -95,6 +95,9 @@
- name: browser.search.widget.new
value: true
- name: layout.css.corner-shape.enabled
value: true
# Disabled from https://searchfox.org/firefox-main/rev/d6bfff43852356ca98af848b4705d37f8d41856f/modules/libpref/init/StaticPrefList.yaml#2008
# Only enabled for windows, doesn't really fit inside Zen.
- name: browser.startup.preXulSkeletonUI

View File

@@ -1,18 +0,0 @@
diff --git a/browser/base/content/aboutDialog.css b/browser/base/content/aboutDialog.css
index 017125bc2510e5f5e317a5e78c40d6aa9ded76ca..d343d8c62a2251e3c3a33ae8f2ab9c4c68218c22 100644
--- a/browser/base/content/aboutDialog.css
+++ b/browser/base/content/aboutDialog.css
@@ -135,6 +135,13 @@
margin: 0 40px;
}
+#trademark {
+ font-size: xx-small;
+ text-align: center;
+ margin-block: 10px;
+ color: var(--text-color-deemphasized);
+}
+
#currentChannel {
margin: 0;
padding: 0;

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/aboutDialog.xhtml b/browser/base/content/aboutDialog.xhtml
index 3ffd464b960a4299a7dd0cd87e4fc2f781b9d593..7a831c8ee2b73bb89bf8a82ac24958b55c16a5aa 100644
index 3ffd464b960a4299a7dd0cd87e4fc2f781b9d593..ef9f42d1f0196902b4af31f4496891fcd6319831 100644
--- a/browser/base/content/aboutDialog.xhtml
+++ b/browser/base/content/aboutDialog.xhtml
@@ -102,10 +102,6 @@
@@ -39,8 +39,8 @@ index 3ffd464b960a4299a7dd0cd87e4fc2f781b9d593..7a831c8ee2b73bb89bf8a82ac24958b5
<label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:license" data-l10n-id="bottomLinks-license"/>
- <label is="text-link" class="bottom-link" href="https://www.mozilla.org/about/legal/terms/firefox/" data-l10n-id="bottom-links-terms"/>
- <label is="text-link" class="bottom-link" href="https://www.mozilla.org/privacy/firefox/?utm_source=firefox-browser&#38;utm_medium=firefox-desktop&#38;utm_campaign=about-dialog" data-l10n-id="bottom-links-privacy"/>
+ <label is="text-link" class="bottom-link" href="about:rights" data-l10n-id="bottom-links-terms"/>
+ <label is="text-link" class="bottom-link" href="https://www.zen-browser.app/privacy-policy/" data-l10n-id="bottom-links-privacy"/>
+ <label is="text-link" class="bottom-link" href="about:rights" data-l10n-id="bottomLinks-rights"/>
+ <label is="text-link" class="bottom-link" href="https://www.zen-browser.app/privacy-policy/" data-l10n-id="bottomLinks-privacy"/>
</hbox>
<description id="trademark" data-l10n-id="trademarkInfo"></description>
</vbox>

View File

@@ -35,7 +35,6 @@
<command id="cmd_zenToggleTabsOnRight" />
<command id="cmd_zenReplacePinnedUrlWithCurrent" />
<command id="cmd_zenEditPinnedUrl" />
<command id="cmd_contextZenAddToEssentials" />
<command id="cmd_contextZenRemoveFromEssentials" />

View File

@@ -131,10 +131,10 @@
</box>
<html:input type="range" value="0.4" step="0.001" id="PanelUI-zen-gradient-generator-opacity"
#ifdef XP_MACOSX
max="0.9"
max="0.8"
min="0.30"
#else
max="0.9"
max="0.8"
min="0.25"
#endif
/>

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d83330c265dfc 100644
index 08b5b56e069d038d72c87355920c4ce8a55ed805..555ffd4772d9d4903491fdff9f3682852f8a52bd 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -511,6 +511,7 @@
@@ -523,15 +523,12 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (tabs.length > 1 || !tabs[0].selected) {
this._updateTabsAfterInsert();
@@ -4866,11 +5030,17 @@
@@ -4866,11 +5030,14 @@
if (ownerTab) {
tab.owner = ownerTab;
}
+ if ((!tab.pinned && tabGroup?.isZenFolder && !Services.prefs.getBoolPref('zen.folders.owned-tabs-in-folder')) || (tabGroup && tabGroup.hasAttribute("split-view-group"))) {
+ tabGroup = null;
+ }
+ if (openerTab?.hasAttribute("zen-glance-tab")) {
+ openerTab = gZenGlanceManager.getTabOrGlanceParent(openerTab);
+ }
// Ensure we have an index if one was not provided.
@@ -542,7 +539,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (
!bulkOrderedOpen &&
((openerTab &&
@@ -4882,7 +5052,7 @@
@@ -4882,7 +5049,7 @@
let lastRelatedTab =
openerTab && this._lastRelatedTabMap.get(openerTab);
let previousTab = lastRelatedTab || openerTab || this.selectedTab;
@@ -551,7 +548,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
tabGroup = previousTab.group;
}
if (
@@ -4898,7 +5068,7 @@
@@ -4898,7 +5065,7 @@
previousTab.splitview
) + 1;
} else if (previousTab.visible) {
@@ -560,7 +557,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} else if (previousTab == FirefoxViewHandler.tab) {
elementIndex = 0;
}
@@ -4926,14 +5096,14 @@
@@ -4926,14 +5093,14 @@
}
// Ensure index is within bounds.
if (tab.pinned) {
@@ -579,7 +576,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (pinned && !itemAfter?.pinned) {
itemAfter = null;
@@ -4950,7 +5120,7 @@
@@ -4950,7 +5117,7 @@
this.tabContainer._invalidateCachedTabs();
@@ -588,7 +585,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (
(this.isTab(itemAfter) && itemAfter.group == tabGroup) ||
this.isSplitViewWrapper(itemAfter)
@@ -4981,7 +5151,11 @@
@@ -4981,7 +5148,11 @@
const tabContainer = pinned
? this.tabContainer.pinnedTabsContainer
: this.tabContainer;
@@ -600,7 +597,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
if (tab.group?.collapsed) {
@@ -4996,6 +5170,7 @@
@@ -4996,6 +5167,7 @@
if (pinned) {
this._updateTabBarForPinnedTabs();
}
@@ -608,7 +605,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
TabBarVisibility.update();
}
@@ -5544,6 +5719,7 @@
@@ -5544,6 +5716,7 @@
telemetrySource,
} = {}
) {
@@ -616,7 +613,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
// can be considered equivalent to closing the window.
if (
@@ -5633,6 +5809,7 @@
@@ -5633,6 +5806,7 @@
if (lastToClose) {
this.removeTab(lastToClose, aParams);
}
@@ -624,7 +621,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} catch (e) {
console.error(e);
}
@@ -5678,6 +5855,14 @@
@@ -5678,6 +5852,14 @@
return;
}
@@ -639,7 +636,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
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
@@ -5685,6 +5870,9 @@
@@ -5685,6 +5867,9 @@
// state).
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
let isLastTab = this.#isLastTabInWindow(aTab);
@@ -649,7 +646,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (
!this._beginRemoveTab(aTab, {
closeWindowFastpath: true,
@@ -5696,13 +5884,14 @@
@@ -5696,13 +5881,14 @@
telemetrySource,
})
) {
@@ -665,7 +662,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let lockTabSizing =
!this.tabContainer.verticalMode &&
!aTab.pinned &&
@@ -5733,7 +5922,13 @@
@@ -5733,7 +5919,13 @@
// We're not animating, so we can cancel the animation stopwatch.
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
aTab._closeTimeAnimTimerId = null;
@@ -680,7 +677,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
return;
}
@@ -5867,7 +6062,7 @@
@@ -5867,7 +6059,7 @@
closeWindowWithLastTab != null
? closeWindowWithLastTab
: !window.toolbar.visible ||
@@ -689,7 +686,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (closeWindow) {
// We've already called beforeunload on all the relevant tabs if we get here,
@@ -5891,6 +6086,7 @@
@@ -5891,6 +6083,7 @@
newTab = true;
}
@@ -697,7 +694,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation.
@@ -5931,13 +6127,7 @@
@@ -5931,13 +6124,7 @@
aTab._mouseleave();
if (newTab) {
@@ -712,7 +709,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} else {
TabBarVisibility.update();
}
@@ -6070,6 +6260,7 @@
@@ -6070,6 +6257,7 @@
this.tabs[i]._tPos = i;
}
@@ -720,7 +717,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (!this._windowIsClosing) {
// update tab close buttons state
this.tabContainer._updateCloseButtons();
@@ -6255,6 +6446,7 @@
@@ -6255,6 +6443,7 @@
memory_after: await getTotalMemoryUsage(),
time_to_unload_in_ms: timeElapsed,
});
@@ -728,7 +725,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
/**
@@ -6300,6 +6492,7 @@
@@ -6300,6 +6489,7 @@
}
let excludeTabs = new Set(aExcludeTabs);
@@ -736,7 +733,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor.
@@ -6312,15 +6505,22 @@
@@ -6312,15 +6502,22 @@
!excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) {
@@ -761,7 +758,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let tab = this.tabContainer.findNextTab(aTab, {
direction: 1,
filter: _tab => remainingTabs.includes(_tab),
@@ -6334,7 +6534,7 @@
@@ -6334,7 +6531,7 @@
}
if (tab) {
@@ -770,7 +767,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
// If no qualifying visible tab was found, see if there is a tab in
@@ -6355,7 +6555,7 @@
@@ -6355,7 +6552,7 @@
});
}
@@ -779,7 +776,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
_blurTab(aTab) {
@@ -6366,7 +6566,7 @@
@@ -6366,7 +6563,7 @@
* @returns {boolean}
* False if swapping isn't permitted, true otherwise.
*/
@@ -788,7 +785,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// Do not allow transfering a private tab to a non-private window
// and vice versa.
if (
@@ -6420,6 +6620,7 @@
@@ -6420,6 +6617,7 @@
// fire the beforeunload event in the process. Close the other
// window if this was its last tab.
if (
@@ -796,7 +793,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
!remoteBrowser._beginRemoveTab(aOtherTab, {
adoptedByTab: aOurTab,
closeWindowWithLastTab: true,
@@ -6431,7 +6632,7 @@
@@ -6431,7 +6629,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.
@@ -805,7 +802,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (closeWindow) {
let win = aOtherTab.documentGlobal;
win.windowUtils.suppressAnimation(true);
@@ -6565,11 +6766,13 @@
@@ -6565,11 +6763,13 @@
}
// Finish tearing down the tab that's going away.
@@ -819,7 +816,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
this.setTabTitle(aOurTab);
@@ -6771,10 +6974,10 @@
@@ -6771,10 +6971,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
}
@@ -832,7 +829,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
aTab.selected ||
aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
@@ -6834,7 +7037,8 @@
@@ -6834,7 +7034,8 @@
* @param {object} [aOptions={}]
* Key-value pairs that will be serialized into the features string.
*/
@@ -842,7 +839,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (this.tabs.length == 1) {
return null;
}
@@ -6851,7 +7055,7 @@
@@ -6851,7 +7052,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);
@@ -851,7 +848,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
private: PrivateBrowsingUtils.isWindowPrivate(window),
features: Object.entries(aOptions)
.map(([key, value]) => `${key}=${value}`)
@@ -6859,6 +7063,8 @@
@@ -6859,6 +7060,8 @@
openerWindow: window,
args,
});
@@ -860,7 +857,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
/**
@@ -6971,7 +7177,7 @@
@@ -6971,7 +7174,7 @@
* `true` if element is a `<tab-group>`
*/
isTabGroup(element) {
@@ -869,7 +866,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
/**
@@ -7056,8 +7262,8 @@
@@ -7056,8 +7259,8 @@
}
// Don't allow mixing pinned and unpinned tabs.
@@ -880,7 +877,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
}
@@ -7103,8 +7309,8 @@
@@ -7103,8 +7306,8 @@
this.#handleTabMove(
element,
() => {
@@ -891,7 +888,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
neighbor = neighbor.group;
}
if (neighbor?.splitview) {
@@ -7115,6 +7321,12 @@
@@ -7115,6 +7318,12 @@
return;
}
}
@@ -904,7 +901,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (movingForwards && neighbor) {
neighbor.after(element);
@@ -7173,23 +7385,31 @@
@@ -7173,23 +7382,31 @@
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
if (this.isTabGroupLabel(targetElement)) {
targetElement = targetElement.group;
@@ -942,7 +939,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} 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
@@ -7202,12 +7422,35 @@
@@ -7202,12 +7419,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.
@@ -979,7 +976,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// 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.
@@ -7216,6 +7459,7 @@
@@ -7216,6 +7456,7 @@
}
let getContainer = () =>
@@ -987,7 +984,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
element.pinned
? this.tabContainer.pinnedTabsContainer
: this.tabContainer;
@@ -7224,11 +7468,15 @@
@@ -7224,11 +7465,15 @@
element,
() => {
if (moveBefore) {
@@ -1004,7 +1001,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
},
metricsContext
@@ -7302,11 +7550,15 @@
@@ -7302,11 +7547,15 @@
* @param {TabMetricsContext} [metricsContext]
*/
moveTabToExistingGroup(aTab, aGroup, metricsContext) {
@@ -1023,7 +1020,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
if (aTab.group && aTab.group.id === aGroup.id) {
return;
@@ -7378,6 +7630,7 @@
@@ -7378,6 +7627,7 @@
let state = {
tabIndex: tab._tPos,
@@ -1031,7 +1028,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
};
if (tab.visible) {
state.elementIndex = tab.elementIndex;
@@ -7409,7 +7662,7 @@
@@ -7409,7 +7659,7 @@
let changedSplitView =
previousTabState.splitViewId != currentTabState.splitViewId;
@@ -1040,7 +1037,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
tab.dispatchEvent(
new CustomEvent("TabMove", {
bubbles: true,
@@ -7456,6 +7709,10 @@
@@ -7456,6 +7706,10 @@
moveActionCallback();
@@ -1051,7 +1048,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// Clear tabs cache after moving nodes because the order of tabs may have
// changed.
this.tabContainer._invalidateCachedTabs();
@@ -7506,7 +7763,22 @@
@@ -7506,7 +7760,22 @@
* @returns {object}
* The new tab in the current window, null if the tab couldn't be adopted.
*/
@@ -1075,7 +1072,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// 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
@@ -7549,6 +7821,8 @@
@@ -7549,6 +7818,8 @@
}
params.skipLoad = true;
let newTab = this.addWebTab("about:blank", params);
@@ -1084,7 +1081,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
aTab.container.tabDragAndDrop.finishAnimateTabMove();
@@ -8259,7 +8533,7 @@
@@ -8259,7 +8530,7 @@
// preventDefault(). It will still raise the window if appropriate.
return;
}
@@ -1093,7 +1090,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
window.focus();
aEvent.preventDefault();
}
@@ -8276,7 +8550,6 @@
@@ -8276,7 +8547,6 @@
on_TabGroupCollapse(aEvent) {
aEvent.target.tabs.forEach(tab => {
@@ -1101,7 +1098,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
});
}
@@ -8630,7 +8903,9 @@
@@ -8630,7 +8900,9 @@
let filter = this._tabFilters.get(tab);
if (filter) {
@@ -1111,7 +1108,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let listener = this._tabListeners.get(tab);
if (listener) {
@@ -9435,6 +9710,7 @@
@@ -9435,6 +9707,7 @@
aWebProgress.isTopLevel
) {
this.mTab.setAttribute("busy", "true");
@@ -1119,7 +1116,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
gBrowser._tabAttrModified(this.mTab, ["busy"]);
this.mTab._notselectedsinceload = !this.mTab.selected;
}
@@ -9515,6 +9791,7 @@
@@ -9515,6 +9788,7 @@
// known defaults. Note we use the original URL since about:newtab
// redirects to a prerendered page.
const shouldRemoveFavicon =
@@ -1127,7 +1124,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
!this.mBrowser.mIconURL &&
!ignoreBlank &&
!(originalLocation.spec in FAVICON_DEFAULTS);
@@ -9689,13 +9966,6 @@
@@ -9689,13 +9963,6 @@
this.mBrowser.originalURI = aRequest.originalURI;
}
@@ -1141,7 +1138,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}
let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
@@ -10587,7 +10857,8 @@ var TabContextMenu = {
@@ -10587,7 +10854,8 @@ var TabContextMenu = {
);
contextUnpinSelectedTabs.hidden =
!this.contextTab.pinned || !this.multiselected;

View File

@@ -1,23 +1,8 @@
diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js
index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c52cfcb39e 100644
index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9ea21b6f6 100644
--- a/browser/components/tabbrowser/content/tabs.js
+++ b/browser/components/tabbrowser/content/tabs.js
@@ -197,8 +197,12 @@
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_sidebarPositionStart",
- "sidebar.position_start",
- true
+ "zen.tabs.vertical.right-side",
+ true,
+ null,
+ newValue => {
+ return !newValue;
+ }
);
if (gMultiProcessBrowser) {
@@ -220,7 +224,7 @@
@@ -220,7 +220,7 @@
this.tooltip = "tabbrowser-tab-tooltip";
@@ -26,7 +11,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
this.tabDragAndDrop.init();
}
@@ -444,7 +448,7 @@
@@ -444,7 +444,7 @@
// and we're not hitting the scroll buttons.
if (
event.button != 0 ||
@@ -35,7 +20,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
event.composedTarget.localName == "toolbarbutton"
) {
return;
@@ -525,7 +529,6 @@
@@ -525,7 +525,6 @@
});
}
} else if (isTabGroupLabel(event.target)) {
@@ -43,7 +28,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
} else if (
event.originalTarget.closest("scrollbox") &&
!Services.prefs.getBoolPref(
@@ -561,6 +564,9 @@
@@ -561,6 +560,9 @@
}
on_keydown(event) {
@@ -53,7 +38,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
let { altKey, shiftKey } = event;
let [accel, nonAccel] =
AppConstants.platform == "macosx"
@@ -755,7 +761,6 @@
@@ -755,7 +757,6 @@
this._updateCloseButtons();
if (!this.#animatingGroups.size) {
@@ -61,7 +46,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
}
document
@@ -822,7 +827,7 @@
@@ -822,7 +823,7 @@
}
get newTabButton() {
@@ -70,7 +55,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
}
get verticalMode() {
@@ -838,6 +843,7 @@
@@ -838,6 +839,7 @@
}
get overflowing() {
@@ -78,7 +63,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
return this.hasAttribute("overflow");
}
@@ -851,29 +857,56 @@
@@ -851,29 +853,56 @@
if (pinnedChildren?.at(-1)?.id == "pinned-tabs-container-periphery") {
pinnedChildren.pop();
}
@@ -108,7 +93,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
+ } else if (!isTab(tab)) {
+ tabs.splice(i, 1);
+ }
+ }
}
+ };
+ expandTabs(pinnedTabs);
+ expandTabs(unpinnedChildren);
@@ -129,7 +114,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
+ // remove the separator from the list
+ allTabs.splice(i, 1);
+ i--;
}
+ }
+ i++;
}
-
@@ -145,7 +130,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
}
get allSplitViews() {
@@ -958,29 +991,28 @@
@@ -958,29 +987,28 @@
return this.#focusableItems;
}
@@ -185,7 +170,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
this.#focusableItems = focusableItems;
return this.#focusableItems;
@@ -993,6 +1025,7 @@
@@ -993,6 +1021,7 @@
* focusable (ex, we don't want the splitview container to be focusable, only its children).
*/
get dragAndDropElements() {
@@ -193,7 +178,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
if (this.#dragAndDropElements) {
return this.#dragAndDropElements;
}
@@ -1063,6 +1096,7 @@
@@ -1063,6 +1092,7 @@
_invalidateCachedTabs() {
this.#allTabs = null;
this._invalidateCachedVisibleTabs();
@@ -201,7 +186,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
}
_invalidateCachedVisibleTabs() {
@@ -1082,7 +1116,8 @@
@@ -1082,7 +1112,8 @@
isContainerVerticalPinnedGrid(tab) {
return (
@@ -211,7 +196,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
this.verticalMode &&
this.hasAttribute("expanded") &&
!this.expandOnHover
@@ -1176,7 +1211,7 @@
@@ -1176,7 +1207,7 @@
if (node == null) {
// We have a container for non-tab elements at the end of the scrollbox.
@@ -220,7 +205,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
}
node.before(tab);
@@ -1271,7 +1306,7 @@
@@ -1271,7 +1302,7 @@
// There are separate "new tab" buttons for horizontal tabs toolbar, vertical tabs and
// for when the tab strip is overflowed (which is shared by vertical and horizontal tabs);
// Attach the long click popup to all of them.
@@ -229,7 +214,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
const newTab2 = this.newTabButton;
const newTabVertical = document.getElementById(
"vertical-tabs-newtab-button"
@@ -1376,8 +1411,10 @@
@@ -1376,8 +1407,10 @@
*/
_handleTabSelect(aInstant) {
let selectedTab = this.selectedItem;
@@ -240,7 +225,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
selectedTab._notselectedsinceload = false;
}
@@ -1386,7 +1423,7 @@
@@ -1386,7 +1419,7 @@
* @param {boolean} [shouldScrollInstantly=false]
*/
#ensureTabIsVisible(tab, shouldScrollInstantly = false) {
@@ -249,7 +234,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c5
if (arrowScrollbox?.overflowing) {
arrowScrollbox.ensureElementIsVisible(tab, shouldScrollInstantly);
}
@@ -1513,7 +1550,7 @@
@@ -1513,7 +1546,7 @@
}
_notifyBackgroundTab(aTab) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
diff --git a/servo/components/style/values/generics/border.rs b/servo/components/style/values/generics/border.rs
--- a/servo/components/style/values/generics/border.rs
+++ b/servo/components/style/values/generics/border.rs
@@ -296,9 +296,9 @@
pub fn all(s: S) -> Self {
Self {
top_left: s.clone(),
top_right: s.clone(),
bottom_right: s.clone(),
- bottom_left: s.clone(),
+ bottom_left: s,
}
}
}

View File

@@ -0,0 +1,38 @@
diff --git a/gfx/wr/glsl-to-cxx/src/hir.rs b/gfx/wr/glsl-to-cxx/src/hir.rs
--- a/gfx/wr/glsl-to-cxx/src/hir.rs
+++ b/gfx/wr/glsl-to-cxx/src/hir.rs
@@ -3531,10 +3531,11 @@
None,
Type::new(Float),
vec![Type::new(Vec2)],
);
declare_function(state, "pow", None, Type::new(Vec3), vec![Type::new(Vec3)]);
+ declare_function(state, "pow", None, Type::new(Vec2), vec![Type::new(Vec2)]);
declare_function(state, "pow", None, Type::new(Float), vec![Type::new(Float)]);
declare_function(state, "exp", None, Type::new(Float), vec![Type::new(Float)]);
declare_function(state, "exp2", None, Type::new(Float), vec![Type::new(Float)]);
declare_function(state, "log", None, Type::new(Float), vec![Type::new(Float)]);
declare_function(state, "log2", None, Type::new(Float), vec![Type::new(Float)]);
diff --git a/gfx/wr/swgl/src/glsl.h b/gfx/wr/swgl/src/glsl.h
--- a/gfx/wr/swgl/src/glsl.h
+++ b/gfx/wr/swgl/src/glsl.h
@@ -800,10 +800,18 @@
Float pow(Float x, Float y) {
return if_then_else((x == 0) | (x == 1), x, approx_pow2(approx_log2(x) * y));
}
+vec2 pow(vec2 a, vec2 b) {
+ return vec2(pow(a.x, b.x), pow(a.y, b.y));
+}
+
+vec2_scalar pow(vec2_scalar a, vec2_scalar b) {
+ return vec2_scalar(pow(a.x, b.x), pow(a.y, b.y));
+}
+
#define exp __glsl_exp
SI float exp(float x) { return expf(x); }
Float exp(Float y) {

View File

@@ -0,0 +1,19 @@
diff --git a/gfx/wr/swgl/src/glsl.h b/gfx/wr/swgl/src/glsl.h
--- a/gfx/wr/swgl/src/glsl.h
+++ b/gfx/wr/swgl/src/glsl.h
@@ -1611,10 +1611,14 @@
vec3_scalar make_vec3(const vec2_scalar& v, float z) {
return vec3_scalar{v.x, v.y, z};
}
+vec3_scalar make_vec3(float x, const vec2_scalar& v) {
+ return vec3_scalar{x, v.x, v.y};
+}
+
vec3_scalar make_vec3(float x, float y, float z) {
return vec3_scalar{x, y, z};
}
vec3_scalar make_vec3(int32_t x, int32_t y, float z) {

View File

@@ -0,0 +1,22 @@
diff --git a/gfx/wr/swgl/src/glsl.h b/gfx/wr/swgl/src/glsl.h
--- a/gfx/wr/swgl/src/glsl.h
+++ b/gfx/wr/swgl/src/glsl.h
@@ -1599,10 +1599,17 @@
x += a.x;
y += a.y;
z += a.z;
return *this;
}
+
+ vec3& operator*=(Float a) {
+ x *= a;
+ y *= a;
+ z *= a;
+ return *this;
+ }
};
vec3_scalar force_scalar(const vec3& v) {
return vec3_scalar{force_scalar(v.x), force_scalar(v.y), force_scalar(v.z)};
}

View File

@@ -0,0 +1,20 @@
diff --git a/gfx/wr/swgl/src/glsl.h b/gfx/wr/swgl/src/glsl.h
--- a/gfx/wr/swgl/src/glsl.h
+++ b/gfx/wr/swgl/src/glsl.h
@@ -469,10 +469,15 @@
vec2_scalar_ref& operator=(const vec2_scalar& a) {
x = a.x;
y = a.y;
return *this;
}
+ vec2_scalar_ref& operator+=(vec2_scalar a) {
+ x += a.x;
+ y += a.y;
+ return *this;
+ }
vec2_scalar_ref& operator*=(vec2_scalar a) {
x *= a.x;
y *= a.y;
return *this;
}

View File

@@ -0,0 +1,67 @@
diff --git a/gfx/wr/glsl-to-cxx/src/hir.rs b/gfx/wr/glsl-to-cxx/src/hir.rs
--- a/gfx/wr/glsl-to-cxx/src/hir.rs
+++ b/gfx/wr/glsl-to-cxx/src/hir.rs
@@ -3537,10 +3537,12 @@
declare_function(state, "pow", None, Type::new(Float), vec![Type::new(Float)]);
declare_function(state, "exp", None, Type::new(Float), vec![Type::new(Float)]);
declare_function(state, "exp2", None, Type::new(Float), vec![Type::new(Float)]);
declare_function(state, "log", None, Type::new(Float), vec![Type::new(Float)]);
declare_function(state, "log2", None, Type::new(Float), vec![Type::new(Float)]);
+ declare_function(state, "isnan", None, Type::new(Bool), vec![Type::new(Float)]);
+ declare_function(state, "isinf", None, Type::new(Bool), vec![Type::new(Float)]);
for t in &[Float, Vec2] {
// recip is non-standard
declare_function(
state,
"recip",
diff --git a/gfx/wr/swgl/src/glsl.h b/gfx/wr/swgl/src/glsl.h
--- a/gfx/wr/swgl/src/glsl.h
+++ b/gfx/wr/swgl/src/glsl.h
@@ -205,10 +205,46 @@
#else
return (Float){sqrtf(v.x), sqrtf(v.y), sqrtf(v.z), sqrtf(v.w)};
#endif
}
+// NOTE: the Bool type is actually int under the hood,
+// and is used for bitwise return value masking in the
+// generated code ("ret_mask" variable).
+//
+// The ret_mask is initialized to -1 (0xffffffff), and
+// is then subsequently masked with condition results.
+//
+// If we use the boolean result directly here (0 or 1),
+// the bitwise AND ends up removing just one bit from
+// the mask, and it doesn't work.
+//
+// Taking the negative transforms 1 (single bit) into -1
+// (all bits 1), and correctly broadcasts the boolean
+// result into all bits of the ret_mask.
+//
+// If the condition is false, 0 becomes -0 which is the
+// same bit pattern for integers (all bits 0).
+
+SI Bool isnan(Float v) {
+ return (Bool){
+ -(fpclassify(v.x) == FP_NAN),
+ -(fpclassify(v.y) == FP_NAN),
+ -(fpclassify(v.z) == FP_NAN),
+ -(fpclassify(v.w) == FP_NAN)
+ };
+}
+
+SI Bool isinf(Float v) {
+ return (Bool){
+ -(fpclassify(v.x) == FP_INFINITE),
+ -(fpclassify(v.y) == FP_INFINITE),
+ -(fpclassify(v.z) == FP_INFINITE),
+ -(fpclassify(v.w) == FP_INFINITE)
+ };
+}
+
SI float recip(float x) {
#if USE_SSE2
return _mm_cvtss_f32(_mm_rcp_ss(_mm_set_ss(x)));
#else
return 1.0f / x;

View File

@@ -0,0 +1,49 @@
diff --git a/gfx/wr/webrender/res/debug.glsl b/gfx/wr/webrender/res/debug.glsl
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/res/debug.glsl
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Display a signed distance field as color
+//
+// It will be orange outside the shape (positive distance), and blue
+// inside (negative distance).
+//
+// Color will cycle every 10px.
+//
+// The shape outline (distance = 0) is drawn as a thin white outline.
+//
+// NaNs and infinite values will be drawn as red and yellow respectively.
+//
+// Example usage:
+//
+// #include debug
+//
+// void main(void) {
+// float d = compute_some_distance_in_pixels(...);
+// oFragColor = debug_sdf(d);
+// }
+//
+vec4 debug_sdf(float d) {
+ if (isnan(d)) {
+ return vec4(1.0, 0.0, 0.0, 1.0);
+ }
+ if (isinf(d)) {
+ return vec4(1.0, 1.0, 0.0, 1.0);
+ }
+
+ vec3 color = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.6, 0.8, 1.0);
+ color *= 1.0 - exp(-0.32 * abs(d));
+ color *= fract(d / 10.0) * 0.4 + 0.6;
+#ifdef SWGL
+ // SWGL doesn't support smoothstep() for now
+ color = mix(color, vec3(1.0), step(abs(d), 2.0));
+#else
+ color = mix(color, vec3(1.0), smoothstep(2.0, 0.0, abs(d)));
+#endif
+ return vec4(color, 1.0);
+}

View File

@@ -0,0 +1,456 @@
diff --git a/gfx/wr/wrench/reftests/clip/clip-superellipse.png b/gfx/wr/wrench/reftests/clip/clip-superellipse.png
new file mode 100644
index 0000000000000000000000000000000000000000..4470f8b707c255bc3e4eb026368945267ee895e6
GIT binary patch
literal 7953
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
G@_zvPNe@N<
literal 0
Hc$@<O00001
diff --git a/gfx/wr/wrench/reftests/clip/clip-superellipse.yaml b/gfx/wr/wrench/reftests/clip/clip-superellipse.yaml
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/clip/clip-superellipse.yaml
@@ -0,0 +1,174 @@
+---
+root:
+ items:
+ - type: clip
+ id: 100
+ complex:
+ - rect: [20, 20, 100, 100]
+ radius:
+ top-left: [32, 16]
+ top-right: [32, 16]
+ bottom-right: [32, 16]
+ bottom-left: [32, 16]
+ shape-top-left: 0.5
+ shape-top-right: +Inf
+ shape-bottom-left: -2.1
+ shape-bottom-right: 0
+ - type: clip-chain
+ id: 200
+ clips: [100]
+ - type: rect
+ bounds: [20, 20, 100, 100]
+ color: red
+ clip-chain: 200
+
+ - type: clip
+ id: 101
+ complex:
+ - rect: [130, 20, 100, 100]
+ radius:
+ top-left: [32, 16]
+ top-right: [32, 16]
+ bottom-right: [32, 16]
+ bottom-left: [32, 16]
+ shape-top-left: 0.5
+ shape-top-right: +Inf
+ shape-bottom-left: -2.1
+ shape-bottom-right: 0
+ clip-mode: clip-out
+ - type: clip-chain
+ id: 201
+ clips: [101]
+ - type: rect
+ bounds: [130, 20, 100, 100]
+ color: green
+ clip-chain: 201
+
+ - type: clip
+ id: 102
+ complex:
+ - rect: [20, 130, 100, 100]
+ radius:
+ top-left: [16, 32]
+ top-right: [16, 32]
+ bottom-right: [16, 32]
+ bottom-left: [16, 32]
+ shape-top-left: 0.5
+ shape-top-right: +Inf
+ shape-bottom-left: -2.1
+ shape-bottom-right: 0
+ - type: clip-chain
+ id: 202
+ clips: [102]
+ - type: rect
+ bounds: [20, 130, 100, 100]
+ color: red
+ clip-chain: 202
+
+ - type: clip
+ id: 103
+ complex:
+ - rect: [130, 130, 100, 100]
+ radius:
+ top-left: [16, 32]
+ top-right: [16, 32]
+ bottom-right: [16, 32]
+ bottom-left: [16, 32]
+ shape-top-left: 0.5
+ shape-top-right: +Inf
+ shape-bottom-left: -2.1
+ shape-bottom-right: 0
+ clip-mode: clip-out
+ - type: clip-chain
+ id: 203
+ clips: [103]
+ - type: rect
+ bounds: [130, 130, 100, 100]
+ color: green
+ clip-chain: 203
+
+ - type: clip
+ id: 104
+ complex:
+ - rect: [20, 240, 100, 100]
+ radius:
+ top-left: [128, 32]
+ top-right: [128, 32]
+ bottom-right: [128, 32]
+ bottom-left: [128, 32]
+ shape-top-left: 0.5
+ shape-top-right: +Inf
+ shape-bottom-left: -2.1
+ shape-bottom-right: 0
+ - type: clip-chain
+ id: 204
+ clips: [104]
+ - type: rect
+ bounds: [20, 240, 100, 100]
+ color: red
+ clip-chain: 204
+
+ - type: clip
+ id: 105
+ complex:
+ - rect: [130, 240, 100, 100]
+ radius:
+ top-left: [128, 32]
+ top-right: [128, 32]
+ bottom-right: [128, 32]
+ bottom-left: [128, 32]
+ shape-top-left: 0.5
+ shape-top-right: +Inf
+ shape-bottom-left: -2.1
+ shape-bottom-right: 0
+ clip-mode: clip-out
+ - type: clip-chain
+ id: 205
+ clips: [105]
+ - type: rect
+ bounds: [130, 240, 100, 100]
+ color: green
+ clip-chain: 205
+
+ - type: clip
+ id: 106
+ complex:
+ - rect: [20, 350, 100, 100]
+ radius:
+ top-left: [32, 128]
+ top-right: [32, 128]
+ bottom-right: [32, 128]
+ bottom-left: [32, 128]
+ shape-top-left: 0.5
+ shape-top-right: +Inf
+ shape-bottom-left: -2.1
+ shape-bottom-right: 0
+ - type: clip-chain
+ id: 206
+ clips: [106]
+ - type: rect
+ bounds: [20, 350, 100, 100]
+ color: red
+ clip-chain: 206
+
+ - type: clip
+ id: 107
+ complex:
+ - rect: [130, 350, 100, 100]
+ radius:
+ top-left: [32, 128]
+ top-right: [32, 128]
+ bottom-right: [32, 128]
+ bottom-left: [32, 128]
+ shape-top-left: 0.5
+ shape-top-right: +Inf
+ shape-bottom-left: -2.1
+ shape-bottom-right: 0
+ clip-mode: clip-out
+ - type: clip-chain
+ id: 207
+ clips: [107]
+ - type: rect
+ bounds: [130, 350, 100, 100]
+ color: green
+ clip-chain: 207
diff --git a/gfx/wr/wrench/reftests/clip/reftest.list b/gfx/wr/wrench/reftests/clip/reftest.list
--- a/gfx/wr/wrench/reftests/clip/reftest.list
+++ b/gfx/wr/wrench/reftests/clip/reftest.list
@@ -1,8 +1,9 @@
platform(linux,mac) == border-with-rounded-clip.yaml border-with-rounded-clip.png
fuzzy-if(platform(swgl),1,4) == clip-mode.yaml clip-mode.png
fuzzy-if(platform(swgl),1,80) == clip-ellipse.yaml clip-ellipse.png
+fuzzy-if(platform(android),128,200) fuzzy-if(platform(swgl),1,350) == clip-superellipse.yaml clip-superellipse.png
fuzzy(1,1000) platform(linux,mac) == clip-45-degree-rotation.yaml clip-45-degree-rotation-ref.png
== clip-3d-transform.yaml clip-3d-transform-ref.yaml
fuzzy(1,4) == clip-corner-overlap.yaml clip-corner-overlap-ref.yaml
== custom-clip-chain-node-ancestors.yaml custom-clip-chain-node-ancestors-ref.yaml
== fixed-position-clipping.yaml fixed-position-clipping-ref.yaml
diff --git a/gfx/wr/wrench/reftests/mask/reftest.list b/gfx/wr/wrench/reftests/mask/reftest.list
--- a/gfx/wr/wrench/reftests/mask/reftest.list
+++ b/gfx/wr/wrench/reftests/mask/reftest.list
@@ -14,5 +14,6 @@
platform(linux,mac) == checkerboard.yaml checkerboard.png
skip_on(android,device) fuzzy(2,1900) == checkerboard.yaml checkerboard-tiling.yaml # Fails on a Pixel2
== missing-mask.yaml missing-mask-ref.yaml
platform(linux) == scaled-filter-raster-root.yaml scaled-filter-raster-root.png
platform(linux,mac) == mask-multiple-coord-systems.yaml mask-multiple-coord-systems.png
+fuzzy(1,10) fuzzy-if(platform(swgl),5,100) == shaped-corners.yaml shaped-corners.png
diff --git a/gfx/wr/wrench/reftests/mask/shaped-corners.png b/gfx/wr/wrench/reftests/mask/shaped-corners.png
new file mode 100644
index 0000000000000000000000000000000000000000..0599a1f93a7d3564c6da950908fe495c98216f7c
GIT binary patch
literal 2026
zc%0Q$eK?bA943z5J|??Lm|-lE#i=vZtd)%+6X{b_9Ew_nmDQorSv$$wNS7|nmGoh=
zaK-vmYv-aQA6IG)P2#kAIbn0kXQhPB^X%IGJm>%O$L{C7@8`as-|zXoyRP?icvui_
zCSj(Di3u)v4SNGvU&9|_0nk3G`Gkqd^jpDf)~3|L51LRk^jBiTaC2(*yRyZ3apZG1
zb8+IQVYlO?jEao?yVx92`Kt3c`^RCCTW7m=kk}g~>IHF2veQPsJB*I4m~!l0nKC%3
z85}DdyRLfO?0vC}I+&EAdRFBbGuO0HhzYV0%SQCymS;7UcZ=unq?McLJSK7qn?_cq
zB3?8}RtClqXuC;@b0noLB4?v2Um?X#GI|E%pxOx+#W@%H6-537RXGVMK9bQ4%tN4^
zASpy7x)UPzL*-u)c^oQ#fXIcYJPnacP`M8xe~fzh3a`^7O<5R|Z-*yVJ1ZH8<#%Ug
zJ()IfQcK9fhGt3j5rwtM_#Hc~h*PMx|135X!H$SA&y2<In6x6UMdhmz%LI1BN?|Q(
zwIdO+BnR@PK|EV)iM@lcwiK^v#1r@W@yY(YQoEK8Jh5M>-H%uv^5@r*X=>}1Nr#%E
z0d$}lvBa_X4}*AnY$e~UYl@1{f!*err^ret#4^%KLlXKgXXn#7Rq3H;Yh7q+|28R^
zwkx7Xi&%!PZ-cn84&vrYh!HY?UYlI$!r$ySqd6GH=>T@c#$G6&Uk2oD@!3F*>W~0A
z;u?^h^MKpmHXgXGI3Iz~#m)hb8`R)2HUOBdqV7U9K$L@p&s-03d4VT^AnRTQa=KJt
z4(CLJDtY1tDEE3oISsmJL<9GxQ<*?M-})Zni)Rqk<q+GlA&OT5druFjBUli@H=jO1
z2?98~MGH2+TnP*e;yp_C_Mi*Rrmo^!@WdgTPoSV6R!s%c?0##98q>f}pmhPh6x_}%
zuv83kwizuj=YGZwNPCyG0;zO8emnkhhL0kFFz#^tt-sSlCo?l&$4tk`$qg}Y&o;by
zQzI=28Xt)t(hi7MrxO@TW${@j+1Q{=6{gdy)IGmg^H&?COuBPu-=kmZnlA;Q7{kNm
z*s$M`&ujN4=U-;zI<7kB$RO2w+}^_G;f98a_}}iSU%F`&OKshrK99oJ#Pi1F{2md1
zxErH7KW%`@;SUA;kz)SWgX8A1*a{RYc&BpEHR2yrX6*|wZ{L;g6@2uw_Rl(-C-GpD
z=$^!S|H}E2@0Hc8H7`YoDtGdm%s;c0NrU8qG)Pu;^aax$66edh?pvUZu*A+MgZ$jB
z6c<{FXx5?-F?v0p1zCe=n}z-IV$-p5YkXnK-|Z<WjA@``-}YgfMU*YYrtLuAZo+8#
zx*HUp=01g`edzSY#FZ|>S#e!{Q<V)=;I?^Eo!;72%YEbu343^5c~gFgX+DgSQu;;?
z;-VudE(+%IWU=bfTp}FHi9#Q8qNc#&HEiz0yc-txQV$S#a1=jK6A3qt>T+R)gdUFW
zWra5;(vxq8b?Pz$>3y}7J2qe>{hPTOrIAdfb9Jo-axJy}vK_TmKmoJxA|>Iz?1Z<7
zy2(5;?u}E#>PaO>nMO4b5BY}q5tOb>nBCJfU-!Pl8#|>Z?OwVs!v><oSY?&V3=#SP
zzs?87JUu<C^PZ=HD0!?-#Zu$RNi*i%x4`r#YsU|9pBESoE3F8Ps-hcQ-42H);6zL{
zzc2LMCboEe6OvWO7kftK8A#>*kkP&p(-m;I%;qa0qrEe7z>8D}qO0?Ip9PyG=?R@)
z+;QiHz#iWFYWodY<egRnxt}b%KsD~D!norfjkl_u&h1wlySg4y@RO^dtMQ4*0)~%)
z^bd&LLOF&qM~)Pqo@+epO*HrMNn`%TI)d+NV`MPD{!EA=f~cI}pX8560`J573A|eP
z%Gtar{ho-rJYg+?C#5Z%Z*CKQpdL}>cNcE8+Rm)ABhz9;DRC?wOaEcerviA@Fm)AC
z*}xO$)qITu)r2mz7&x17^S&(Di3mFeC(6PX{s_Bt7mn8bkHXAA^Xa?(Yxy`w)7_^S
TV>Bg#Z@5WtKp0!@_p|UHon4m#
literal 0
Hc$@<O00001
diff --git a/gfx/wr/wrench/reftests/mask/shaped-corners.yaml b/gfx/wr/wrench/reftests/mask/shaped-corners.yaml
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/mask/shaped-corners.yaml
@@ -0,0 +1,12 @@
+---
+root:
+ items:
+ - type: clip
+ id: 2
+ complex:
+ - rect: [10, 10, 200, 200]
+ radius: [200, 200, 200, 200, 8.2, 0, -2.8, +Inf]
+ - type: rect
+ clip-chain: [2]
+ bounds: [10, 10, 200, 200]
+ color: blue
diff --git a/gfx/wr/wrench/src/yaml_helper.rs b/gfx/wr/wrench/src/yaml_helper.rs
--- a/gfx/wr/wrench/src/yaml_helper.rs
+++ b/gfx/wr/wrench/src/yaml_helper.rs
@@ -206,10 +206,12 @@
impl YamlHelper for Yaml {
fn as_f32(&self) -> Option<f32> {
match *self {
Yaml::Integer(iv) => Some(iv as f32),
Yaml::Real(ref sv) => f32::from_str(sv.as_str()).ok(),
+ Yaml::String(ref sv) if sv == "+Inf" => Some(f32::INFINITY),
+ Yaml::String(ref sv) if sv == "-Inf" => Some(f32::NEG_INFINITY),
_ => None,
}
}
fn as_force_f32(&self) -> Option<f32> {
@@ -483,24 +485,48 @@
shape_top_right: 1.0,
shape_bottom_left: 1.0,
shape_bottom_right: 1.0,
})
}
+ Yaml::Array(ref array) if array.len() == 8 => {
+ let top_left = array[0].as_border_radius_component();
+ let top_right = array[1].as_border_radius_component();
+ let bottom_left = array[2].as_border_radius_component();
+ let bottom_right = array[3].as_border_radius_component();
+ let shape_top_left = array[4].as_f32().unwrap();
+ let shape_top_right = array[5].as_f32().unwrap();
+ let shape_bottom_left = array[6].as_f32().unwrap();
+ let shape_bottom_right = array[7].as_f32().unwrap();
+ Some(BorderRadius {
+ top_left,
+ top_right,
+ bottom_left,
+ bottom_right,
+ shape_top_left,
+ shape_top_right,
+ shape_bottom_left,
+ shape_bottom_right,
+ })
+ }
Yaml::Hash(_) => {
let top_left = self["top-left"].as_border_radius_component();
let top_right = self["top-right"].as_border_radius_component();
let bottom_left = self["bottom-left"].as_border_radius_component();
let bottom_right = self["bottom-right"].as_border_radius_component();
+ let shape_top_left = self["shape-top-left"].as_f32().unwrap_or(1.0);
+ let shape_top_right = self["shape-top-right"].as_f32().unwrap_or(1.0);
+ let shape_bottom_left = self["shape-bottom-left"].as_f32().unwrap_or(1.0);
+ let shape_bottom_right = self["shape-bottom-right"].as_f32().unwrap_or(1.0);
Some(BorderRadius {
top_left,
top_right,
bottom_left,
bottom_right,
- shape_top_left: 1.0,
- shape_top_right: 1.0,
- shape_bottom_left: 1.0,
- shape_bottom_right: 1.0,
+ shape_top_left,
+ shape_top_right,
+ shape_bottom_left,
+ shape_bottom_right,
})
}
_ => {
panic!("Invalid border radius specified: {:?}", self);
}

View File

@@ -0,0 +1,592 @@
diff --git a/gfx/wr/webrender/res/border_shared.glsl b/gfx/wr/webrender/res/border_shared.glsl
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/res/border_shared.glsl
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include gpu_buffer
+
+#define SEGMENT_TOP_LEFT 0
+#define SEGMENT_TOP_RIGHT 1
+#define SEGMENT_BOTTOM_RIGHT 2
+#define SEGMENT_BOTTOM_LEFT 3
+#define SEGMENT_LEFT 4
+#define SEGMENT_TOP 5
+#define SEGMENT_RIGHT 6
+#define SEGMENT_BOTTOM 7
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec2 aTaskOrigin;
+PER_INSTANCE in int aFlags;
+PER_INSTANCE in int aGpuDataAddress;
+PER_INSTANCE in vec4 aClipParams1;
+PER_INSTANCE in vec4 aClipParams2;
+
+struct BorderInstanceGpuData {
+ vec4 rect;
+ vec4 color0;
+ vec4 color1;
+ vec2 widths;
+ vec2 radii;
+ float shape;
+};
+
+BorderInstanceGpuData fetch_gpu_data(int index) {
+ BorderInstanceGpuData data;
+
+ vec4 texels[5] = fetch_from_gpu_buffer_5f(index);
+ data.rect = texels[0];
+ data.color0 = texels[1];
+ data.color1 = texels[2];
+ data.widths = texels[3].xy;
+ data.radii = texels[3].zw;
+ data.shape = texels[4].x;
+
+ return data;
+}
+
+#endif
diff --git a/gfx/wr/webrender/res/cs_border_segment.glsl b/gfx/wr/webrender/res/cs_border_segment.glsl
--- a/gfx/wr/webrender/res/cs_border_segment.glsl
+++ b/gfx/wr/webrender/res/cs_border_segment.glsl
@@ -1,10 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#include shared,rect,ellipse
+#include shared,rect,border_shared,ellipse
// For edges, the colors are the same. For corners, these
// are the colors of each edge making up the corner.
flat varying mediump vec4 vColor00;
flat varying mediump vec4 vColor01;
@@ -44,19 +44,10 @@
flat varying mediump vec4 vClipParams2;
// Local space position
varying highp vec2 vPos;
-#define SEGMENT_TOP_LEFT 0
-#define SEGMENT_TOP_RIGHT 1
-#define SEGMENT_BOTTOM_RIGHT 2
-#define SEGMENT_BOTTOM_LEFT 3
-#define SEGMENT_LEFT 4
-#define SEGMENT_TOP 5
-#define SEGMENT_RIGHT 6
-#define SEGMENT_BOTTOM 7
-
// Border styles as defined in webrender_api/types.rs
#define BORDER_STYLE_NONE 0
#define BORDER_STYLE_SOLID 1
#define BORDER_STYLE_DOUBLE 2
#define BORDER_STYLE_DOTTED 3
@@ -72,20 +63,10 @@
#define CLIP_DASH_EDGE 2
#define CLIP_DOT 3
#ifdef WR_VERTEX_SHADER
-PER_INSTANCE in vec2 aTaskOrigin;
-PER_INSTANCE in vec4 aRect;
-PER_INSTANCE in vec4 aColor0;
-PER_INSTANCE in vec4 aColor1;
-PER_INSTANCE in int aFlags;
-PER_INSTANCE in vec2 aWidths;
-PER_INSTANCE in vec2 aRadii;
-PER_INSTANCE in vec4 aClipParams1;
-PER_INSTANCE in vec4 aClipParams2;
-
vec2 get_outer_corner_scale(int segment) {
vec2 p;
switch (segment) {
case SEGMENT_TOP_LEFT:
@@ -153,16 +134,18 @@
return result;
}
void main(void) {
+ BorderInstanceGpuData data = fetch_gpu_data(aGpuDataAddress);
+
int segment = aFlags & 0xff;
int style0 = (aFlags >> 8) & 0xff;
int style1 = (aFlags >> 16) & 0xff;
int clip_mode = (aFlags >> 24) & 0x0f;
- vec2 size = aRect.zw - aRect.xy;
+ vec2 size = data.rect.zw - data.rect.xy;
vec2 outer_scale = get_outer_corner_scale(segment);
vec2 outer = outer_scale * size;
vec2 clip_sign = 1.0 - 2.0 * outer_scale;
// Set some flags used by the FS to determine the
@@ -176,19 +159,19 @@
edge_axis = ivec2(0, 1);
edge_reference = outer;
break;
case SEGMENT_TOP_RIGHT:
edge_axis = ivec2(1, 0);
- edge_reference = vec2(outer.x - aWidths.x, outer.y);
+ edge_reference = vec2(outer.x - data.widths.x, outer.y);
break;
case SEGMENT_BOTTOM_RIGHT:
edge_axis = ivec2(0, 1);
- edge_reference = outer - aWidths;
+ edge_reference = outer - data.widths;
break;
case SEGMENT_BOTTOM_LEFT:
edge_axis = ivec2(1, 0);
- edge_reference = vec2(outer.x, outer.y - aWidths.y);
+ edge_reference = vec2(outer.x, outer.y - data.widths.y);
break;
case SEGMENT_TOP:
case SEGMENT_BOTTOM:
edge_axis = ivec2(1, 1);
break;
@@ -199,23 +182,23 @@
}
vSegmentClipMode = vec2(float(segment), float(clip_mode));
vStyleEdgeAxis = vec4(float(style0), float(style1), float(edge_axis.x), float(edge_axis.y));
- vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0);
+ vPartialWidths = vec4(data.widths / 3.0, data.widths / 2.0);
vPos = size * aPosition.xy;
- vec4[2] color0 = get_colors_for_side(aColor0, style0);
+ vec4[2] color0 = get_colors_for_side(data.color0, style0);
vColor00 = color0[0];
vColor01 = color0[1];
- vec4[2] color1 = get_colors_for_side(aColor1, style1);
+ vec4[2] color1 = get_colors_for_side(data.color1, style1);
vColor10 = color1[0];
vColor11 = color1[1];
- vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
- vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
- vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
- vEdgeReference = vec4(edge_reference, edge_reference + aWidths);
+ vClipCenter_Sign = vec4(outer + clip_sign * data.radii, clip_sign);
+ vClipRadii = vec4(data.radii, max(data.radii - data.widths, 0.0));
+ 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;
// For the case of dot and dash clips, optimize the number of pixels that
// are hit to just include the dot itself.
@@ -234,17 +217,17 @@
// This is a gross approximation which works out because dashes don't have
// a strong curvature and we will overshoot by inflating the geometry by
// this amount on each side (sqrt(2) * length(dash) would be enough and we
// compute 2 * approx_length(dash)).
float dash_length = length(aClipParams1.xy - aClipParams2.xy);
- float width = max(aWidths.x, aWidths.y);
+ float width = max(data.widths.x, data.widths.y);
// expand by a small amout for AA just like we do for dots.
vec2 r = vec2(max(dash_length, width)) + 2.0;
vPos = clamp(vPos, center - r, center + r);
}
- gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
+ gl_Position = uTransform * vec4(aTaskOrigin + data.rect.xy + vPos, 0.0, 1.0);
}
#endif
#ifdef WR_FRAGMENT_SHADER
vec4 evaluate_color_for_style_in_corner(
diff --git a/gfx/wr/webrender/res/cs_border_solid.glsl b/gfx/wr/webrender/res/cs_border_solid.glsl
--- a/gfx/wr/webrender/res/cs_border_solid.glsl
+++ b/gfx/wr/webrender/res/cs_border_solid.glsl
@@ -1,10 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#include shared,rect,ellipse
+#include shared,rect,border_shared,ellipse
#define DONT_MIX 0
#define MIX_AA 1
#define MIX_NO_AA 2
@@ -37,27 +37,12 @@
flat varying highp vec2 vVerticalClipRadii;
// Local space position
varying highp vec2 vPos;
-#define SEGMENT_TOP_LEFT 0
-#define SEGMENT_TOP_RIGHT 1
-#define SEGMENT_BOTTOM_RIGHT 2
-#define SEGMENT_BOTTOM_LEFT 3
-
#ifdef WR_VERTEX_SHADER
-PER_INSTANCE in vec2 aTaskOrigin;
-PER_INSTANCE in vec4 aRect;
-PER_INSTANCE in vec4 aColor0;
-PER_INSTANCE in vec4 aColor1;
-PER_INSTANCE in int aFlags;
-PER_INSTANCE in vec2 aWidths;
-PER_INSTANCE in vec2 aRadii;
-PER_INSTANCE in vec4 aClipParams1;
-PER_INSTANCE in vec4 aClipParams2;
-
vec2 get_outer_corner_scale(int segment) {
vec2 p;
switch (segment) {
case SEGMENT_TOP_LEFT:
@@ -80,15 +65,17 @@
return p;
}
void main(void) {
+ BorderInstanceGpuData data = fetch_gpu_data(aGpuDataAddress);
+
int segment = aFlags & 0xff;
bool do_aa = ((aFlags >> 24) & 0xf0) != 0;
vec2 outer_scale = get_outer_corner_scale(segment);
- vec2 size = aRect.zw - aRect.xy;
+ vec2 size = data.rect.zw - data.rect.xy;
vec2 outer = outer_scale * size;
vec2 clip_sign = 1.0 - 2.0 * outer_scale;
int mix_colors;
switch (segment) {
@@ -105,15 +92,15 @@
}
vMixColors.x = mix_colors;
vPos = size * aPosition.xy;
- vColor0 = aColor0;
- vColor1 = aColor1;
- vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
- vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
- vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
+ vColor0 = data.color0;
+ vColor1 = data.color1;
+ vClipCenter_Sign = vec4(outer + clip_sign * data.radii, clip_sign);
+ vClipRadii = vec4(data.radii, max(data.radii - data.widths, 0.0));
+ vColorLine = vec4(outer, data.widths.y * -clip_sign.y, data.widths.x * clip_sign.x);
vec2 horizontal_clip_sign = vec2(-clip_sign.x, clip_sign.y);
vHorizontalClipCenter_Sign = vec4(aClipParams1.xy +
horizontal_clip_sign * aClipParams1.zw,
horizontal_clip_sign);
@@ -123,11 +110,11 @@
vVerticalClipCenter_Sign = vec4(aClipParams2.xy +
vertical_clip_sign * aClipParams2.zw,
vertical_clip_sign);
vVerticalClipRadii = aClipParams2.zw;
- gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
+ gl_Position = uTransform * vec4(aTaskOrigin + data.rect.xy + vPos, 0.0, 1.0);
}
#endif
#ifdef WR_FRAGMENT_SHADER
void main(void) {
diff --git a/gfx/wr/webrender/src/border.rs b/gfx/wr/webrender/src/border.rs
--- a/gfx/wr/webrender/src/border.rs
+++ b/gfx/wr/webrender/src/border.rs
@@ -6,13 +6,14 @@
use api::{NormalBorder as ApiNormalBorder, RepeatMode};
use api::units::*;
use crate::clip::ClipNodeId;
use crate::ellipse::Ellipse;
use euclid::vec2;
+use crate::renderer::GpuBufferBuilderF;
use crate::scene_building::SceneBuilder;
use crate::spatial_tree::SpatialNodeIndex;
-use crate::gpu_types::{BorderInstance, BorderSegment, BrushFlags};
+use crate::gpu_types::{BorderInstance, BorderInstanceGpuData, BorderSegment, BrushFlags};
use crate::prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor};
use crate::prim_store::borders::NormalBorderPrim;
use crate::util::{lerp, RectHelpers};
use crate::internal_types::LayoutPrimitiveInfo;
use crate::segment::EdgeMask;
@@ -125,10 +126,11 @@
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BorderSegmentCacheKey {
pub size: LayoutSizeAu,
pub radius: LayoutSizeAu,
+ pub shape: u32,
pub side0: BorderSideAu,
pub side1: BorderSideAu,
pub segment: BorderSegment,
pub do_aa: bool,
pub h_adjacent_corner_outer: LayoutPointAu,
@@ -762,10 +764,11 @@
),
border.left,
border.top,
LayoutSize::new(widths.left, widths.top),
border.radius.top_left,
+ border.radius.shape_top_left,
BorderSegment::TopLeft,
EdgeMask::TOP | EdgeMask::LEFT,
rect.top_right(),
border.radius.top_right,
rect.bottom_left(),
@@ -789,10 +792,11 @@
),
border.top,
border.right,
LayoutSize::new(widths.right, widths.top),
border.radius.top_right,
+ border.radius.shape_top_right,
BorderSegment::TopRight,
EdgeMask::TOP | EdgeMask::RIGHT,
rect.min,
border.radius.top_left,
rect.max,
@@ -816,10 +820,11 @@
),
border.right,
border.bottom,
LayoutSize::new(widths.right, widths.bottom),
border.radius.bottom_right,
+ border.radius.shape_bottom_right,
BorderSegment::BottomRight,
EdgeMask::BOTTOM | EdgeMask::RIGHT,
rect.bottom_left(),
border.radius.bottom_left,
rect.top_right(),
@@ -843,10 +848,11 @@
),
border.bottom,
border.left,
LayoutSize::new(widths.left, widths.bottom),
border.radius.bottom_left,
+ border.radius.shape_bottom_left,
BorderSegment::BottomLeft,
EdgeMask::BOTTOM | EdgeMask::LEFT,
rect.max,
border.radius.bottom_right,
rect.min,
@@ -881,30 +887,37 @@
color1: ColorF,
segment: BorderSegment,
instances: &mut Vec<BorderInstance>,
widths: DeviceSize,
radius: DeviceSize,
+ shape: f32,
do_aa: bool,
h_adjacent_corner_outer: DevicePoint,
h_adjacent_corner_radius: DeviceSize,
v_adjacent_corner_outer: DevicePoint,
v_adjacent_corner_radius: DeviceSize,
+ gpu_buffer_builder: &mut GpuBufferBuilderF,
) {
let base_flags = (segment as i32) |
((style0 as i32) << 8) |
((style1 as i32) << 16) |
((do_aa as i32) << 28);
- let base_instance = BorderInstance {
- task_origin: DevicePoint::zero(),
+ let instance_gpu_data = BorderInstanceGpuData {
local_rect: task_rect,
- flags: base_flags,
color0: color0.premultiplied(),
color1: color1.premultiplied(),
widths,
radius,
+ shape
+ };
+
+ let base_instance = BorderInstance {
+ task_origin: DevicePoint::zero(),
+ flags: base_flags,
clip_params: [0.0; 8],
+ gpu_data_address: instance_gpu_data.write(gpu_buffer_builder)
};
match segment {
BorderSegment::TopLeft |
BorderSegment::TopRight |
@@ -1018,10 +1031,11 @@
non_overlapping_rect: LayoutRect,
side0: BorderSide,
side1: BorderSide,
widths: LayoutSize,
radius: LayoutSize,
+ shape: f32,
segment: BorderSegment,
edge_flags: EdgeMask,
h_adjacent_corner_outer: LayoutPoint,
h_adjacent_corner_radius: LayoutSize,
v_adjacent_corner_outer: LayoutPoint,
@@ -1137,10 +1151,11 @@
do_aa,
side0: side0.into(),
side1: side1.into(),
segment,
radius: radius.to_au(),
+ shape: shape.to_bits(),
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(),
v_adjacent_corner_outer: (v_corner_outer - image_rect.min).to_point().to_au(),
v_adjacent_corner_radius: v_corner_radius.to_au(),
@@ -1200,10 +1215,11 @@
cache_key: BorderSegmentCacheKey {
do_aa,
side0: side.into(),
side1: side.into(),
radius: LayoutSizeAu::zero(),
+ shape: 0,
size: size.to_au(),
segment,
h_adjacent_corner_outer: LayoutPointAu::zero(),
h_adjacent_corner_radius: LayoutSizeAu::zero(),
v_adjacent_corner_outer: LayoutPointAu::zero(),
@@ -1217,10 +1233,11 @@
pub fn build_border_instances(
cache_key: &BorderSegmentCacheKey,
cache_size: DeviceIntSize,
border: &ApiNormalBorder,
scale: LayoutToDeviceScale,
+ gpu_buffer_builder: &mut GpuBufferBuilderF,
) -> Vec<BorderInstance> {
let mut instances = Vec::new();
let (side0, side1, flip0, flip1) = match cache_key.segment {
BorderSegment::Left => (&border.left, &border.left, false, false),
@@ -1247,10 +1264,11 @@
let color0 = side0.border_color(flip0);
let color1 = side1.border_color(flip1);
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 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();
@@ -1263,15 +1281,17 @@
color1,
cache_key.segment,
&mut instances,
widths,
radius,
+ shape,
border.do_aa,
h_corner_outer,
h_corner_radius,
v_corner_outer,
v_corner_radius,
+ gpu_buffer_builder,
);
instances
}
diff --git a/gfx/wr/webrender/src/gpu_types.rs b/gfx/wr/webrender/src/gpu_types.rs
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -192,22 +192,40 @@
Top,
Right,
Bottom,
}
+pub struct BorderInstanceGpuData {
+ pub local_rect: DeviceRect,
+ pub color0: PremultipliedColorF,
+ pub color1: PremultipliedColorF,
+ pub widths: DeviceSize,
+ pub radius: DeviceSize,
+ pub shape: f32,
+}
+
+impl BorderInstanceGpuData {
+ pub fn write(&self, gpu_buffer_builder: &mut GpuBufferBuilderF) -> GpuBufferAddress {
+ let mut writer = gpu_buffer_builder.write_blocks(5);
+ 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.finish()
+ }
+}
+
#[derive(Debug, Clone)]
#[repr(C)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BorderInstance {
pub task_origin: DevicePoint,
- pub local_rect: DeviceRect,
- pub color0: PremultipliedColorF,
- pub color1: PremultipliedColorF,
pub flags: i32,
- pub widths: DeviceSize,
- pub radius: DeviceSize,
+ pub gpu_data_address: GpuBufferAddress,
pub clip_params: [f32; 8],
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
diff --git a/gfx/wr/webrender/src/prim_store/borders.rs b/gfx/wr/webrender/src/prim_store/borders.rs
--- a/gfx/wr/webrender/src/prim_store/borders.rs
+++ b/gfx/wr/webrender/src/prim_store/borders.rs
@@ -230,19 +230,20 @@
false, // TODO(gw): We don't calculate opacity for borders yet!
RenderTaskParent::Surface,
&mut frame_state.frame_gpu_data.f32,
frame_state.rg_builder,
&mut frame_state.surface_builder,
- &mut |rg_builder, _| {
+ &mut |rg_builder, gpu_buffer_builder| {
rg_builder.add().init(RenderTask::new_dynamic(
cache_size,
RenderTaskKind::new_border_segment(
build_border_instances(
&segment.cache_key,
cache_size,
&self.border,
scale,
+ gpu_buffer_builder,
)
),
))
}
);
diff --git a/gfx/wr/webrender/src/renderer/vertex.rs b/gfx/wr/webrender/src/renderer/vertex.rs
--- a/gfx/wr/webrender/src/renderer/vertex.rs
+++ b/gfx/wr/webrender/src/renderer/vertex.rs
@@ -63,16 +63,12 @@
pub const BORDER: VertexDescriptor = VertexDescriptor {
vertex_attributes: &[VertexAttribute::quad_instance_vertex()],
instance_attributes: &[
VertexAttribute::f32x2("aTaskOrigin"),
- VertexAttribute::f32x4("aRect"),
- VertexAttribute::f32x4("aColor0"),
- VertexAttribute::f32x4("aColor1"),
VertexAttribute::i32("aFlags"),
- VertexAttribute::f32x2("aWidths"),
- VertexAttribute::f32x2("aRadii"),
+ VertexAttribute::gpu_buffer_address("aGpuDataAddress"),
VertexAttribute::f32x4("aClipParams1"),
VertexAttribute::f32x4("aClipParams2"),
],
};

View File

@@ -2,6 +2,24 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
[
{
"type": "phabricator",
"ids": [
"D296935",
"D303334",
"D297660",
"D304517",
"D306773",
"D306774",
"D306775",
"D306776",
"D306777",
"D306778",
"D306779"
],
"name": "Corner shape support"
},
{
"type": "phabricator",
"id": "D299584",

View File

@@ -16,4 +16,3 @@ category app-startup nsBrowserGlue @mozilla.org/browser/browserglue;1 applicatio
#include common/Components.manifest
#include sessionstore/SessionComponents.manifest
#include live-folders/LiveFoldersComponents.manifest
#include space-routing/SpaceRoutingComponents.manifest

View File

@@ -6,29 +6,29 @@ import { nsZenDOMOperatedFeature } from "chrome://browser/content/zen-components
// prettier-ignore
const SVG_ICONS = [
"airplane.svg", "american-football.svg", "baseball.svg", "basket.svg",
"bed.svg", "bell.svg", "bookmark.svg", "book.svg",
"briefcase.svg", "brush.svg", "bug.svg", "build.svg",
"cafe.svg", "call.svg", "card.svg", "chat.svg",
"checkbox.svg", "circle.svg", "cloud.svg", "code.svg",
"coins.svg", "construct.svg", "cutlery.svg", "egg.svg",
"extension-puzzle.svg", "eye.svg", "fast-food.svg", "fish.svg",
"flag.svg", "flame.svg", "flask.svg", "folder.svg",
"game-controller.svg", "globe-1.svg", "globe.svg", "grid-2x2.svg",
"grid-3x3.svg", "heart.svg", "ice-cream.svg", "image.svg",
"inbox.svg", "key.svg", "layers.svg", "leaf.svg",
"lightning.svg", "location.svg", "lock-closed.svg", "logo-rss.svg",
"logo-usd.svg", "mail.svg", "map.svg", "megaphone.svg",
"moon.svg", "music.svg", "navigate.svg", "nuclear.svg",
"page.svg", "palette.svg", "paw.svg", "people.svg",
"pizza.svg", "planet.svg", "present.svg", "rocket.svg",
"school.svg", "shapes.svg", "shirt.svg", "skull.svg",
"squares.svg", "square.svg", "star-1.svg", "star.svg",
"stats-chart.svg", "sun.svg", "tada.svg", "terminal.svg",
"ticket.svg", "time.svg", "trash.svg", "triangle.svg",
"video.svg", "volume-high.svg", "wallet.svg", "warning.svg",
"water.svg", "weight.svg",
];
"airplane.svg", "american-football.svg", "baseball.svg", "basket.svg",
"bed.svg", "bell.svg", "bookmark.svg", "book.svg",
"briefcase.svg", "brush.svg", "bug.svg", "build.svg",
"cafe.svg", "call.svg", "card.svg", "chat.svg",
"checkbox.svg", "circle.svg", "cloud.svg", "code.svg",
"coins.svg", "construct.svg", "cutlery.svg", "egg.svg",
"extension-puzzle.svg", "eye.svg", "fast-food.svg", "fish.svg",
"flag.svg", "flame.svg", "flask.svg", "folder.svg",
"game-controller.svg", "globe-1.svg", "globe.svg", "grid-2x2.svg",
"grid-3x3.svg", "heart.svg", "ice-cream.svg", "image.svg",
"inbox.svg", "key.svg", "layers.svg", "leaf.svg",
"lightning.svg", "location.svg", "lock-closed.svg", "logo-rss.svg",
"logo-usd.svg", "mail.svg", "map.svg", "megaphone.svg",
"moon.svg", "music.svg", "navigate.svg", "nuclear.svg",
"page.svg", "palette.svg", "paw.svg", "people.svg",
"pizza.svg", "planet.svg", "present.svg", "rocket.svg",
"school.svg", "shapes.svg", "shirt.svg", "skull.svg",
"squares.svg", "square.svg", "star-1.svg", "star.svg",
"stats-chart.svg", "sun.svg", "tada.svg", "terminal.svg",
"ticket.svg", "time.svg", "trash.svg", "triangle.svg",
"video.svg", "volume-high.svg", "wallet.svg", "warning.svg",
"water.svg", "weight.svg",
];
class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
#panel;
@@ -47,7 +47,6 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
init() {
this.#panel = document.getElementById("PanelUI-zen-emojis-picker");
this.#panel.addEventListener("popupshowing", this);
this.#panel.addEventListener("popupshown", this);
this.#panel.addEventListener("popuphidden", this);
this.#panel.addEventListener("command", this);
this.searchInput.addEventListener("input", this);
@@ -58,9 +57,6 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
case "popupshowing":
this.#onPopupShowing(event);
break;
case "popupshown":
this.#onPopupShown(event);
break;
case "popuphidden":
this.#onPopupHidden(event);
break;
@@ -107,20 +103,17 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
return document.getElementById("PanelUI-zen-emojis-picker-search");
}
#changePage(toSvg = false, { animate = true } = {}) {
const pages = document.getElementById("PanelUI-zen-emojis-picker-pages");
#changePage(toSvg = false) {
const itemToScroll = toSvg
? this.svgList
: pages.querySelector('[emojis="true"]');
if (animate) {
itemToScroll.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "start",
});
} else {
pages.scrollLeft = toSvg ? itemToScroll.offsetLeft : 0;
}
: document
.getElementById("PanelUI-zen-emojis-picker-pages")
.querySelector('[emojis="true"]');
itemToScroll.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "start",
});
const button = document.getElementById(
`PanelUI-zen-emojis-picker-change-${toSvg ? "svg" : "emojis"}`
);
@@ -187,6 +180,9 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
});
emojiList.appendChild(item);
}
setTimeout(() => {
this.searchInput.focus();
}, 500);
}
const svgList = this.svgList;
for (const icon of SVG_ICONS) {
@@ -203,23 +199,14 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
}
}
#onPopupShown(event) {
if (event.target !== this.#panel) {
return;
}
const allowEmojis = !this.#panel.hasAttribute("only-svg-icons");
if (allowEmojis) {
this.searchInput.focus({ preventScroll: true });
}
this.#changePage(false, { animate: false });
}
#onPopupHidden(event) {
if (event.target !== this.#panel) {
return;
}
this.#clearEmojis();
this.#changePage(false);
const emojiList = this.emojiList;
emojiList.innerHTML = "";

View File

@@ -185,7 +185,7 @@
.toolbarbutton-1:not(#tabs-newtab-button),
.urlbar-page-action,
.identity-box-button {
--tab-border-radius: 6px;
--tab-border-radius: 8px;
--toolbarbutton-border-radius: var(--tab-border-radius);
--toolbarbutton-padding-inner: 6px;
--toolbarbutton-padding-outer: 1px;

View File

@@ -213,6 +213,8 @@
--toolbarbutton-border-radius: 6px;
--urlbar-margin-inline: 1px !important;
--zen-squircle-value: 1.4;
--tab-icon-overlay-stroke: light-dark(white, black) !important;
--tab-close-button-padding: 4px !important;
@@ -343,3 +345,9 @@
}
%include zen-buttons.css
@media (-moz-pref('layout.css.corner-shape.enabled')) {
*:not(.no-squircles) {
corner-shape: superellipse(var(--zen-squircle-value));
}
}

View File

@@ -35,6 +35,13 @@ export class ZenSpaceRoutingNavigation extends ZenUIComponent {
return;
}
// The tab we spawn for a route must be allowed to load once without being
// redirected again, regardless of when its workspace attribute lands.
if (aBrowser._zenSkipNavRouteOnce) {
aBrowser._zenSkipNavRouteOnce = false;
return;
}
let uri;
try {
uri = aRequest.QueryInterface(Ci.nsIChannel).URI;
@@ -73,49 +80,16 @@ export class ZenSpaceRoutingNavigation extends ZenUIComponent {
}
const currentWorkspaceId = tab.getAttribute("zen-workspace-id");
const targetWorkspaceId =
win.gZenSpaceRoutingManager.getRedirectTargetWorkspaceId(
if (
!win.gZenSpaceRoutingManager.shouldRedirectNavigation(
uri.spec,
currentWorkspaceId,
win
);
if (!targetWorkspaceId) {
)
) {
return;
}
// A brand-new tab whose very first real navigation this is (a
// target="_blank" link, window.open(), or a freshly opened tab) is still
// showing its initial about:blank document. There is nothing to preserve,
// so rather than cancelling the load and spawning a duplicate tab - which
// would leave this one behind empty - just move this very tab into the
// destination space and let the in-flight load finish in place.
const isInitialDocument =
aBrowser.browsingContext?.currentWindowGlobal?.isInitialDocument ?? false;
if (isInitialDocument) {
const wasSelected = tab.selected;
// Defer so we don't mutate the tab strip from inside a progress notification.
win.setTimeout(() => {
if (!tab.isConnected) {
return;
}
gBrowser.selectedTab = tab.owner;
win.gZenWorkspaces.moveTabToWorkspace(tab, targetWorkspaceId);
if (wasSelected) {
const targetWorkspace =
win.gZenWorkspaces.getWorkspaceFromId(targetWorkspaceId);
if (targetWorkspace) {
win.gZenWorkspaces.lastSelectedWorkspaceTabs[targetWorkspaceId] =
tab;
win.gZenWorkspaces.changeWorkspace(targetWorkspace);
}
}
}, 0);
return;
}
// An already-loaded page is navigating in place. Preserve it in its current
// tab and re-open the destination in a new routed tab instead.
//
// Under Fission the parent-side aRequest is a RemoteWebProgress stand-in
// whose cancel()/loadInfo throw NS_ERROR_NOT_IMPLEMENTED (the real channel
// lives in the content process). Stop the in-place load through the browser,
@@ -137,14 +111,13 @@ export class ZenSpaceRoutingNavigation extends ZenUIComponent {
// Defer so we don't mutate the tab strip from inside a progress notification.
win.setTimeout(() => {
gBrowser.addTab(urlToOpen, {
const newTab = gBrowser.addTab(urlToOpen, {
triggeringPrincipal: principal,
ownerTab: tab.isConnected ? tab : null,
// The user was actively navigating this tab, so follow the navigation
// into the routed tab instead of opening it in the background (addTab
// defaults inBackground to true).
inBackground: false,
});
if (newTab?.linkedBrowser) {
newTab.linkedBrowser._zenSkipNavRouteOnce = true;
}
}, 0);
}
}

View File

@@ -78,9 +78,6 @@ document.addEventListener(
case "cmd_zenReplacePinnedUrlWithCurrent":
gZenPinnedTabManager.replacePinnedUrlWithCurrent();
break;
case "cmd_zenEditPinnedUrl":
gZenPinnedTabManager.editPinnedUrl();
break;
case "cmd_contextZenAddToEssentials":
gZenPinnedTabManager.addToEssentials();
break;

View File

@@ -723,11 +723,8 @@
const { isNearLeftEdge, isNearRightEdge } =
this.#shouldSwitchSpace(event);
if (isNearLeftEdge || isNearRightEdge) {
if (!this.#changeSpaceTimer && !this.#isOutOfWindow) {
if (!this.#changeSpaceTimer) {
this.#changeSpaceTimer = setTimeout(() => {
if (this.#isOutOfWindow) {
return;
}
this.clearDragOverVisuals();
gZenWorkspaces
.changeWorkspaceShortcut(
@@ -959,10 +956,8 @@
if (ownerGlobal?.gZenCompactModeManager) {
// Sometimes, dragend doesn't always get called when dragging
// to different windows, see gh-8643.
requestAnimationFrame(() => {
delete ownerGlobal.gZenCompactModeManager._isTabBeingDragged;
ownerGlobal.gZenCompactModeManager._clearAllHoverStates();
});
delete ownerGlobal.gZenCompactModeManager._isTabBeingDragged;
ownerGlobal.gZenCompactModeManager._clearAllHoverStates();
}
this.clearSpaceSwitchTimer();
gZenFolders.highlightGroupOnDragOver(null);

View File

@@ -53,10 +53,6 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
#setupEventListeners() {
window.addEventListener("TabClose", this.onTabClose.bind(this));
window.addEventListener("TabSelect", this.onLocationChange.bind(this));
window.addEventListener(
"MozDOMFullscreen:Entered",
this.onFullscreenEntered.bind(this)
);
document
.getElementById("tabbrowser-tabpanels")
@@ -1418,23 +1414,6 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
}
}
/**
* Handle DOM Fullscreen request while inside glance
*
* @param {Event} event - The MozDOMFullscreen:Entered event
*/
onFullscreenEntered(event) {
const browser = this.#currentBrowser;
if (!browser) {
return;
}
if (event.target === browser) {
this.fullyOpenGlance();
}
}
/**
* Manage tab close for glance tabs
*

View File

@@ -41,11 +41,11 @@
&:hover {
background: light-dark(rgb(41, 41, 41), rgb(204, 204, 204));
scale: 1.05;
scale: 1.02;
}
&:hover:active {
scale: 0.95;
scale: 0.98;
}
& label {

View File

@@ -4,8 +4,8 @@
<html:template id="zen-glance-sidebar-template">
<vbox class="zen-glance-sidebar-container">
<toolbarbutton class="zen-glance-sidebar-close toolbarbutton-1" command="cmd_zenGlanceClose" data-l10n-id="zen-general-confirm" />
<toolbarbutton class="zen-glance-sidebar-open toolbarbutton-1" command="cmd_zenGlanceExpand" />
<toolbarbutton class="zen-glance-sidebar-split toolbarbutton-1" command="cmd_zenGlanceSplit" />
<toolbarbutton class="no-squircles zen-glance-sidebar-close toolbarbutton-1" command="cmd_zenGlanceClose" data-l10n-id="zen-general-confirm" />
<toolbarbutton class="no-squircles zen-glance-sidebar-open toolbarbutton-1" command="cmd_zenGlanceExpand" />
<toolbarbutton class="no-squircles zen-glance-sidebar-split toolbarbutton-1" command="cmd_zenGlanceSplit" />
</vbox>
</html:template>

View File

@@ -1232,38 +1232,19 @@ class nsZenWindowSync {
activeIndex = Math.min(activeIndex, entries.length - 1);
activeIndex = Math.max(activeIndex, 0);
let entryToUse = (entries[activeIndex] || entries[0]) ?? null;
this.#setPinnedInitialState(
aTab,
{ url: entryToUse?.url, title: entryToUse?.title },
image
);
});
}
/**
* Sets the canonical pinned URL for a tab across all windows. Used to let the
* user edit a pinned tab's URL directly.
*
* @param {object} aTab - The tab to set the pinned URL for.
* @param {string} aUrl - The URL to store as the canonical pinned URL.
* @param {string} [aImage] - Optional Icon to store.
*/
setPinnedUrl(aTab, aUrl, aImage) {
this.log(`Setting pinned url for tab ${aTab.id}`);
this.#setPinnedInitialState(
aTab,
{ url: aUrl, title: aTab.zenStaticLabel },
aImage
);
}
#setPinnedInitialState(aTab, aEntry, aImage) {
const initialState = { entry: aEntry, image: aImage };
this.#runOnAllWindows(null, win => {
const targetTab = this.getItemFromWindow(win, aTab.id);
if (targetTab) {
targetTab._zenPinnedInitialState = initialState;
}
const initialState = {
entry: {
url: entryToUse?.url,
title: entryToUse?.title,
},
image,
};
this.#runOnAllWindows(null, win => {
const targetTab = this.getItemFromWindow(win, aTab.id);
if (targetTab) {
targetTab._zenPinnedInitialState = initialState;
}
});
});
}

View File

@@ -1,5 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
category browser-window-delayed-startup resource:///modules/zen/spacerouting/ZenSpaceRoutingManager.sys.mjs gZenSpaceRoutingManager.onDelayedBrowserStartup

View File

@@ -18,58 +18,6 @@ class nsZenSpaceRoutingManager {
this.#readFromDisk();
}
/**
* Auto invoked for every window on delayed startup
*
* @param {nsIDOMWindow} window - The browser window that just started up
*/
onDelayedBrowserStartup(window) {
const element = window.MozXULElement.parseXULToFragment(`
<menuseparator/>
<menuitem id="context_zen-add-domain-to-routing"
data-lazy-l10n-id="tab-context-zen-add-domain-to-sr"
data-l10n-args='{"tabCount": 1}'/>
`);
window.document.getElementById("context_undoCloseTab").after(element);
window.document
.getElementById("context_zen-add-domain-to-routing")
.addEventListener("command", this.#onAddSelectedToRouting.bind(this));
window.document
.getElementById("tabContextMenu")
.addEventListener(
"popupshowing",
this.#updateTabCloseCountState.bind(this)
);
}
/**
* Updates the "context_zen-add-domain-to-routing" command
* to reflect the number of selected tabs, when applicable.
*
* @param {Event} event - The event param
*/
#updateTabCloseCountState(event) {
const window = event.target.documentGlobal;
window.document.l10n.setArgs(
window.document.getElementById("context_zen-add-domain-to-routing"),
{ tabCount: window.gBrowser.selectedTabs.length }
);
}
/**
* Callback for whenever the menuitem command is ran
*
* @param {Event} event - The event parameter
*/
#onAddSelectedToRouting(event) {
const window = event.target.documentGlobal;
const tabs = window.TabContextMenu.contextTab.multiselected
? window.gBrowser.selectedTabs
: [window.TabContextMenu.contextTab];
this.addRouteForSelected(tabs, window);
}
/**
* Callback that will be executed from tabbrowser.js
* This method can be used to stop the tab from being created.
@@ -104,7 +52,7 @@ class nsZenSpaceRoutingManager {
break;
default: {
const targetWorkspace =
win.gZenWorkspaces.getWorkspaceFromId(targetRoute);
win?.gZenWorkspaces?.getWorkspaceFromId(targetRoute);
if (targetWorkspace) {
userContextId = targetWorkspace.containerTabId;
@@ -159,26 +107,8 @@ class nsZenSpaceRoutingManager {
* @returns {boolean} True when the navigation should open in a new routed tab
*/
shouldRedirectNavigation(uriString, currentWorkspaceId, win) {
return !!this.getRedirectTargetWorkspaceId(
uriString,
currentWorkspaceId,
win
);
}
/**
* Resolves the destination space for an in-place top-level navigation, or
* null when the navigation should be left alone (no rule, the destination is
* "most-recent-space", the tab already lives there, or the space is gone).
*
* @param {string} uriString - The destination URI
* @param {string|null} currentWorkspaceId - The zen-workspace-id of the navigating tab
* @param {Window} win - The owning browser window
* @returns {string|null} The target workspace id, or null to leave the navigation in place
*/
getRedirectTargetWorkspaceId(uriString, currentWorkspaceId, win) {
if (!win?.gZenWorkspaces?.workspaceEnabled) {
return null;
return false;
}
const targetRoute = this.routeUri(uriString, { fromExternal: false });
@@ -188,13 +118,11 @@ class nsZenSpaceRoutingManager {
targetRoute === "most-recent-space" ||
targetRoute === currentWorkspaceId
) {
return null;
return false;
}
// Only redirect when the destination space actually exists.
return win.gZenWorkspaces.getWorkspaceFromId(targetRoute)
? targetRoute
: null;
return !!win.gZenWorkspaces.getWorkspaceFromId(targetRoute);
}
/**
@@ -475,37 +403,6 @@ class nsZenSpaceRoutingManager {
this.#file.data.defaultRouteExternal = routeType;
}
/**
* Adds a new route for all given tabs
*
* @param {Array<object>} selectedTabs - The tabs that should be routed
* @param {Window} parentWindow - The window from which this is being executed
*/
addRouteForSelected(selectedTabs, parentWindow) {
const newRoute = this.createNewRoute();
let routeReference = "";
if (selectedTabs.length == 1) {
newRoute.matchType = "contains";
routeReference = selectedTabs[0].linkedBrowser.currentURI.host;
} else {
newRoute.matchType = "regex";
routeReference = "(";
for (let i = 0; i < selectedTabs.length; i++) {
const domain = selectedTabs[i].linkedBrowser.currentURI.host;
routeReference += domain.replaceAll(".", "\.");
if (i != selectedTabs.length - 1) {
routeReference += "|";
}
}
routeReference += ")";
}
newRoute.reference = routeReference;
this.updateRoute(newRoute);
this.openSpaceRoutingDialog(parentWindow);
}
/**
* Saves all routes. The list of
* routes is stripped of empty routes

View File

@@ -1507,26 +1507,18 @@ class nsZenWorkspaces {
}
if (container) {
const newtabPlacement = Services.prefs.getBoolPref(
"zen.view.show-newtab-button-top",
false
);
const insertElement = newtabPlacement
? container.firstChild
: container.lastChild;
if (tab.group?.hasAttribute("split-view-group")) {
gBrowser.zenHandleTabMove(tab.group, () => {
for (const subTab of tab.group.tabs) {
subTab.setAttribute("zen-workspace-id", workspaceID);
}
container.insertBefore(tab.group, insertElement);
container.insertBefore(tab.group, container.lastChild);
});
continue;
}
gBrowser.zenHandleTabMove(tab, () => {
tab.setAttribute("zen-workspace-id", workspaceID);
container.insertBefore(tab, insertElement);
container.insertBefore(tab, container.lastChild);
});
}
// also change glance tab if it's the same tab
@@ -2298,12 +2290,12 @@ class nsZenWorkspaces {
}
onBeforeTabSelect(aTab) {
if (this.#inChangingWorkspace || !aTab) {
if (this.#inChangingWorkspace) {
// Just in case, Let's not do these checks while we are
// in the middle of changing workspace,
return false;
}
const tabSpace = aTab.getAttribute("zen-workspace-id");
const tabSpace = aTab?.getAttribute("zen-workspace-id");
if (
tabSpace &&
tabSpace !== this.activeWorkspace &&

View File

@@ -31,6 +31,7 @@
& toolbarbutton {
margin: 0;
max-width: 28px;
border-radius: var(--toolbarbutton-border-radius);
height: 28px;
display: flex;
justify-content: center;

View File

@@ -1240,17 +1240,13 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
*/
contextSplitTabs(otherTabHint = null) {
let tabs;
let currentTab = gZenGlanceManager.getTabOrGlanceParent(
TabContextMenu.contextTab || gBrowser.selectedTab
);
let currentTab = TabContextMenu.contextTab || gBrowser.selectedTab;
if (currentTab.multiselected) {
tabs = gBrowser.selectedTabs;
} else if (!currentTab.selected && !currentTab.splitView) {
tabs = [
currentTab,
...gBrowser.selectedTabs.filter(
t => t !== currentTab && !t.hasAttribute("zen-glance-tab")
),
...gBrowser.selectedTabs.filter(t => t !== currentTab),
];
} else {
tabs = [currentTab];

View File

@@ -246,66 +246,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
gZenUIManager.showToast("zen-pinned-tab-replaced");
}
async editPinnedUrl(tab = undefined) {
tab ??= TabContextMenu.contextTab;
if (!tab || !tab.pinned) {
return;
}
const initialUrl =
tab._zenPinnedInitialState?.entry?.url ||
tab.linkedBrowser?.currentURI?.spec;
const [title, label] = await document.l10n.formatValues([
{ id: "zen-pinned-tab-edit-url-title" },
{ id: "zen-pinned-tab-edit-url-label" },
]);
const result = { value: initialUrl ?? "" };
const confirmed = Services.prompt.prompt(
window,
title,
label,
result,
null,
{ value: false }
);
if (!confirmed) {
return;
}
let uri;
try {
uri = Services.uriFixup.getFixupURIInfo(
result.value.trim(),
Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
).preferredURI;
} catch (_) {}
if (!uri) {
gZenUIManager.showToast("zen-pinned-tab-url-invalid");
return;
}
const url = uri.spec;
// Skip when the value wasn't actually changed from what was prefilled.
if (!url || url === initialUrl) {
return;
}
const image = tab.zenStaticIcon || (await this.#getCachedFavicon(uri));
window.gZenWindowSync.setPinnedUrl(tab, url, image);
this.#resetTabToStoredState(tab);
gZenUIManager.showToast("zen-pinned-tab-url-edited");
}
async #getCachedFavicon(uri) {
try {
const favicon = await PlacesUtils.favicons.getFaviconForPage(uri);
return favicon?.dataURI?.spec;
} catch (ex) {
console.error("Failed to get favicon for edited pinned url:", ex);
return null;
}
}
_initClosePinnedTabShortcut() {
let cmdClose = document.getElementById("cmd_close");
@@ -605,20 +545,11 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
const elements = window.MozXULElement.parseXULToFragment(`
<menuseparator id="context_zen-pinned-tab-separator" hidden="true"/>
<menu id="context_zen-edit-pinned-page"
data-lazy-l10n-id="tab-context-zen-edit-pinned-page"
data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}"
hidden="true">
<menupopup>
<menuitem id="context_zen-replace-pinned-url-with-current"
data-lazy-l10n-id="tab-context-zen-replace-pinned-url-with-current"
data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}"
command="cmd_zenReplacePinnedUrlWithCurrent"/>
<menuitem id="context_zen-edit-pinned-url"
data-lazy-l10n-id="tab-context-zen-edit-pinned-url"
command="cmd_zenEditPinnedUrl"/>
</menupopup>
</menu>
<menuitem id="context_zen-replace-pinned-url-with-current"
data-lazy-l10n-id="tab-context-zen-replace-pinned-url-with-current"
data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}"
hidden="true"
command="cmd_zenReplacePinnedUrlWithCurrent"/>
<menuitem id="context_zen-reset-pinned-tab"
data-lazy-l10n-id="tab-context-zen-reset-pinned-tab"
data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}"
@@ -688,24 +619,15 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
const zenResetPinnedTab = document.getElementById(
"context_zen-reset-pinned-tab"
);
const zenEditPinnedPage = document.getElementById(
"context_zen-edit-pinned-page"
);
const zenReplacePinnedUrl = document.getElementById(
"context_zen-replace-pinned-url-with-current"
);
[zenResetPinnedTab, zenEditPinnedPage].forEach(element => {
[zenResetPinnedTab, zenReplacePinnedUrl].forEach(element => {
if (element) {
element.hidden = !isVisible;
document.l10n.setArgs(element, { isEssential });
}
});
[zenResetPinnedTab, zenEditPinnedPage, zenReplacePinnedUrl].forEach(
element => {
if (element) {
document.l10n.setArgs(element, { isEssential });
}
}
);
zenAddEssential.hidden = isEssential || !!contextTab.group;
document.l10n
.formatValue("tab-context-zen-add-essential-badge", {
@@ -935,7 +857,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
) {
return;
}
// Remove # from the URL
// Remove # and ? from the URL
const pinUrl = tab._zenPinnedInitialState.entry.url.split("#")[0];
const currentUrl = location.split("#")[0];
// Add an indicator that the pin has been changed
@@ -975,14 +897,10 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} else {
tab.setAttribute("zen-pinned-changed", "true");
}
if (tab._zenPinnedInitialState.image) {
tab.style.setProperty(
"--zen-original-tab-icon",
`url(${tab._zenPinnedInitialState.image})`
);
} else {
tab.style.removeProperty("--zen-original-tab-icon");
}
tab.style.setProperty(
"--zen-original-tab-icon",
`url(${tab._zenPinnedInitialState.image})`
);
}
removeTabContainersDragoverClass(hideIndicator = true) {

View File

@@ -235,7 +235,7 @@
}
@media (-moz-platform: macos) {
--border-radius-medium: 12px;
--border-radius-medium: 14px;
--tab-border-radius: 8px;
}
@@ -1232,6 +1232,8 @@
background: var(--zen-essential-tab-selected-bg);
margin: var(--zen-essential-bg-margin);
border-radius: calc(var(--border-radius-medium) - var(--zen-essential-bg-margin));
/* stylelint-disable-next-line property-no-unknown */
corner-shape: var(--zen-squircle-value);
position: absolute;
inset: 0;
z-index: 0;

View File

@@ -13,8 +13,6 @@ prefs = ["zen.workspaces.separate-essentials=false"]
["browser_pinned_created.js"]
["browser_pinned_edit_url.js"]
["browser_pinned_nounload_reset.js"]
["browser_pinned_reset_button.js"]

View File

@@ -1,384 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
async function pinTab(url) {
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
gBrowser.pinTab(tab);
await gBrowser.TabStateFlusher.flush(tab.linkedBrowser);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(r => setTimeout(r, 500));
return tab;
}
// XPCOM service methods can't be stubbed in place (non-configurable), so we
// swap the whole service object out for a mock and restore it afterwards.
function mockPrompt(value) {
const original = Services.prompt;
Services.prompt = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptService]),
prompt(win, title, label, result) {
if (value === null) {
return false; // user cancelled
}
result.value = value;
return true;
},
};
return () => {
Services.prompt = original;
};
}
function mockFavicons(faviconSpec) {
const original = PlacesUtils.favicons;
const mock = {
callCount: 0,
defaultFavicon: { spec: "data:image/png;base64,DEFAULT" },
getFaviconForPage() {
mock.callCount++;
return Promise.resolve(
faviconSpec ? { dataURI: { spec: faviconSpec } } : null
);
},
};
PlacesUtils.favicons = mock;
return {
mock,
restore: () => {
PlacesUtils.favicons = original;
},
};
}
add_task(async function test_EditPinnedUrl_SurvivesRebuild() {
// Pinned tab at url1 (loaded), then select a different tab (unfocus it).
const tab = await pinTab("https://example.com/1");
const other = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"https://example.com/other"
);
const editedUrl = "https://example.com/edited";
const restorePrompt = mockPrompt(editedUrl);
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
// Close + re-open rebuilds the tab: the in-memory _zenPinnedInitialState is
// gone and gets reconstructed from the persisted session via
// setPinnedTabState (exactly what #onSessionStoreInitialized does).
delete tab._zenPinnedInitialState;
await window.gZenWindowSync.setPinnedTabState(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
editedUrl,
"After the tab is rebuilt, the pinned URL should still be the edited one"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(other);
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_ActiveTabNavigates() {
// Editing the active (focused) pinned tab applies the new URL immediately:
// the live tab navigates to it (matching Arc's behavior).
const tab = await pinTab("https://example.com/1");
Assert.equal(gBrowser.selectedTab, tab, "the pinned tab should be active");
const editedUrl = "https://example.com/edited";
const restorePrompt = mockPrompt(editedUrl);
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
await BrowserTestUtils.waitForCondition(
() => tab.linkedBrowser.currentURI.spec === editedUrl,
"the active pinned tab to navigate to the edited URL"
);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
editedUrl,
"Editing the active pinned tab should navigate it to the new URL"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(
async function test_EditPinnedUrl_FaviconLookupErrorLeavesImageEmpty() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons(null);
// Simulate a Places DB failure so #getCachedFavicon hits its catch branch.
favicons.mock.getFaviconForPage = () =>
Promise.reject(new Error("simulated favicon DB failure"));
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
"https://example.org/edited",
"The URL should still be updated when the favicon lookup fails"
);
ok(
!tab._zenPinnedInitialState.image,
"The image should be left empty (populated by the next navigation)"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
}
);
add_task(async function test_EditPinnedUrl_UpdatesUrlAndFavicon() {
const tab = await pinTab("https://example.com/1");
const faviconSpec = "data:image/png;base64,iVBORw0KGgo=";
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons(faviconSpec);
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
"https://example.org/edited",
"The pinned URL should be updated to the edited value"
);
Assert.equal(
tab._zenPinnedInitialState.image,
faviconSpec,
"The stored icon should be the cached favicon for the new URL"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_NoCachedFaviconLeavesImageEmpty() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons(null); // no cached favicon for the new URL
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
ok(
!tab._zenPinnedInitialState.image,
"Without a cached favicon the image is left empty, not the default; the " +
"next navigation captures the real icon"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_ClearsStaleTitle() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
ok(
!tab._zenPinnedInitialState.entry.title,
"The previous title is cleared so the new page's title is used on load"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_KeepsCustomLabel() {
const tab = await pinTab("https://example.com/1");
tab.zenStaticLabel = "My Pinned Tab";
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.title,
"My Pinned Tab",
"An explicit custom label is preserved across a URL edit"
);
} finally {
restorePrompt();
favicons.restore();
delete tab.zenStaticLabel;
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_KeepsCustomIcon() {
const tab = await pinTab("https://example.com/1");
const customIcon = "data:image/svg+xml,custom-icon";
tab.zenStaticIcon = customIcon;
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
"https://example.org/edited",
"The pinned URL should still be updated when a custom icon is set"
);
Assert.equal(
tab._zenPinnedInitialState.image,
customIcon,
"A user-set custom icon should be preserved, not overridden by a favicon"
);
Assert.equal(
favicons.mock.callCount,
0,
"Favicon lookup should be skipped when a custom icon is set"
);
} finally {
restorePrompt();
favicons.restore();
delete tab.zenStaticIcon;
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_InvalidUrlKeepsState() {
const tab = await pinTab("https://example.com/1");
const originalUrl = tab._zenPinnedInitialState.entry.url;
const restorePrompt = mockPrompt(" "); // whitespace only -> not a valid URL
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
originalUrl,
"The pinned URL should be unchanged for invalid input"
);
ok(
!tab.hasAttribute("zen-pinned-changed"),
"The tab should not be marked as changed for invalid input"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_CancelKeepsState() {
const tab = await pinTab("https://example.com/1");
const originalUrl = tab._zenPinnedInitialState.entry.url;
const restorePrompt = mockPrompt(null); // user cancels the dialog
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
originalUrl,
"The pinned URL should be unchanged when the dialog is cancelled"
);
} finally {
restorePrompt();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_FixesSchemeTypo() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("htps://example.org/typo");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
"https://example.org/typo",
"A mistyped scheme (htps://) should be auto-fixed to https://"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_AddsMissingScheme() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("example.org/no-scheme");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
const stored = tab._zenPinnedInitialState.entry.url;
ok(
/^https?:\/\//.test(stored),
`A scheme should be prepended when omitted (got "${stored}")`
);
ok(
stored.endsWith("example.org/no-scheme"),
`Host and path should be preserved (got "${stored}")`
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_PrefillsWithStoredUrl() {
const tab = await pinTab("https://example.com/1");
// The stored pinned URL differs from the live browser URL (e.g. it was pinned
// as http but the server redirected the tab to https).
tab._zenPinnedInitialState = {
entry: { url: "http://example.com/pinned" },
image: "",
};
let prefilled;
const originalPrompt = Services.prompt;
Services.prompt = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptService]),
prompt(win, title, label, result) {
prefilled = result.value;
return false; // cancel, we only care about the prefilled value
},
};
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
prefilled,
"http://example.com/pinned",
"The edit dialog should prefill with the stored pinned URL, not the live browser URL"
);
} finally {
Services.prompt = originalPrompt;
await BrowserTestUtils.removeTab(tab);
}
});

View File

@@ -5,8 +5,8 @@
"binaryName": "zen",
"version": {
"product": "firefox",
"version": "152.0.3",
"candidate": "152.0.3",
"version": "152.0.1",
"candidate": "152.0.1",
"candidateBuild": 1
},
"buildOptions": {
@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
"displayVersion": "1.21.4b",
"displayVersion": "1.21.3b",
"github": {
"repo": "zen-browser/desktop"
},