mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
fix: re-enable MacWebContentsOcclusion with embedder window fix (#50579)
* fix: re-enable MacWebContentsOcclusion with embedder window fix Replace the full revert of Chromium's MacWebContentsOcclusion cleanup with a targeted patch that handles embedder windows shown after WebContentsViewCocoa attachment. This lets us drop the feature flag disable in feature_list.cc and re-enable upstream occlusion tracking. Adds tests for show/hide event counts on macOS and visibility tracking across multiple child WebContentsViews. * test: drop show/hide event count assertion The assertion that 'show' fires exactly once per w.show() call is not an API guarantee - macOS can send multiple occlusion state notifications during a single show() when other windows are on screen (common on CI after hundreds of prior tests). The visibilitychange-count test in api-web-contents-view-spec.ts covers the actual invariant we care about. * fix: ignore WebContentsOcclusionCheckerMac synthetic notifications in window delegate On macOS 13.3-25.x, Chromium's occlusion checker enables manual frame-intersection detection and posts synthetic NSWindowDidChangeOcclusionStateNotification tagged with its class name in userInfo. These fire when the checker's NSContainsRect heuristic decides a window is covered by another window's frame, but the real -[NSWindow occlusionState] hasn't changed. Our delegate was treating these the same as real macOS notifications and emitting show/hide events based on occlusionState, which was unchanged - resulting in spurious duplicate show events when e.g. Quick Look opened and its frame intersected the BrowserWindow.
This commit is contained in:
@@ -119,7 +119,7 @@ build_disable_thin_lto_mac.patch
|
||||
feat_corner_smoothing_css_rule_and_blink_painting.patch
|
||||
build_add_public_config_simdutf_config.patch
|
||||
fix_multiple_scopedpumpmessagesinprivatemodes_instances.patch
|
||||
revert_code_health_clean_up_stale_macwebcontentsocclusion.patch
|
||||
fix_handle_embedder_windows_shown_after_webcontentsviewcocoa_attach.patch
|
||||
feat_add_signals_when_embedder_cleanup_callbacks_run_for.patch
|
||||
feat_separate_content_settings_callback_for_sync_and_async_clipboard.patch
|
||||
fix_win32_synchronous_spellcheck.patch
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Samuel Attard <sattard@anthropic.com>
|
||||
Date: Mon, 30 Mar 2026 03:05:40 -0700
|
||||
Subject: fix: handle embedder windows shown after WebContentsViewCocoa attach
|
||||
|
||||
The occlusion checker assumes windows are shown before or at the same
|
||||
time as a WebContentsViewCocoa is attached. Embedders like Electron
|
||||
support creating a window hidden, attaching web contents, and showing
|
||||
later. This breaks three assumptions:
|
||||
|
||||
1. updateWebContentsVisibility only checks -[NSWindow isOccluded], which
|
||||
defaults to NO for never-shown windows, so viewDidMoveToWindow
|
||||
incorrectly reports kVisible for hidden windows.
|
||||
|
||||
2. windowChangedOcclusionState: only responds to checker-originated
|
||||
notifications, but setOccluded: early-returns when isOccluded doesn't
|
||||
change. A hidden window's isOccluded is NO and stays NO after show(),
|
||||
so no checker notification fires on show and the view never updates
|
||||
to kVisible.
|
||||
|
||||
3. performOcclusionStateUpdates iterates orderedWindows and marks
|
||||
not-yet-shown windows as occluded (their occlusionState lacks the
|
||||
Visible bit), which stops painting before first show.
|
||||
|
||||
Fix by also checking occlusionState in updateWebContentsVisibility,
|
||||
responding to macOS-originated notifications in
|
||||
windowChangedOcclusionState:, and skipping non-visible windows in
|
||||
performOcclusionStateUpdates.
|
||||
|
||||
This patch can be removed if the changes are upstreamed to Chromium.
|
||||
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
index a5570988c3721d9f6bd05c402a7658d3af6f2c2c..54aaffde30c14a27068f89b6de6123abd6ea0660 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
@@ -400,9 +400,11 @@ - (void)performOcclusionStateUpdates {
|
||||
for (NSWindow* window in windowsFromFrontToBack) {
|
||||
// The fullscreen transition causes spurious occlusion notifications.
|
||||
// See https://crbug.com/1081229 . Also, ignore windows that don't have
|
||||
- // web contentses.
|
||||
+ // web contentses, and windows that aren't visible (embedders like
|
||||
+ // Electron may create windows hidden with web contents already attached;
|
||||
+ // marking these as occluded would stop painting before first show).
|
||||
if (window == _windowReceivingFullscreenTransitionNotifications ||
|
||||
- ![window containsWebContentsViewCocoa])
|
||||
+ ![window isVisible] || ![window containsWebContentsViewCocoa])
|
||||
continue;
|
||||
|
||||
[window setOccluded:[self isWindowOccluded:window
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
index 1ef2c9052262eccdbc40030746a858b7f30ac469..34708d45274f95b5f35cdefad98ad4a1c3c28e1c 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
@@ -477,7 +477,8 @@ - (void)updateWebContentsVisibility {
|
||||
Visibility visibility = Visibility::kVisible;
|
||||
if ([self isHiddenOrHasHiddenAncestor] || ![self window])
|
||||
visibility = Visibility::kHidden;
|
||||
- else if ([[self window] isOccluded])
|
||||
+ else if ([[self window] isOccluded] ||
|
||||
+ !([[self window] occlusionState] & NSWindowOcclusionStateVisible))
|
||||
visibility = Visibility::kOccluded;
|
||||
|
||||
[self updateWebContentsVisibility:visibility];
|
||||
@@ -521,11 +522,12 @@ - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
|
||||
}
|
||||
|
||||
- (void)windowChangedOcclusionState:(NSNotification*)aNotification {
|
||||
- // Only respond to occlusion notifications sent by the occlusion checker.
|
||||
- NSDictionary* userInfo = [aNotification userInfo];
|
||||
- NSString* occlusionCheckerKey = [WebContentsOcclusionCheckerMac className];
|
||||
- if (userInfo[occlusionCheckerKey] != nil)
|
||||
- [self updateWebContentsVisibility];
|
||||
+ // Respond to occlusion notifications from both macOS and the occlusion
|
||||
+ // checker. Embedders (e.g. Electron) may attach a WebContentsViewCocoa to
|
||||
+ // a window that has not yet been shown; macOS will notify us when the
|
||||
+ // window's occlusion state changes, but the occlusion checker will not
|
||||
+ // because -[NSWindow isOccluded] remains NO before and after show.
|
||||
+ [self updateWebContentsVisibility];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow {
|
||||
@@ -1,279 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: David Sanders <dsanders11@ucsbalum.com>
|
||||
Date: Wed, 8 Jan 2025 23:53:27 -0800
|
||||
Subject: Revert "Code Health: Clean up stale MacWebContentsOcclusion"
|
||||
|
||||
Chrome has removed this WebContentsOcclusion feature flag upstream,
|
||||
which is now causing our visibility tests to break. This patch
|
||||
restores the legacy occlusion behavior to ensure the roll can continue
|
||||
while we debug the issue.
|
||||
|
||||
This patch can be removed when the root cause because the visibility
|
||||
specs failing on MacOS only is debugged and fixed. It should be removed
|
||||
before Electron 35's stable date.
|
||||
|
||||
Refs: https://chromium-review.googlesource.com/c/chromium/src/+/6078344
|
||||
|
||||
This partially (leaves the removal of the feature flag) reverts
|
||||
ef865130abd5539e7bce12308659b19980368f12.
|
||||
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
|
||||
index 04c7635cc093d9d676869383670a8f2199f14ac6..52d76e804e47ab0b56016d26262d6d67cbc00875 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "base/metrics/field_trial_params.h"
|
||||
#import "content/app_shim_remote_cocoa/web_contents_view_cocoa.h"
|
||||
|
||||
+extern CONTENT_EXPORT const base::FeatureParam<bool>
|
||||
+ kEnhancedWindowOcclusionDetection;
|
||||
extern CONTENT_EXPORT const base::FeatureParam<bool>
|
||||
kDisplaySleepAndAppHideDetection;
|
||||
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
index a5570988c3721d9f6bd05c402a7658d3af6f2c2c..0a2dba6aa2d48bc39d2a55c8b4d6606744c10ca7 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
|
||||
@@ -14,9 +14,16 @@
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "base/metrics/field_trial_params.h"
|
||||
#include "base/no_destructor.h"
|
||||
+#include "content/common/features.h"
|
||||
#include "content/public/browser/content_browser_client.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
|
||||
+using features::kMacWebContentsOcclusion;
|
||||
+
|
||||
+// Experiment features.
|
||||
+const base::FeatureParam<bool> kEnhancedWindowOcclusionDetection{
|
||||
+ &kMacWebContentsOcclusion, "EnhancedWindowOcclusionDetection", false};
|
||||
+
|
||||
namespace {
|
||||
|
||||
NSString* const kWindowDidChangePositionInWindowList =
|
||||
@@ -125,7 +132,8 @@ - (void)dealloc {
|
||||
|
||||
- (BOOL)isManualOcclusionDetectionEnabled {
|
||||
return [WebContentsOcclusionCheckerMac
|
||||
- manualOcclusionDetectionSupportedForCurrentMacOSVersion];
|
||||
+ manualOcclusionDetectionSupportedForCurrentMacOSVersion] &&
|
||||
+ kEnhancedWindowOcclusionDetection.Get();
|
||||
}
|
||||
|
||||
// Alternative implementation of orderWindow:relativeTo:. Replaces
|
||||
diff --git a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
index 1ef2c9052262eccdbc40030746a858b7f30ac469..c7101b0d71826b05f61bfe0e74429d922769e792 100644
|
||||
--- a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
+++ b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
|
||||
@@ -15,6 +15,7 @@
|
||||
#import "content/app_shim_remote_cocoa/web_drag_source_mac.h"
|
||||
#import "content/browser/web_contents/web_contents_view_mac.h"
|
||||
#import "content/browser/web_contents/web_drag_dest_mac.h"
|
||||
+#include "content/common/features.h"
|
||||
#include "content/public/browser/content_browser_client.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
#include "ui/base/clipboard/clipboard_constants.h"
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "ui/resources/grit/ui_resources.h"
|
||||
|
||||
using content::DropData;
|
||||
+using features::kMacWebContentsOcclusion;
|
||||
using remote_cocoa::mojom::DraggingInfo;
|
||||
using remote_cocoa::mojom::SelectionDirection;
|
||||
|
||||
@@ -122,12 +124,15 @@ @implementation WebContentsViewCocoa {
|
||||
WebDragSource* __strong _dragSource;
|
||||
NSDragOperation _dragOperation;
|
||||
|
||||
+ BOOL _inFullScreenTransition;
|
||||
BOOL _willSetWebContentsOccludedAfterDelay;
|
||||
}
|
||||
|
||||
+ (void)initialize {
|
||||
- // Create the WebContentsOcclusionCheckerMac shared instance.
|
||||
- [WebContentsOcclusionCheckerMac sharedInstance];
|
||||
+ if (base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ // Create the WebContentsOcclusionCheckerMac shared instance.
|
||||
+ [WebContentsOcclusionCheckerMac sharedInstance];
|
||||
+ }
|
||||
}
|
||||
|
||||
- (instancetype)initWithViewsHostableView:(ui::ViewsHostableView*)v {
|
||||
@@ -438,6 +443,7 @@ - (void)updateWebContentsVisibility:
|
||||
(remote_cocoa::mojom::Visibility)visibility {
|
||||
using remote_cocoa::mojom::Visibility;
|
||||
|
||||
+ DCHECK(base::FeatureList::IsEnabled(kMacWebContentsOcclusion));
|
||||
if (!_host)
|
||||
return;
|
||||
|
||||
@@ -483,6 +489,20 @@ - (void)updateWebContentsVisibility {
|
||||
[self updateWebContentsVisibility:visibility];
|
||||
}
|
||||
|
||||
+- (void)legacyUpdateWebContentsVisibility {
|
||||
+ using remote_cocoa::mojom::Visibility;
|
||||
+ if (!_host || _inFullScreenTransition)
|
||||
+ return;
|
||||
+ Visibility visibility = Visibility::kVisible;
|
||||
+ if ([self isHiddenOrHasHiddenAncestor] || ![self window])
|
||||
+ visibility = Visibility::kHidden;
|
||||
+ else if ([[self window] occlusionState] & NSWindowOcclusionStateVisible)
|
||||
+ visibility = Visibility::kVisible;
|
||||
+ else
|
||||
+ visibility = Visibility::kOccluded;
|
||||
+ _host->OnWindowVisibilityChanged(visibility);
|
||||
+}
|
||||
+
|
||||
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
|
||||
// Subviews do not participate in auto layout unless the the size this view
|
||||
// changes. This allows RenderWidgetHostViewMac::SetBounds(..) to select a
|
||||
@@ -505,11 +525,39 @@ - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
|
||||
|
||||
NSWindow* oldWindow = [self window];
|
||||
|
||||
+ if (base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ if (oldWindow) {
|
||||
+ [notificationCenter
|
||||
+ removeObserver:self
|
||||
+ name:NSWindowDidChangeOcclusionStateNotification
|
||||
+ object:oldWindow];
|
||||
+ }
|
||||
+
|
||||
+ if (newWindow) {
|
||||
+ [notificationCenter
|
||||
+ addObserver:self
|
||||
+ selector:@selector(windowChangedOcclusionState:)
|
||||
+ name:NSWindowDidChangeOcclusionStateNotification
|
||||
+ object:newWindow];
|
||||
+ }
|
||||
+
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ _inFullScreenTransition = NO;
|
||||
if (oldWindow) {
|
||||
- [notificationCenter
|
||||
- removeObserver:self
|
||||
- name:NSWindowDidChangeOcclusionStateNotification
|
||||
- object:oldWindow];
|
||||
+ NSArray* notificationsToRemove = @[
|
||||
+ NSWindowDidChangeOcclusionStateNotification,
|
||||
+ NSWindowWillEnterFullScreenNotification,
|
||||
+ NSWindowDidEnterFullScreenNotification,
|
||||
+ NSWindowWillExitFullScreenNotification,
|
||||
+ NSWindowDidExitFullScreenNotification
|
||||
+ ];
|
||||
+ for (NSString* notificationName in notificationsToRemove) {
|
||||
+ [notificationCenter removeObserver:self
|
||||
+ name:notificationName
|
||||
+ object:oldWindow];
|
||||
+ }
|
||||
}
|
||||
|
||||
if (newWindow) {
|
||||
@@ -517,26 +565,66 @@ - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
|
||||
selector:@selector(windowChangedOcclusionState:)
|
||||
name:NSWindowDidChangeOcclusionStateNotification
|
||||
object:newWindow];
|
||||
+ // The fullscreen transition causes spurious occlusion notifications.
|
||||
+ // See https://crbug.com/1081229
|
||||
+ [notificationCenter addObserver:self
|
||||
+ selector:@selector(fullscreenTransitionStarted:)
|
||||
+ name:NSWindowWillEnterFullScreenNotification
|
||||
+ object:newWindow];
|
||||
+ [notificationCenter addObserver:self
|
||||
+ selector:@selector(fullscreenTransitionComplete:)
|
||||
+ name:NSWindowDidEnterFullScreenNotification
|
||||
+ object:newWindow];
|
||||
+ [notificationCenter addObserver:self
|
||||
+ selector:@selector(fullscreenTransitionStarted:)
|
||||
+ name:NSWindowWillExitFullScreenNotification
|
||||
+ object:newWindow];
|
||||
+ [notificationCenter addObserver:self
|
||||
+ selector:@selector(fullscreenTransitionComplete:)
|
||||
+ name:NSWindowDidExitFullScreenNotification
|
||||
+ object:newWindow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowChangedOcclusionState:(NSNotification*)aNotification {
|
||||
- // Only respond to occlusion notifications sent by the occlusion checker.
|
||||
- NSDictionary* userInfo = [aNotification userInfo];
|
||||
- NSString* occlusionCheckerKey = [WebContentsOcclusionCheckerMac className];
|
||||
- if (userInfo[occlusionCheckerKey] != nil)
|
||||
- [self updateWebContentsVisibility];
|
||||
+ if (!base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ [self legacyUpdateWebContentsVisibility];
|
||||
+ return;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+- (void)fullscreenTransitionStarted:(NSNotification*)notification {
|
||||
+ _inFullScreenTransition = YES;
|
||||
+}
|
||||
+
|
||||
+- (void)fullscreenTransitionComplete:(NSNotification*)notification {
|
||||
+ _inFullScreenTransition = NO;
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow {
|
||||
+ if (!base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ [self legacyUpdateWebContentsVisibility];
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
[self updateWebContentsVisibility];
|
||||
}
|
||||
|
||||
- (void)viewDidHide {
|
||||
+ if (!base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ [self legacyUpdateWebContentsVisibility];
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
[self updateWebContentsVisibility];
|
||||
}
|
||||
|
||||
- (void)viewDidUnhide {
|
||||
+ if (!base::FeatureList::IsEnabled(kMacWebContentsOcclusion)) {
|
||||
+ [self legacyUpdateWebContentsVisibility];
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
[self updateWebContentsVisibility];
|
||||
}
|
||||
|
||||
diff --git a/content/common/features.cc b/content/common/features.cc
|
||||
index 1272f3f588e00162a2b8665b23a4d23ef06e8ede..ff1a27d1928e4d1af10b46d570a13519a42f62d8 100644
|
||||
--- a/content/common/features.cc
|
||||
+++ b/content/common/features.cc
|
||||
@@ -395,6 +395,14 @@ BASE_FEATURE(kInterestGroupUpdateIfOlderThan, base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
BASE_FEATURE(kIOSurfaceCapturer, base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
#endif
|
||||
|
||||
+// Feature that controls whether WebContentsOcclusionChecker should handle
|
||||
+// occlusion notifications.
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+BASE_FEATURE(kMacWebContentsOcclusion,
|
||||
+ "MacWebContentsOcclusion",
|
||||
+ base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
+#endif
|
||||
+
|
||||
// When enabled, child process will not terminate itself when IPC is reset.
|
||||
BASE_FEATURE(kKeepChildProcessAfterIPCReset, base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
diff --git a/content/common/features.h b/content/common/features.h
|
||||
index dcfbeeab7b80b687ada74f6826107e26901e7cf1..980a2df8bb93c4cf569384fad1ed0982fc77ef68 100644
|
||||
--- a/content/common/features.h
|
||||
+++ b/content/common/features.h
|
||||
@@ -150,6 +150,9 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE(kInterestGroupUpdateIfOlderThan);
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kIOSurfaceCapturer);
|
||||
#endif
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kMacWebContentsOcclusion);
|
||||
+#endif
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kKeepChildProcessAfterIPCReset);
|
||||
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kLocalNetworkAccessForWorkers);
|
||||
@@ -87,13 +87,6 @@ void InitializeFeatureList() {
|
||||
std::string(",") + sandbox::policy::features::kNetworkServiceSandbox.name;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
disable_features +=
|
||||
// MacWebContentsOcclusion is causing some odd visibility
|
||||
// issues with multiple web contents
|
||||
std::string(",") + features::kMacWebContentsOcclusion.name;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
// Enable window.showSaveFilePicker api for saving pdf files.
|
||||
// Refs https://issues.chromium.org/issues/373852607
|
||||
|
||||
@@ -43,6 +43,14 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
|
||||
#pragma mark - NSWindowDelegate
|
||||
|
||||
- (void)windowDidChangeOcclusionState:(NSNotification*)notification {
|
||||
// Chromium's WebContentsOcclusionCheckerMac posts synthetic occlusion
|
||||
// notifications tagged with its class name in userInfo. These reflect the
|
||||
// checker's manual frame-intersection heuristic, not an actual macOS
|
||||
// occlusion state change, so the real occlusionState hasn't changed and
|
||||
// emitting show/hide in response would be spurious.
|
||||
if (notification.userInfo[@"WebContentsOcclusionCheckerMac"] != nil)
|
||||
return;
|
||||
|
||||
// notification.object is the window that changed its state.
|
||||
// It's safe to use self.window instead if you don't assign one delegate to
|
||||
// many windows
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BaseWindow, BrowserWindow, View, WebContentsView, webContents, screen }
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { once } from 'node:events';
|
||||
import { setTimeout as setTimeoutAsync } from 'node:timers/promises';
|
||||
|
||||
import { HexColors, ScreenCapture, hasCapturableScreen, nextFrameTime } from './lib/screen-helpers';
|
||||
import { defer, ifdescribe, waitUntil } from './lib/spec-helpers';
|
||||
@@ -309,6 +310,94 @@ describe('WebContentsView', () => {
|
||||
}
|
||||
expect(visibilityState).to.equal('visible');
|
||||
});
|
||||
|
||||
it('tracks visibility for multiple child WebContentsViews', async () => {
|
||||
const w = new BaseWindow({ show: false });
|
||||
const cv = new View();
|
||||
w.setContentView(cv);
|
||||
|
||||
const v1 = new WebContentsView();
|
||||
const v2 = new WebContentsView();
|
||||
cv.addChildView(v1);
|
||||
cv.addChildView(v2);
|
||||
v1.setBounds({ x: 0, y: 0, width: 400, height: 300 });
|
||||
v2.setBounds({ x: 0, y: 300, width: 400, height: 300 });
|
||||
|
||||
await v1.webContents.loadURL('about:blank');
|
||||
await v2.webContents.loadURL('about:blank');
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'hidden'))).to.eventually.be.fulfilled();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v2, 'hidden'))).to.eventually.be.fulfilled();
|
||||
|
||||
w.show();
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'visible'))).to.eventually.be.fulfilled();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v2, 'visible'))).to.eventually.be.fulfilled();
|
||||
|
||||
w.hide();
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'hidden'))).to.eventually.be.fulfilled();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v2, 'hidden'))).to.eventually.be.fulfilled();
|
||||
});
|
||||
|
||||
it('tracks visibility independently when a child WebContentsView is hidden via setVisible', async () => {
|
||||
const w = new BaseWindow();
|
||||
const cv = new View();
|
||||
w.setContentView(cv);
|
||||
|
||||
const v1 = new WebContentsView();
|
||||
const v2 = new WebContentsView();
|
||||
cv.addChildView(v1);
|
||||
cv.addChildView(v2);
|
||||
v1.setBounds({ x: 0, y: 0, width: 400, height: 300 });
|
||||
v2.setBounds({ x: 0, y: 300, width: 400, height: 300 });
|
||||
|
||||
await v1.webContents.loadURL('about:blank');
|
||||
await v2.webContents.loadURL('about:blank');
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'visible'))).to.eventually.be.fulfilled();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v2, 'visible'))).to.eventually.be.fulfilled();
|
||||
|
||||
v1.setVisible(false);
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'hidden'))).to.eventually.be.fulfilled();
|
||||
// v2 should remain visible while v1 is hidden
|
||||
expect(await v2.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
|
||||
|
||||
v1.setVisible(true);
|
||||
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v1, 'visible'))).to.eventually.be.fulfilled();
|
||||
});
|
||||
|
||||
it('fires a single visibilitychange event per show/hide transition', async () => {
|
||||
const w = new BaseWindow({ show: false });
|
||||
const v = new WebContentsView();
|
||||
w.setContentView(v);
|
||||
await v.webContents.loadURL('about:blank');
|
||||
|
||||
await v.webContents.executeJavaScript(`
|
||||
window.__visChanges = [];
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
window.__visChanges.push(document.visibilityState);
|
||||
});
|
||||
`);
|
||||
|
||||
w.show();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v, 'visible'))).to.eventually.be.fulfilled();
|
||||
|
||||
// Give any delayed/queued occlusion updates time to fire.
|
||||
await setTimeoutAsync(1500);
|
||||
|
||||
w.hide();
|
||||
await expect(waitUntil(async () => await haveVisibilityState(v, 'hidden'))).to.eventually.be.fulfilled();
|
||||
|
||||
await setTimeoutAsync(1500);
|
||||
|
||||
const changes = await v.webContents.executeJavaScript('window.__visChanges');
|
||||
// Expect exactly one 'visible' followed by one 'hidden'. Extra events
|
||||
// would indicate the occlusion checker is causing spurious transitions.
|
||||
expect(changes).to.deep.equal(['visible', 'hidden']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setBorderRadius', () => {
|
||||
|
||||
Reference in New Issue
Block a user