chore: Sync upstream to Firefox 152.0

This commit is contained in:
mr-cheffy
2026-06-11 07:52:02 +00:00
committed by github-actions[bot]
parent d5cbe55d2b
commit 690fff1ca0
17 changed files with 482 additions and 154 deletions

View File

@@ -35,7 +35,7 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne
### Firefox Versions
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `151.0.4`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 151.0.4`!
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0`!
### Contributing

View File

@@ -1 +1 @@
9a6aa4c359d1fb6ac60decc82402f82d49a17cea
a58ad2d2952face15859068dd4421cf68d6a9dda

View File

@@ -7,7 +7,7 @@ diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content
id="selection-shortcut-action-panel"
noautofocus="true"
consumeoutsideclicks="never"
+ nonnativepopover="true"
+ nonnative=""
type="arrow">
<hbox class="panel-subview-body">
<html:moz-button id="ai-action-button"/>
@@ -19,7 +19,7 @@ diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content
<panel class="panel-no-padding"
id="chat-shortcuts-options-panel"
noautofocus="true"
+ nonnativepopover="true"
+ nonnative=""
type="arrow">
<vbox class="panel-subview-body"/>
</panel>
@@ -31,7 +31,7 @@ diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content
noautofocus="true"
norolluponanchor="true"
consumeoutsideclicks="false"
+ nonnativepopover="true"
+ nonnative=""
role="tooltip">
<html:div class="tab-preview-content-interactive"></html:div>
<html:div class="tab-preview-content-main">
@@ -46,7 +46,7 @@ diff --git a/browser/components/asrouter/modules/FeatureCallout.sys.mjs b/browse
type="arrow"
consumeoutsideclicks="never"
norolluponanchor="true"
+
+ nonnative=""
position="${panel_position.panel_position_string}"
${hide_arrow ? "" : 'show-arrow=""'}
${autohide ? "" : 'noautohide="true"'}
@@ -61,7 +61,7 @@ diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/brows
hidden="true"
flip="slide"
position="bottomright topright"
+ hidepopovertail="true"
+ hidepopovertail=""
noautofocus="true">
<panelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView"
viewCacheId="appMenu-viewCache">
@@ -70,31 +70,33 @@ diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/brows
diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
--- a/dom/xul/XULPopupElement.cpp
+++ b/dom/xul/XULPopupElement.cpp
@@ -80,10 +80,14 @@
@@ -80,10 +80,15 @@
}
void XULPopupElement::OpenPopupAtScreen(int32_t aXPos, int32_t aYPos,
bool aIsContextMenu,
Event* aTriggerEvent) {
+ // TODO(cheff): At nsCocoaWindow::Show but we check for ShouldShowAsNSPopover
+ // to determine whether to use a native popover or not. This should sort of
+ // "replicate" that logic here, but it's a bit of a hacky way.
+ SetAttr(kNameSpaceID_None, nsGkAtoms::nonnativepopover, u"true"_ns, true);
+ if (NodeInfo()->NameAtom() == nsGkAtoms::panel) {
+ // TODO(bug 2038354): Remove this and make the front-end set the attribute
+ // explicitly.
+ SetAttr(kNameSpaceID_None, nsGkAtoms::nonnative, u"true"_ns, true);
+ }
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
pm->ShowPopupAtScreen(this, aXPos, aYPos, aIsContextMenu, aTriggerEvent);
}
}
@@ -93,10 +97,14 @@
@@ -93,10 +98,15 @@
int32_t aWidth, int32_t aHeight,
bool aIsContextMenu,
bool aAttributesOverride,
Event* aTriggerEvent) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ // TODO: See OpenPopupAtScreen. We should remove this and use the other
+ // implementation because this is a bit of a hacky way to determine whether to
+ // use a native popover or not.
+ SetAttr(kNameSpaceID_None, nsGkAtoms::nonnativepopover, u"true"_ns, true);
+ if (NodeInfo()->NameAtom() == nsGkAtoms::panel) {
+ // TODO(bug 2038354): Remove this and make the front-end set the attribute
+ // explicitly.
+ SetAttr(kNameSpaceID_None, nsGkAtoms::nonnative, u"true"_ns, true);
+ }
if (pm) {
pm->ShowPopupAtScreenRect(
this, aPosition, nsIntRect(aXPos, aYPos, aWidth, aHeight),
@@ -103,7 +105,7 @@ diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -528,18 +528,10 @@
@@ -516,18 +516,10 @@
// Move the popup to the position specified in its |left| and |top|
// attributes.
@@ -122,7 +124,7 @@ diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
public:
/**
* Return whether the popup direction should be RTL.
@@ -548,10 +540,18 @@
@@ -536,10 +528,18 @@
*
* Return whether the popup direction should be RTL.
*/
@@ -144,7 +146,7 @@ diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -19672,10 +19672,19 @@
@@ -19840,10 +19840,19 @@
value: true
mirror: always
@@ -168,20 +170,20 @@ diff --git a/toolkit/themes/shared/global-shared.css b/toolkit/themes/shared/glo
--- a/toolkit/themes/shared/global-shared.css
+++ b/toolkit/themes/shared/global-shared.css
@@ -72,10 +72,22 @@
--menuitem-border-radius: var(--panel-menuitem-border-radius);
--menuitem-padding: var(--panel-menuitem-padding);
--menuitem-margin: var(--panel-menuitem-margin);
--menuitem-border-radius: var(--arrowpanel-menuitem-border-radius);
--menuitem-padding: var(--arrowpanel-menuitem-padding);
--menuitem-margin: var(--arrowpanel-menuitem-margin);
}
+/* stylelint-disable-next-line media-query-no-invalid */
+@media -moz-pref("widget.macos.native-popovers") and (-moz-platform: macos) {
+ panel:not(:where([nonnativepopover="true"])) {
+ panel:not([nonnative]) {
+ background-color: transparent;
+ --panel-background: transparent;
+ --panel-shadow: none;
+ --panel-box-shadow: none;
+ --panel-border-color: transparent;
+ --panel-shadow-margin: 0px;
+
+ --panel-box-shadow-margin: 0px;
+ --panel-padding: 0px;
+ }
+}
+
@@ -241,7 +243,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
+ // Check if this window should use NSPopover for popup/menu display
+ bool ShouldUseNSPopover() const;
+ bool ShouldShowAsNSPopover() const override;
+ bool ShouldShowAsNSPopover() const;
+
[[nodiscard]] nsresult Create(nsIWidget* aParent, const DesktopIntRect& aRect,
const InitData&) override;
@@ -265,7 +267,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
#include "nsIDOMWindowUtils.h"
#include "nsILocalFileMac.h"
#include "CocoaCompositorWidget.h"
@@ -5031,10 +5034,15 @@
@@ -5088,10 +5091,15 @@
if (mWindowType == WindowType::Popup) {
SetPopupWindowLevel();
mWindow.backgroundColor = NSColor.clearColor;
@@ -281,7 +283,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// the active space. Does not work with multiple displays. See
// NeedsRecreateToReshow() for multi-display with multi-space workaround.
mWindow.collectionBehavior = mWindow.collectionBehavior |
@@ -5236,10 +5244,57 @@
@@ -5293,10 +5301,57 @@
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
@@ -339,7 +341,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
if (!mWindow) {
@@ -5300,10 +5355,58 @@
@@ -5357,10 +5412,53 @@
mWindow.contentView.needsDisplay = YES;
if (!nativeParentWindow || mPopupLevel != PopupLevel::Parent) {
[mWindow orderFront:nil];
@@ -375,22 +377,17 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
+ // specific parent view
+ NSRect positioningRect = [parentView convertRect:windowRect
+ fromView:nil];
+ BOOL shouldHideAnchor = NO;
+ auto& element = popupFrame->PopupElement();
+ if (element.GetBoolAttr(nsGkAtoms::hidepopovertail)) {
+ shouldHideAnchor = YES;
+ }
+ bool shouldHideAnchor =
+ popupFrame->PopupElement().GetBoolAttr(nsGkAtoms::hidepopovertail);
+ [(PopupWindow*)mWindow showPopoverRelativeToRect:positioningRect
+ ofView:parentView
+ preferredEdge:preferredEdge
+ hiddenAnchor:shouldHideAnchor];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-method-access"
+ SyncPopoverBounds([(PopupWindow*)mWindow popover], popupFrame);
+
+
+
+
+#pragma clang diagnostic pop
+ // Exit early here since the popover is now shown.
+
+ return;
+ }
// If our popup window is a non-native context menu, tell the OS (and
@@ -398,7 +395,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// close other programs' context menus when ours open.
if ([mWindow isKindOfClass:[PopupWindow class]] &&
[(PopupWindow*)mWindow isContextMenu]) {
@@ -5373,11 +5476,15 @@
@@ -5430,11 +5528,15 @@
// unhook it here before ordering it out. When you order out the child
// of a window it hides the parent window.
if (mWindowType == WindowType::Popup && nativeParentWindow) {
@@ -415,7 +412,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// other programs) that a menu has closed.
if ([mWindow isKindOfClass:[PopupWindow class]] &&
[(PopupWindow*)mWindow isContextMenu]) {
@@ -5424,10 +5531,28 @@
@@ -5481,10 +5583,28 @@
return false;
}
return nsIWidget::ShouldUseOffMainThreadCompositing();
@@ -424,8 +421,8 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
+bool nsCocoaWindow::ShouldUseNSPopover() const {
+ // Use NSPopover for panel popups when the preference is enabled
+ // But not for detached popups - they should use traditional window logic
+ return (mWindowType == WindowType::Popup && mPopupType == PopupType::Panel &&
+ mozilla::StaticPrefs::widget_macos_native_popovers());
+ return mWindowType == WindowType::Popup && mPopupType == PopupType::Panel &&
+ mozilla::StaticPrefs::widget_macos_native_popovers();
+}
+
+bool nsCocoaWindow::ShouldShowAsNSPopover() const {
@@ -444,7 +441,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
return mWindow.isOpaque ? TransparencyMode::Opaque
: TransparencyMode::Transparent;
@@ -6378,10 +6503,19 @@
@@ -6442,10 +6562,22 @@
// We ignore aRepaint -- we have to call display:YES, otherwise the
// title bar doesn't immediately get repainted and is displayed in
@@ -454,17 +451,20 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
+ [(PopupWindow*)mWindow updatePopoverContent];
+ // A popover won't resize by setting the frame
+ // as it's size is calculated based on the content size
+ // Therefor the content size has to be changed as well
+ // Therefore the content size has to be changed as well
+ NSSize contentSize = NSMakeSize(aWidth, aHeight);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-method-access"
+ [[(PopupWindow*)mWindow popover] setContentSize:contentSize];
+ SyncPopoverBounds([(PopupWindow*)mWindow popover], GetPopupFrame());
+#pragma clang diagnostic pop
+ }
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
void nsCocoaWindow::Resize(const DesktopRect& aRect, bool aRepaint) {
@@ -8393,18 +8527,31 @@
@@ -8517,18 +8649,31 @@
backing:bufferingType
defer:deferCreation];
if (!self) {
@@ -497,7 +497,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// Return 0 in order to match what the system does for sheet windows and
// _NSPopoverWindows.
- (CGFloat)_backdropBleedAmount {
@@ -8460,10 +8607,122 @@
@@ -8584,10 +8729,125 @@
- (void)setIsContextMenu:(BOOL)flag {
mIsContextMenu = flag;
@@ -537,7 +537,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
+}
+
+- (BOOL)usePopover {
+ return mUsePopover && !mIsContextMenu;
+ return mUsePopover;
+}
+
+- (void)showPopoverRelativeToRect:(NSRect)positioningRect
@@ -562,7 +562,10 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
+
+ // This is a hidden API that prevents the popover from showing its arrow
+ // pointing to the anchor.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-method-access"
+ [mPopover setShouldHideAnchor:hiddenAnchor];
+#pragma clang diagnostic pop
+
+ [mPopover showRelativeToRect:positioningRect
+ ofView:positioningView
@@ -620,25 +623,6 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
return NO;
}
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -829,10 +829,15 @@
virtual void SuppressAnimation(bool aSuppress) {}
/** Sets windows-specific mica backdrop on this widget. */
virtual void SetMicaBackdrop(bool) {}
+ /**
+ * Determine whether this widget should be shown as an NSPopover.
+ */
+ virtual bool ShouldShowAsNSPopover() const { return false; }
+
/**
* Return size mode (minimized, maximized, normalized).
* Returns a value from nsSizeMode (see nsIWidgetListener.h)
*/
virtual nsSizeMode SizeMode() = 0;
diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -654,16 +638,4 @@ diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
Atom("highest", "highest"),
Atom("horizontal", "horizontal"),
Atom("hover", "hover"),
@@ -759,10 +760,11 @@
Atom("nohref", "nohref"),
Atom("noinitialselection", "noinitialselection"),
Atom("nomodule", "nomodule"),
Atom("nonce", "nonce"),
Atom("none", "none"),
+ Atom("nonnativepopover", "nonnativepopover"),
Atom("noresize", "noresize"),
Atom("normal", "normal"),
Atom("normalizeSpace", "normalize-space"),
Atom("noscript", "noscript"),
Atom("noshade", "noshade"),

View File

@@ -1,3 +1,18 @@
diff --git a/toolkit/themes/shared/global-shared.css b/toolkit/themes/shared/global-shared.css
--- a/toolkit/themes/shared/global-shared.css
+++ b/toolkit/themes/shared/global-shared.css
@@ -80,11 +80,10 @@
background-color: transparent;
--panel-background: transparent;
--panel-box-shadow: none;
--panel-border-color: transparent;
--panel-box-shadow-margin: 0px;
- --panel-padding: 0px;
}
}
/* Lightweight theme roots */
diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h

View File

@@ -40,7 +40,7 @@ add_task(async function testNormalBrowsing() {
browser: tab.linkedBrowser,
uriString: TEST_PAGE,
});
testWhitelistedPage(tab.ownerGlobal);
testWhitelistedPage(tab.documentGlobal);
info("Load a test page that's no longer whitelisted");
Services.prefs.setCharPref(PREF_WHITELISTED_HOSTNAMES, "");
@@ -54,5 +54,5 @@ add_task(async function testNormalBrowsing() {
);
BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, TEST_PAGE);
await blockedLoaded;
testBlockedPage(tab.ownerGlobal);
testBlockedPage(tab.documentGlobal);
});

View File

@@ -2,7 +2,10 @@
["browser_1119088.js"]
disabled="Disabled by import_external_tests.py"
support-files = ["mac_desktop_image.py"]
support-files = [
"large.png",
"mac_desktop_image.py"
]
run-if = [
"os == 'mac'",
]
@@ -12,6 +15,7 @@ skip-if = [
]
["browser_420786.js"]
support-files = ["large.png"]
run-if = [
"os == 'linux' && os_version == '22.04' && arch == 'x86_64' && display == 'wayland'",
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11'",
@@ -116,4 +120,5 @@ tags = "os_integration"
["browser_setDesktopBackgroundPreview.js"]
disabled="Disabled by import_external_tests.py"
support-files = ["large.png"]
tags = "os_integration"

View File

@@ -92,20 +92,19 @@ add_setup(async function () {
/**
* Tests "Set As Desktop Background" platform implementation on macOS.
*
* Sets the desktop background image to the browser logo from the about:logo
* page and verifies it was set successfully. Setting the desktop background
* (which uses the nsIShellService::setDesktopBackground() interface method)
* downloads the image to ~/Pictures using a unique file name and sets the
* desktop background to the downloaded file leaving the download in place.
* After setDesktopBackground() is called, the test uses a python script to
* validate that the current desktop background is in fact set to the
* downloaded logo.
* Sets the desktop background image to the large.png image and verifies it was
* set successfully. Setting the desktop background (which uses the
* nsIShellService::setDesktopBackground() interface method) downloads the
* image to ~/Pictures using a unique file name and sets the desktop background
* to the downloaded file leaving the download in place. After
* setDesktopBackground() is called, the test uses a python script to validate
* that the current desktop background is in fact set to the downloaded image.
*/
add_task(async function () {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:logo",
url: getRootDirectory(gTestPath) + "large.png",
},
async () => {
let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(
@@ -140,7 +139,7 @@ add_task(async function () {
// For simplicity, we're going to reach in and access the image on the
// page directly, which means the page shouldn't be running in a remote
// browser. Thankfully, about:logo runs in the parent process for now.
// browser. Thankfully, chrome:// runs in the parent process for now.
Assert.ok(
!gBrowser.selectedBrowser.isRemoteBrowser,
"image can be accessed synchronously from the parent process"

View File

@@ -12,7 +12,7 @@ add_task(async function () {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:logo",
url: getRootDirectory(gTestPath) + "large.png",
},
() => {
var brandName = Services.strings
@@ -46,7 +46,7 @@ add_task(async function () {
// For simplicity, we're going to reach in and access the image on the
// page directly, which means the page shouldn't be running in a remote
// browser. Thankfully, about:logo runs in the parent process for now.
// browser. Thankfully, chrome:// runs in the parent process for now.
Assert.ok(
!gBrowser.selectedBrowser.isRemoteBrowser,
"image can be accessed synchronously from the parent process"

View File

@@ -323,14 +323,39 @@ add_task(async function test_setAsDefaultPDFHandler_knownBrowser() {
}
});
// Wait for the deferred set_default_pdf_handler_attempt event to be recorded,
// then return the single event that was emitted by the most recent call.
async function awaitAttemptEvent() {
await TestUtils.waitForCondition(() => {
const events = Glean.browser.setDefaultPdfHandlerAttempt.testGetValue();
return events && events.length;
}, "Recorded set_default_pdf_handler_attempt event");
const events = Glean.browser.setDefaultPdfHandlerAttempt.testGetValue();
Assert.equal(events.length, 1, "Recorded exactly one attempt event");
return events[0];
}
add_task(async function test_setAsDefaultPDFHandler_fallback() {
const sandbox = sinon.createSandbox();
// Enable the IOpenWithLauncher branch explicitly so the test does not
// depend on the build-channel default of
// browser.shell.setDefaultPDFHandler.useOpenWith, and use a 0ms wait so
// the deferred attempt event fires promptly.
await SpecialPowers.pushPrefEnv({
set: [
["browser.shell.setDefaultPDFHandler.useOpenWith", true],
["browser.shell.setDefaultPDFHandler.attemptWaitTimeMs", 0],
],
});
try {
const userChoiceStub = sandbox
.stub(ShellService, "setAsDefaultPDFHandlerUserChoice")
.rejects(new Error("mock userChoice failure"));
sandbox.stub(ShellService, "_isWindows11").returns(true);
const isDefaultHandlerForStub = sandbox
.stub(ShellService, "isDefaultHandlerFor")
.returns(true);
info(
"When userChoice fails and open-with picker succeeds, should not fall back to settings dialog"
@@ -352,27 +377,42 @@ add_task(async function test_setAsDefaultPDFHandler_fallback() {
1,
"Recorded user-choice failure"
);
let event = await awaitAttemptEvent();
Assert.equal(event.extra.method, "open_with", "Event method is open_with");
Assert.equal(event.extra.success, "true", "Event success is true");
Assert.equal(
Glean.browser.setDefaultPdfHandlerUserChoiceResult.Success.testGetValue(),
undefined,
"Did not record user-choice success"
event.extra.result_is_default,
"true",
"Event result_is_default reflects isDefaultHandlerFor"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerOpenWithResult.Success.testGetValue(),
1,
"Recorded open-with success"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerOpenWithResult.Failure.testGetValue(),
undefined,
"Did not record open-with failure"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerModernSettingsResult.Success.testGetValue(),
undefined,
"Did not record modern settings result"
Assert.ok(
isDefaultHandlerForStub.calledWith(".pdf"),
"Sampled isDefaultHandlerFor after the delay"
);
userChoiceStub.resetHistory();
isDefaultHandlerForStub.resetHistory();
launchOpenWithDefaultPickerForFileTypeStub.resetHistory();
launchModernSettingsDialogDefaultAppsStub.resetHistory();
info(
"When the picker succeeds but Firefox is not default after the delay, event records result_is_default=false"
);
Services.fog.testResetFOG();
isDefaultHandlerForStub.returns(false);
await ShellService.setAsDefaultPDFHandler(false);
event = await awaitAttemptEvent();
Assert.equal(event.extra.method, "open_with", "Event method is open_with");
Assert.equal(event.extra.success, "true", "Event success is true");
Assert.equal(
event.extra.result_is_default,
"false",
"Event result_is_default is false when Firefox did not become default"
);
isDefaultHandlerForStub.returns(true);
userChoiceStub.resetHistory();
isDefaultHandlerForStub.resetHistory();
launchOpenWithDefaultPickerForFileTypeStub.resetHistory();
launchModernSettingsDialogDefaultAppsStub.resetHistory();
@@ -399,72 +439,120 @@ add_task(async function test_setAsDefaultPDFHandler_fallback() {
1,
"Recorded user-choice failure"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerUserChoiceResult.Success.testGetValue(),
undefined,
"Did not record user-choice success"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerOpenWithResult.Failure.testGetValue(),
1,
"Recorded open-with failure"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerOpenWithResult.Success.testGetValue(),
undefined,
"Did not record open-with success"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerModernSettingsResult.Success.testGetValue(),
1,
"Recorded modern settings success"
);
event = await awaitAttemptEvent();
Assert.equal(
Glean.browser.setDefaultPdfHandlerModernSettingsResult.Failure.testGetValue(),
undefined,
"Did not record modern settings failure"
event.extra.method,
"settings",
"Event method is settings (last attempted)"
);
Assert.equal(
event.extra.success,
"true",
"Event success reflects modern settings launch"
);
Assert.equal(
event.extra.result_is_default,
"true",
"Event result_is_default reflects isDefaultHandlerFor"
);
userChoiceStub.resetHistory();
isDefaultHandlerForStub.resetHistory();
launchOpenWithDefaultPickerForFileTypeStub.resetHistory();
launchModernSettingsDialogDefaultAppsStub.resetHistory();
info(
"When userChoice fails, open-with fails, and modern settings fails, should record all failures"
"When userChoice fails, open-with fails, and modern settings fails, event records success=false"
);
Services.fog.testResetFOG();
isDefaultHandlerForStub.returns(false);
launchModernSettingsDialogDefaultAppsStub.throws(
new Error("mock modern settings failure")
);
await ShellService.setAsDefaultPDFHandler(false);
Assert.equal(
Glean.browser.setDefaultPdfHandlerUserChoiceResult.ErrOther.testGetValue(),
1,
"Recorded user-choice failure"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerUserChoiceResult.Success.testGetValue(),
undefined,
"Did not record user-choice success"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerOpenWithResult.Failure.testGetValue(),
1,
"Recorded open-with failure"
);
Assert.equal(
Glean.browser.setDefaultPdfHandlerModernSettingsResult.Failure.testGetValue(),
1,
"Recorded modern settings failure"
);
event = await awaitAttemptEvent();
Assert.equal(
Glean.browser.setDefaultPdfHandlerModernSettingsResult.Success.testGetValue(),
undefined,
"Did not record modern settings success"
event.extra.method,
"settings",
"Event method is settings (last attempted)"
);
Assert.equal(
event.extra.success,
"false",
"Event success is false when every method failed"
);
Assert.equal(
event.extra.result_is_default,
"false",
"Event result_is_default is false when no method set the default"
);
} finally {
launchOpenWithDefaultPickerForFileTypeStub.reset();
launchModernSettingsDialogDefaultAppsStub.reset();
sandbox.restore();
await SpecialPowers.popPrefEnv();
}
});
add_task(async function test_setAsDefaultPDFHandler_useOpenWithDisabled() {
const sandbox = sinon.createSandbox();
// With useOpenWith disabled, a userChoice failure should skip the
// IOpenWithLauncher branch entirely and fall straight through to the
// modern settings dialog.
await SpecialPowers.pushPrefEnv({
set: [
["browser.shell.setDefaultPDFHandler.useOpenWith", false],
["browser.shell.setDefaultPDFHandler.attemptWaitTimeMs", 0],
],
});
try {
sandbox
.stub(ShellService, "setAsDefaultPDFHandlerUserChoice")
.rejects(new Error("mock userChoice failure"));
sandbox.stub(ShellService, "_isWindows11").returns(true);
sandbox.stub(ShellService, "isDefaultHandlerFor").returns(true);
Services.fog.testResetFOG();
await ShellService.setAsDefaultPDFHandler(false);
Assert.ok(
launchOpenWithDefaultPickerForFileTypeStub.notCalled,
"Did not invoke open-with picker when pref is disabled"
);
Assert.ok(
launchModernSettingsDialogDefaultAppsStub.called,
"Fell through to modern settings dialog"
);
const event = await awaitAttemptEvent();
Assert.equal(
event.extra.method,
"settings",
"Event method skipped open_with and recorded settings"
);
Assert.equal(event.extra.success, "true", "Event success is true");
Assert.equal(
event.extra.result_is_default,
"true",
"Event result_is_default reflects isDefaultHandlerFor"
);
} finally {
launchOpenWithDefaultPickerForFileTypeStub.reset();
launchModernSettingsDialogDefaultAppsStub.reset();
sandbox.restore();
await SpecialPowers.popPrefEnv();
}
});

View File

@@ -16,7 +16,7 @@ add_task(async function () {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:logo",
url: getRootDirectory(gTestPath) + "large.png",
},
async () => {
const dialogLoad = BrowserTestUtils.domWindowOpened(null, async win => {

View File

@@ -1,4 +1,3 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,131 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.defineESModuleGetters(this, {
ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
});
const BAREBONES_DESKTOP_ENTRY = `[Desktop Entry]
Version=1.5
Type=Application
Name=test_desktopEntryStatus.js test case
`;
let gHomeDir;
let gSystemDir;
const filename = what => `test_desktopEntryStatus_file_${what}.desktop`;
// GLib caches results for efficiency. Unfortunately, it doesn't really provide
// a way to invalidate that cache, aside from hoping that the file monitor
// picks up on it. Resolve this by setting up all of the desktop entries at the
// start, then doing checks, then exiting.
//
// (Some others are special-cased, namely absent and Hidden= checks.)
const kDesktopEntries = [
{
label: "visible",
content: BAREBONES_DESKTOP_ENTRY,
expected: Ci.nsIGNOMEShellService.DESKTOP_ENTRY_VISIBLE,
},
{
label: "nodisplay",
content: BAREBONES_DESKTOP_ENTRY + "NoDisplay=true\n",
expected: Ci.nsIGNOMEShellService.DESKTOP_ENTRY_INVISIBLE,
},
{
label: "onlyshowin-matching",
content: BAREBONES_DESKTOP_ENTRY + "OnlyShowIn=FirefoxOS\n",
expected: Ci.nsIGNOMEShellService.DESKTOP_ENTRY_VISIBLE,
},
{
label: "onlyshowin-notmatching",
content: BAREBONES_DESKTOP_ENTRY + "OnlyShowIn=another\n",
expected: Ci.nsIGNOMEShellService.DESKTOP_ENTRY_INVISIBLE,
},
{
label: "notshowin-matching",
content: BAREBONES_DESKTOP_ENTRY + "NotShowIn=FirefoxOS\n",
expected: Ci.nsIGNOMEShellService.DESKTOP_ENTRY_INVISIBLE,
},
{
label: "notshowin-notmatching",
content: BAREBONES_DESKTOP_ENTRY + "NotShowIn=another\n",
expected: Ci.nsIGNOMEShellService.DESKTOP_ENTRY_VISIBLE,
},
];
add_setup(async function setup() {
let unique = await IOUtils.createUniqueDirectory(
Services.dirsvc.get("TmpD", Ci.nsIFile).path,
"desktopEntryStatusTest"
);
let homeDir = PathUtils.join(unique, "data-home");
Services.env.set("XDG_DATA_HOME", homeDir);
gHomeDir = PathUtils.join(homeDir, "applications");
await IOUtils.makeDirectory(gHomeDir, { createAncestors: true });
let systemDir = PathUtils.join(unique, "data-system");
Services.env.set("XDG_DATA_DIRS", systemDir);
gSystemDir = PathUtils.join(systemDir, "applications");
await IOUtils.makeDirectory(gSystemDir, { createAncestors: true });
Services.env.set("XDG_CURRENT_DESKTOP", "FirefoxOS");
await IOUtils.writeUTF8(
PathUtils.join(gHomeDir, filename("deleted")),
BAREBONES_DESKTOP_ENTRY + "Hidden=true\n"
);
await IOUtils.writeUTF8(
PathUtils.join(gSystemDir, filename("deleted")),
BAREBONES_DESKTOP_ENTRY
);
for (const desktopEntry of kDesktopEntries) {
await IOUtils.writeUTF8(
PathUtils.join(gHomeDir, filename(desktopEntry.label + "-home")),
desktopEntry.content
);
await IOUtils.writeUTF8(
PathUtils.join(gSystemDir, filename(desktopEntry.label + "-system")),
desktopEntry.content
);
}
registerCleanupFunction(async () => {
return IOUtils.remove(unique, { recursive: true });
});
});
add_task(function test_desktopEntryStatus() {
Assert.equal(
ShellService.getDesktopEntryStatus(filename("absent")),
Ci.nsIGNOMEShellService.DESKTOP_ENTRY_ABSENT,
"A desktop entry that doesn't exist should be absent."
);
Assert.equal(
ShellService.getDesktopEntryStatus(filename("hidden")),
Ci.nsIGNOMEShellService.DESKTOP_ENTRY_ABSENT,
"A desktop entry shadowed by one with the Hidden= attribute should be absent."
);
for (const desktopEntry of kDesktopEntries) {
Assert.equal(
ShellService.getDesktopEntryStatus(
filename(desktopEntry.label + "-home")
),
desktopEntry.expected,
"Desktop entry matches when at the local level: " + desktopEntry.label
);
Assert.equal(
ShellService.getDesktopEntryStatus(
filename(desktopEntry.label + "-system")
),
desktopEntry.expected,
"Desktop entry matches when at the system level: " + desktopEntry.label
);
}
});

View File

@@ -0,0 +1,109 @@
/* Any copyright is dedicated to the Public Domain.
* https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.defineESModuleGetters(this, {
StartupOSIntegration:
"moz-src:///browser/components/shell/StartupOSIntegration.sys.mjs",
WindowsLaunchOnLogin: "resource://gre/modules/WindowsLaunchOnLogin.sys.mjs",
sinon: "resource://testing-common/Sinon.sys.mjs",
});
const PREF = "browser.startup.windowsLaunchOnLogin.defaultEnabled";
async function runWith({ isFirstRun, prefValue, approved }) {
let sandbox = sinon.createSandbox();
let approvedStub = sandbox
.stub(WindowsLaunchOnLogin, "getLaunchOnLoginApproved")
.resolves(approved);
let createStub = sandbox
.stub(WindowsLaunchOnLogin, "createLaunchOnLogin")
.resolves();
if (prefValue === null) {
Services.prefs.clearUserPref(PREF);
} else {
Services.prefs.setBoolPref(PREF, prefValue);
}
try {
await StartupOSIntegration.maybeCreateLaunchOnLoginOnFirstRun(isFirstRun);
return { approvedStub, createStub };
} finally {
sandbox.restore();
Services.prefs.clearUserPref(PREF);
}
}
add_task(async function test_creates_when_all_conditions_true() {
let { createStub } = await runWith({
isFirstRun: true,
prefValue: true,
approved: true,
});
Assert.ok(
createStub.calledOnce,
"createLaunchOnLogin should be called when isFirstRun, pref, and approval are all true"
);
});
add_task(async function test_skips_when_not_first_run() {
let { createStub, approvedStub } = await runWith({
isFirstRun: false,
prefValue: true,
approved: true,
});
Assert.ok(
!createStub.called,
"createLaunchOnLogin should not be called when isFirstRun is false"
);
Assert.ok(
!approvedStub.called,
"getLaunchOnLoginApproved should be short-circuited when isFirstRun is false"
);
});
add_task(async function test_skips_when_pref_disabled() {
let { createStub, approvedStub } = await runWith({
isFirstRun: true,
prefValue: false,
approved: true,
});
Assert.ok(
!createStub.called,
"createLaunchOnLogin should not be called when pref is false"
);
Assert.ok(
!approvedStub.called,
"getLaunchOnLoginApproved should be short-circuited when pref is false"
);
});
add_task(async function test_skips_when_windows_policy_denies() {
let { createStub, approvedStub } = await runWith({
isFirstRun: true,
prefValue: true,
approved: false,
});
Assert.ok(
approvedStub.calledOnce,
"getLaunchOnLoginApproved should be consulted when pref and isFirstRun are true"
);
Assert.ok(
!createStub.called,
"createLaunchOnLogin should not be called when Windows policy denies"
);
});
add_task(async function test_uses_pref_default_when_unset() {
let { createStub } = await runWith({
isFirstRun: true,
prefValue: null,
approved: true,
});
Assert.ok(
createStub.calledOnce,
"createLaunchOnLogin should be called when pref is at its built-in default of true"
);
});

View File

@@ -5,6 +5,11 @@ run-if = [
firefox-appdir = "browser"
tags = "os_integration"
["test_desktopEntryStatus.js"]
run-if = [
"os == 'linux'",
]
["test_linuxDesktopEntry.js"]
run-if = [
"os == 'linux'",
@@ -15,6 +20,11 @@ run-if = [
"os == 'mac'",
]
["test_maybeCreateLaunchOnLoginOnFirstRun.js"]
run-if = [
"os == 'win'"
]
["test_secondaryTileJs.js"]
run-if = [
"os == 'win'"

View File

@@ -63,7 +63,7 @@ async function do_test(test) {
if (test.value) {
info("Creating mock filepicker to select files");
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window.browsingContext);
MockFilePicker.init();
MockFilePicker.returnValue = MockFilePicker.returnOK;
MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", []);
MockFilePicker.setFiles([tempFile]);

View File

@@ -6,7 +6,7 @@
"version": {
"product": "firefox",
"version": "151.0.4",
"candidate": "151.0.4",
"candidate": "152.0",
"candidateBuild": 1
},
"buildOptions": {