diff --git a/README.md b/README.md index af0a6a3ca..0fb827b34 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne ### Firefox Versions - [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `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 diff --git a/build/firefox-cache/l10n-last-commit-hash b/build/firefox-cache/l10n-last-commit-hash index f138bc25d..3d6223d5d 100644 --- a/build/firefox-cache/l10n-last-commit-hash +++ b/build/firefox-cache/l10n-last-commit-hash @@ -1 +1 @@ -9a6aa4c359d1fb6ac60decc82402f82d49a17cea \ No newline at end of file +a58ad2d2952face15859068dd4421cf68d6a9dda \ No newline at end of file diff --git a/src/external-patches/firefox/native_macos_popovers/D284084.patch b/src/external-patches/firefox/native_macos_popovers/D284084.patch index 59e6e31fb..791888e3c 100644 --- a/src/external-patches/firefox/native_macos_popovers/D284084.patch +++ b/src/external-patches/firefox/native_macos_popovers/D284084.patch @@ -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"> @@ -19,7 +19,7 @@ diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content @@ -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"> @@ -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"> @@ -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"), diff --git a/src/external-patches/firefox/native_macos_popovers/D299584.patch b/src/external-patches/firefox/native_macos_popovers/D299584.patch index a729c092c..11c4006be 100644 --- a/src/external-patches/firefox/native_macos_popovers/D299584.patch +++ b/src/external-patches/firefox/native_macos_popovers/D299584.patch @@ -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 diff --git a/src/zen/tests/mochitests/safebrowsing/browser_whitelisted.js b/src/zen/tests/mochitests/safebrowsing/browser_whitelisted.js index c62d41a4f..5848a32ff 100644 --- a/src/zen/tests/mochitests/safebrowsing/browser_whitelisted.js +++ b/src/zen/tests/mochitests/safebrowsing/browser_whitelisted.js @@ -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); }); diff --git a/src/zen/tests/mochitests/shell/browser.toml b/src/zen/tests/mochitests/shell/browser.toml index 8f6af8802..79e0e5b60 100644 --- a/src/zen/tests/mochitests/shell/browser.toml +++ b/src/zen/tests/mochitests/shell/browser.toml @@ -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" diff --git a/src/zen/tests/mochitests/shell/browser_1119088.js b/src/zen/tests/mochitests/shell/browser_1119088.js index 6702769a3..2f466c92d 100644 --- a/src/zen/tests/mochitests/shell/browser_1119088.js +++ b/src/zen/tests/mochitests/shell/browser_1119088.js @@ -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" diff --git a/src/zen/tests/mochitests/shell/browser_420786.js b/src/zen/tests/mochitests/shell/browser_420786.js index b9becb49c..1f7beb45c 100644 --- a/src/zen/tests/mochitests/shell/browser_420786.js +++ b/src/zen/tests/mochitests/shell/browser_420786.js @@ -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" diff --git a/src/zen/tests/mochitests/shell/browser_setDefaultPDFHandler.js b/src/zen/tests/mochitests/shell/browser_setDefaultPDFHandler.js index 332a7c6f7..04cc1f069 100644 --- a/src/zen/tests/mochitests/shell/browser_setDefaultPDFHandler.js +++ b/src/zen/tests/mochitests/shell/browser_setDefaultPDFHandler.js @@ -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(); } }); diff --git a/src/zen/tests/mochitests/shell/browser_setDesktopBackgroundPreview.js b/src/zen/tests/mochitests/shell/browser_setDesktopBackgroundPreview.js index b847d0998..733123e04 100644 --- a/src/zen/tests/mochitests/shell/browser_setDesktopBackgroundPreview.js +++ b/src/zen/tests/mochitests/shell/browser_setDesktopBackgroundPreview.js @@ -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 => { diff --git a/src/zen/tests/mochitests/shell/gtest/SetDefaultBrowserButtonTests.cpp b/src/zen/tests/mochitests/shell/gtest/SetDefaultBrowserButtonTests.cpp index 18f901df8..d28d077b9 100644 --- a/src/zen/tests/mochitests/shell/gtest/SetDefaultBrowserButtonTests.cpp +++ b/src/zen/tests/mochitests/shell/gtest/SetDefaultBrowserButtonTests.cpp @@ -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/. */ diff --git a/src/zen/tests/mochitests/shell/large.png b/src/zen/tests/mochitests/shell/large.png new file mode 100644 index 000000000..37012cf96 Binary files /dev/null and b/src/zen/tests/mochitests/shell/large.png differ diff --git a/src/zen/tests/mochitests/shell/unit/test_desktopEntryStatus.js b/src/zen/tests/mochitests/shell/unit/test_desktopEntryStatus.js new file mode 100644 index 000000000..de9b047e9 --- /dev/null +++ b/src/zen/tests/mochitests/shell/unit/test_desktopEntryStatus.js @@ -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 + ); + } +}); diff --git a/src/zen/tests/mochitests/shell/unit/test_maybeCreateLaunchOnLoginOnFirstRun.js b/src/zen/tests/mochitests/shell/unit/test_maybeCreateLaunchOnLoginOnFirstRun.js new file mode 100644 index 000000000..16106de4d --- /dev/null +++ b/src/zen/tests/mochitests/shell/unit/test_maybeCreateLaunchOnLoginOnFirstRun.js @@ -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" + ); +}); diff --git a/src/zen/tests/mochitests/shell/unit/xpcshell.toml b/src/zen/tests/mochitests/shell/unit/xpcshell.toml index 4b4a87edc..bfd6f01ea 100644 --- a/src/zen/tests/mochitests/shell/unit/xpcshell.toml +++ b/src/zen/tests/mochitests/shell/unit/xpcshell.toml @@ -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'" diff --git a/src/zen/tests/mochitests/tooltiptext/browser_input_file_tooltips.js b/src/zen/tests/mochitests/tooltiptext/browser_input_file_tooltips.js index 2f1385f37..7e4cfb9a0 100644 --- a/src/zen/tests/mochitests/tooltiptext/browser_input_file_tooltips.js +++ b/src/zen/tests/mochitests/tooltiptext/browser_input_file_tooltips.js @@ -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]); diff --git a/surfer.json b/surfer.json index bcc52e5b1..74a1fb7b8 100644 --- a/surfer.json +++ b/surfer.json @@ -6,7 +6,7 @@ "version": { "product": "firefox", "version": "151.0.4", - "candidate": "151.0.4", + "candidate": "152.0", "candidateBuild": 1 }, "buildOptions": {