blob: e175e3d1b6105117c1c3043c80ee3de827196128 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "base/mac/foundation_util.h"
#include "base/mac/scoped_nsobject.h"
#include "base/mac/scoped_objc_class_swizzler.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#import "content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
using remote_cocoa::mojom::DraggingInfo;
using remote_cocoa::mojom::DraggingInfoPtr;
using remote_cocoa::mojom::SelectionDirection;
using content::DropData;
namespace {
const char kEnhancedWindowOcclusionDetection[] =
"EnhancedWindowOcclusionDetection";
const char kDisplaySleepAndAppHideDetection[] =
"DisplaySleepAndAppHideDetection";
const int kNeverCalled = -100;
} // namespace
// An NSWindow subclass that enables programmatic setting of macOS occlusion and
// miniaturize states.
@interface WebContentsHostWindowForOcclusionTesting : NSWindow {
BOOL _miniaturizedForTesting;
}
@property(assign, nonatomic) BOOL occludedForTesting;
@property(assign, nonatomic) BOOL modifyingChildWindowList;
@end
@implementation WebContentsHostWindowForOcclusionTesting
@synthesize occludedForTesting = _occludedForTesting;
@synthesize modifyingChildWindowList = _modifyingChildWindowList;
- (NSWindowOcclusionState)occlusionState {
return _occludedForTesting ? 0 : NSWindowOcclusionStateVisible;
}
- (void)miniaturize:(id)sender {
// Miniaturizing a window doesn't immediately take effect (isMiniaturized
// returns false) so fake it with a flag and removal from window list.
_miniaturizedForTesting = YES;
[self orderOut:nil];
}
- (void)deminiaturize:(id)sender {
_miniaturizedForTesting = NO;
[self orderFront:nil];
}
- (BOOL)isMiniaturized {
return _miniaturizedForTesting;
}
- (void)addChildWindow:(NSWindow*)childWindow
ordered:(NSWindowOrderingMode)place {
_modifyingChildWindowList = YES;
[super addChildWindow:childWindow ordered:place];
_modifyingChildWindowList = NO;
}
- (void)removeChildWindow:(NSWindow*)childWindow {
_modifyingChildWindowList = YES;
[super removeChildWindow:childWindow];
_modifyingChildWindowList = NO;
}
@end
@interface WebContentsViewCocoaForOcclusionTesting : WebContentsViewCocoa
@end
@implementation WebContentsViewCocoaForOcclusionTesting
- (void)updateWebContentsVisibilityFromWindowVisibility:
(remote_cocoa::mojom::Visibility)windowVisibility {
WebContentsHostWindowForOcclusionTesting* hostWindow =
base::mac::ObjCCast<WebContentsHostWindowForOcclusionTesting>(
[self window]);
EXPECT_FALSE([hostWindow modifyingChildWindowList]);
[super updateWebContentsVisibilityFromWindowVisibility:windowVisibility];
}
@end
// A class that waits for invocations of the private
// -_notifyUpdateWebContentsVisibility method in
// WebContentsOcclusionCheckerMac to complete.
@interface WebContentVisibilityUpdateWatcher : NSObject
@end
@implementation WebContentVisibilityUpdateWatcher
+ (std::unique_ptr<base::mac::ScopedObjCClassSwizzler>&)swizzler {
// The swizzler needs to be generally available (i.e. not stored in an
// instance variable) because we want to call the original
// -_notifyUpdateWebContentsVisibility from the swapped-in version
// defined below. At the point where the swapped-in version is
// called, the callee is an instance of WebContentsOcclusionCheckerMac,
// not WebContentVisibilityUpdateWatcher, so it has no access to any
// instance variables we define for WebContentVisibilityUpdateWatcher.
// Storing the swizzler in a static makes it available to any caller.
static base::NoDestructor<std::unique_ptr<base::mac::ScopedObjCClassSwizzler>>
swizzler;
return *swizzler;
}
// A global place to stash the runLoop.
+ (base::RunLoop**)runLoop {
static base::RunLoop* runLoop = nullptr;
return &runLoop;
}
- (instancetype)init {
self = [super init];
[WebContentVisibilityUpdateWatcher swizzler].reset(
new base::mac::ScopedObjCClassSwizzler(
NSClassFromString(@"WebContentsOcclusionCheckerMac"),
[WebContentVisibilityUpdateWatcher class],
@selector(_notifyUpdateWebContentsVisibility)));
return self;
}
- (void)dealloc {
[WebContentVisibilityUpdateWatcher swizzler].reset();
[super dealloc];
}
- (void)waitForOcclusionUpdate {
// -_notifyUpdateWebContentsVisibility is invoked by
// -performSelector:afterDelay: which means it will only get called after
// a turn of the run loop. So, we don't have to worry that it might have
// already been called, which would block us here until the test timed out.
base::RunLoop runLoop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, runLoop.QuitClosure(), TestTimeouts::action_timeout());
(*[WebContentVisibilityUpdateWatcher runLoop]) = &runLoop;
runLoop.Run();
(*[WebContentVisibilityUpdateWatcher runLoop]) = nullptr;
}
- (void)_notifyUpdateWebContentsVisibility {
// Proceed with the notification.
[WebContentVisibilityUpdateWatcher swizzler]->InvokeOriginal<void>(
self, @selector(_notifyUpdateWebContentsVisibility));
if (*[WebContentVisibilityUpdateWatcher runLoop]) {
(*[WebContentVisibilityUpdateWatcher runLoop])->Quit();
}
}
@end
// A class that counts invocations of the public
// -notifyUpdateWebContentsVisibility method in WebContentsOcclusionCheckerMac.
@interface WebContentVisibilityUpdateCounter : NSObject
@end
@implementation WebContentVisibilityUpdateCounter
+ (std::unique_ptr<base::mac::ScopedObjCClassSwizzler>&)swizzler {
static base::NoDestructor<std::unique_ptr<base::mac::ScopedObjCClassSwizzler>>
swizzler;
return *swizzler;
}
+ (NSInteger&)methodInvocationCount {
static NSInteger invocationCount = 0;
return invocationCount;
}
+ (BOOL)methodNeverCalled {
return
[WebContentVisibilityUpdateCounter methodInvocationCount] == kNeverCalled;
}
- (instancetype)init {
self = [super init];
// Set up the swizzling.
[WebContentVisibilityUpdateCounter swizzler].reset(
new base::mac::ScopedObjCClassSwizzler(
NSClassFromString(@"WebContentsOcclusionCheckerMac"),
[WebContentVisibilityUpdateCounter class],
@selector(notifyUpdateWebContentsVisibility)));
[WebContentVisibilityUpdateCounter methodInvocationCount] = kNeverCalled;
return self;
}
- (void)dealloc {
[WebContentVisibilityUpdateCounter methodInvocationCount] = 0;
[super dealloc];
}
- (void)notifyUpdateWebContentsVisibility {
// Proceed with the notification.
[WebContentVisibilityUpdateCounter swizzler]->InvokeOriginal<void>(
self, @selector(notifyUpdateWebContentsVisibility));
NSInteger count = [WebContentVisibilityUpdateCounter methodInvocationCount];
if (count < 0) {
count = 0;
}
[WebContentVisibilityUpdateCounter methodInvocationCount] = count + 1;
}
@end
namespace content {
// A stub class for WebContentsNSViewHost.
class WebContentsNSViewHostStub
: public remote_cocoa::mojom::WebContentsNSViewHost {
public:
WebContentsNSViewHostStub() {}
void OnMouseEvent(bool motion, bool exited) override {}
void OnBecameFirstResponder(SelectionDirection direction) override {}
void OnWindowVisibilityChanged(
remote_cocoa::mojom::Visibility visibility) override {
_visibility = visibility;
}
remote_cocoa::mojom::Visibility WebContentsVisibility() {
return _visibility;
}
void SetDropData(const ::content::DropData& drop_data) override {}
bool DraggingEntered(DraggingInfoPtr dragging_info,
uint32_t* out_result) override {
return false;
}
void DraggingEntered(DraggingInfoPtr dragging_info,
DraggingEnteredCallback callback) override {}
void DraggingExited() override {}
void DraggingUpdated(DraggingInfoPtr dragging_info,
DraggingUpdatedCallback callback) override {}
bool PerformDragOperation(DraggingInfoPtr dragging_info,
bool* out_result) override {
return false;
}
void PerformDragOperation(DraggingInfoPtr dragging_info,
PerformDragOperationCallback callback) override {}
bool DragPromisedFileTo(const ::base::FilePath& file_path,
const ::content::DropData& drop_data,
const ::GURL& download_url,
::base::FilePath* out_file_path) override {
return false;
}
void DragPromisedFileTo(const ::base::FilePath& file_path,
const ::content::DropData& drop_data,
const ::GURL& download_url,
DragPromisedFileToCallback callback) override {}
void EndDrag(uint32_t drag_operation,
const ::gfx::PointF& local_point,
const ::gfx::PointF& screen_point) override {}
private:
remote_cocoa::mojom::Visibility _visibility;
};
// Sets up occlusion tests.
class WindowOcclusionBrowserTestMac : public ContentBrowserTest {
public:
void WaitForOcclusionUpdate() {
if (![[NSClassFromString(@"WebContentsOcclusionCheckerMac") sharedInstance]
willUpdateWebContentsVisibility]) {
return;
}
base::scoped_nsobject<WebContentVisibilityUpdateWatcher> watcher(
[[WebContentVisibilityUpdateWatcher alloc] init]);
[watcher waitForOcclusionUpdate];
}
static WebContentsViewCocoaForOcclusionTesting* WebContentsInWindow(
NSRect contentRect,
NSWindowStyleMask styleMask = NSWindowStyleMaskClosable) {
WebContentsHostWindowForOcclusionTesting* window =
[[[WebContentsHostWindowForOcclusionTesting alloc]
initWithContentRect:contentRect
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:YES] autorelease];
NSRect window_frame = [NSWindow frameRectForContentRect:contentRect
styleMask:styleMask];
window_frame.origin = NSMakePoint(20.0, 200.0);
[window setFrame:window_frame display:NO];
[window setReleasedWhenClosed:NO];
const NSRect kWebContentsFrame = NSMakeRect(0.0, 0.0, 10.0, 10.0);
WebContentsViewCocoaForOcclusionTesting* web_contents_view =
[[[WebContentsViewCocoaForOcclusionTesting alloc]
initWithFrame:kWebContentsFrame] autorelease];
[[window contentView] addSubview:web_contents_view];
return web_contents_view;
}
// Creates |window_a| with a visible (i.e. unoccluded) WebContentsViewCocoa.
void InitWindowA() {
const NSRect kWindowAContentRect = NSMakeRect(0.0, 0.0, 80.0, 60.0);
window_a_web_contents_view_cocoa.reset(
[WebContentsInWindow(kWindowAContentRect) retain]);
window_a.reset(
base::mac::ObjCCast<WebContentsHostWindowForOcclusionTesting>(
[[window_a_web_contents_view_cocoa window] retain]));
[window_a setTitle:@"window_a"];
// Set up a fake host so we can check the occlusion status.
[window_a_web_contents_view_cocoa setHost:&_host_a];
// Bring the browser window onscreen.
OrderWindowFront(window_a);
// Init visibility state.
SetWindowAWebContentsVisibility(remote_cocoa::mojom::Visibility::kVisible);
}
void InitWindowB(NSRect window_frame = NSZeroRect) {
const NSRect kWindowBContentRect = NSMakeRect(0.0, 0.0, 40.0, 40.0);
window_b.reset(
base::mac::ObjCCast<WebContentsHostWindowForOcclusionTesting>(
[[WebContentsInWindow(kWindowBContentRect) window] retain]));
[window_b setTitle:@"window_b"];
if (NSIsEmptyRect(window_frame)) {
window_frame.size =
[NSWindow frameRectForContentRect:kWindowBContentRect
styleMask:[window_b styleMask]]
.size;
}
[window_b setFrame:window_frame display:NO];
OrderWindowFront(window_b);
}
void SetWindowFrame(NSWindow* window, NSRect frame) {
[window setFrame:frame display:YES];
WaitForOcclusionUpdate();
}
void OrderWindowFront(NSWindow* window) {
base::scoped_nsobject<WebContentVisibilityUpdateCounter> watcher;
if (!_enhanced_window_occlusion_detection_enabled &&
!_display_sleep_detection_enabled) {
watcher.reset([[WebContentVisibilityUpdateCounter alloc] init]);
}
[window orderWindow:NSWindowAbove relativeTo:0];
ASSERT_TRUE([window isVisible]);
if (_enhanced_window_occlusion_detection_enabled) {
WaitForOcclusionUpdate();
} else if (!_display_sleep_detection_enabled) {
EXPECT_TRUE([WebContentVisibilityUpdateCounter methodNeverCalled]);
}
}
void OrderWindowOut(NSWindow* window) {
[window orderWindow:NSWindowOut relativeTo:0];
ASSERT_FALSE([window isVisible]);
WaitForOcclusionUpdate();
}
void CloseWindow(NSWindow* window) {
[window close];
ASSERT_FALSE([window isVisible]);
WaitForOcclusionUpdate();
}
void MiniaturizeWindow(NSWindow* window) {
[window miniaturize:nil];
WaitForOcclusionUpdate();
}
void DeminiaturizeWindow(NSWindow* window) {
[window deminiaturize:nil];
WaitForOcclusionUpdate();
}
void AddSubviewOfView(NSView* subview, NSView* view) {
[view addSubview:subview];
WaitForOcclusionUpdate();
}
void SetViewHidden(NSView* view, BOOL hidden) {
[view setHidden:hidden];
WaitForOcclusionUpdate();
}
void RemoveViewFromSuperview(NSView* view) {
[view removeFromSuperview];
WaitForOcclusionUpdate();
}
void PostNotification(NSString* notification_name, id object = nil) {
base::scoped_nsobject<WebContentVisibilityUpdateWatcher> watcher(
[[WebContentVisibilityUpdateWatcher alloc] init]);
[[NSNotificationCenter defaultCenter] postNotificationName:notification_name
object:object
userInfo:nil];
// Ignore notifications that don't go through
// _notifyUpdateWebContentsVisibility.
if ([notification_name
isEqualToString:NSWindowDidChangeOcclusionStateNotification] ||
[notification_name
isEqualToString:NSWindowWillEnterFullScreenNotification] ||
[notification_name
isEqualToString:NSWindowDidEnterFullScreenNotification] ||
[notification_name
isEqualToString:NSWindowWillExitFullScreenNotification] ||
[notification_name
isEqualToString:NSWindowDidExitFullScreenNotification]) {
return;
}
[watcher waitForOcclusionUpdate];
}
void PostWorkspaceNotification(NSString* notification_name) {
ASSERT_TRUE([[NSWorkspace sharedWorkspace] notificationCenter]);
base::scoped_nsobject<WebContentVisibilityUpdateWatcher> watcher(
[[WebContentVisibilityUpdateWatcher alloc] init]);
[[[NSWorkspace sharedWorkspace] notificationCenter]
postNotificationName:notification_name
object:nil
userInfo:nil];
[watcher waitForOcclusionUpdate];
}
remote_cocoa::mojom::Visibility WindowAWebContentsVisibility() {
return _host_a.WebContentsVisibility();
}
void SetWindowAWebContentsVisibility(
remote_cocoa::mojom::Visibility visibility) {
_host_a.OnWindowVisibilityChanged(visibility);
}
void TearDownInProcessBrowserTestFixture() override {
[window_a_web_contents_view_cocoa setHost:nullptr];
}
base::scoped_nsobject<WebContentsHostWindowForOcclusionTesting> window_a;
base::scoped_nsobject<WebContentsViewCocoa> window_a_web_contents_view_cocoa;
base::scoped_nsobject<WebContentsHostWindowForOcclusionTesting> window_b;
protected:
bool _enhanced_window_occlusion_detection_enabled = false;
bool _display_sleep_detection_enabled = false;
private:
WebContentsNSViewHostStub _host_a;
};
class WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature
: public WindowOcclusionBrowserTestMac {
public:
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature() {
_features.InitAndEnableFeatureWithParameters(
features::kMacWebContentsOcclusion,
{{kEnhancedWindowOcclusionDetection, "true"}});
_enhanced_window_occlusion_detection_enabled = true;
}
private:
base::test::ScopedFeatureList _features;
};
class WindowOcclusionBrowserTestMacWithDisplaySleepDetectionFeature
: public WindowOcclusionBrowserTestMac {
public:
WindowOcclusionBrowserTestMacWithDisplaySleepDetectionFeature() {
_features.InitAndEnableFeatureWithParameters(
features::kMacWebContentsOcclusion,
{{kDisplaySleepAndAppHideDetection, "true"}});
_display_sleep_detection_enabled = true;
}
private:
base::test::ScopedFeatureList _features;
};
// Test that enhanced occlusion detection doesn't work if the feature's not
// enabled. Disabled to prevent failures in fieldtrial testing.
IN_PROC_BROWSER_TEST_F(WindowOcclusionBrowserTestMac,
DISABLED_ManualOcclusionDetectionDisabled) {
InitWindowA();
// Create a second window and place it exactly over window_a. The window
// should still be considered visible.
InitWindowB([window_a frame]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
}
// Test that display sleep and app hide detection don't work if the feature's
// not enabled. Disabled to prevent failures in fieldtrial testing.
IN_PROC_BROWSER_TEST_F(WindowOcclusionBrowserTestMac,
DISABLED_OcclusionDetectionOnDisplaySleepDisabled) {
InitWindowA();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Fake a display sleep notification.
ASSERT_TRUE([[NSWorkspace sharedWorkspace] notificationCenter]);
base::scoped_nsobject<WebContentVisibilityUpdateCounter> watcher(
[[WebContentVisibilityUpdateCounter alloc] init]);
[[[NSWorkspace sharedWorkspace] notificationCenter]
postNotificationName:NSWorkspaceScreensDidSleepNotification
object:nil
userInfo:nil];
EXPECT_TRUE([WebContentVisibilityUpdateCounter methodNeverCalled]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
}
// Test that we properly handle occlusion notifications from macOS.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
MacOSOcclusionNotifications) {
InitWindowA();
[window_a setOccludedForTesting:YES];
PostNotification(NSWindowDidChangeOcclusionStateNotification, window_a);
WaitForOcclusionUpdate();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
[window_a setOccludedForTesting:NO];
PostNotification(NSWindowDidChangeOcclusionStateNotification, window_a);
WaitForOcclusionUpdate();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
}
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
ManualOcclusionDetection) {
InitWindowA();
// Create a second window and place it exactly over window_a. Unlike macOS,
// our manual occlusion detection will determine window_a is occluded.
InitWindowB([window_a frame]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
// Move window_b slightly in different directions and check the occlusion
// state of window_a's web contents.
const NSSize window_offsets[] = {
{1.0, 0.0}, {-1.0, 0.0}, {0.0, 1.0}, {0.0, -1.0}};
NSRect window_b_frame = [window_b frame];
for (size_t i = 0; i < std::size(window_offsets); i++) {
// Move window b so that it no longer completely covers
// window_a's webcontents.
NSRect offset_window_frame = NSOffsetRect(
window_b_frame, window_offsets[i].width, window_offsets[i].height);
SetWindowFrame(window_b, offset_window_frame);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Move it back.
SetWindowFrame(window_b, window_b_frame);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
}
}
// Checks manual occlusion detection as windows change display order.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
ManualOcclusionDetectionOnWindowOrderChange) {
InitWindowA();
// Size and position the second window so that it exactly covers the
// first.
InitWindowB([window_a frame]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
OrderWindowFront(window_a);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
OrderWindowFront(window_b);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
}
// Checks that window_a, occluded by window_b, transitions to kVisible while the
// user resizes window_b.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
ManualOcclusionDetectionOnWindowLiveResize) {
InitWindowA();
// Size and position the second window so that it exactly covers the
// first.
InitWindowB([window_a frame]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
// Fake the start of a live resize. window_a's web contents should
// become kVisible because resizing window_b may expose whatever's
// behind it.
PostNotification(NSWindowWillStartLiveResizeNotification, window_b);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Fake the resize end, which should return window_a to kOccluded because
// it's still completely covered by window_b.
PostNotification(NSWindowDidEndLiveResizeNotification, window_b);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
}
// Checks that window_a, occluded by window_b, transitions to kVisible when
// window_b is set to close.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
ManualOcclusionDetectionOnWindowClose) {
InitWindowA();
// Size and position the second window so that it exactly covers the
// first.
InitWindowB([window_a frame]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
// Close window b.
CloseWindow(window_b);
// window_a's web contents should be kVisible, so that it's properly
// updated when window_b goes offscreen.
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
}
// Checks that window_a, occluded by window_b and window_c, remains kOccluded
// when window_b is set to close.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
ManualOcclusionDetectionOnMiddleWindowClose) {
InitWindowA();
// Size and position the second window so that it exactly covers the
// first.
InitWindowB([window_a frame]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
// Create a window_c on top of them both.
const NSRect kWindowCContentRect = NSMakeRect(0.0, 0.0, 80.0, 60.0);
base::scoped_nsobject<NSWindow> window_c(
[[WebContentsInWindow(kWindowCContentRect) window] retain]);
[window_c setTitle:@"window_c"];
// Configure it for the test.
[window_c setFrame:[window_a frame] display:NO];
OrderWindowFront(window_c);
// Close window_b.
CloseWindow(window_b);
// window_a's web contents should remain kOccluded because of window_c.
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
}
// Checks that web contents are marked kHidden on display sleep.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithDisplaySleepDetectionFeature,
OcclusionDetectionOnDisplaySleep) {
InitWindowA();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Fake a display sleep notification.
PostWorkspaceNotification(NSWorkspaceScreensDidSleepNotification);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
// Fake a display wake notification.
PostWorkspaceNotification(NSWorkspaceScreensDidWakeNotification);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
}
// Checks that occlusion updates are ignored in between fullscreen transition
// notifications.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
IgnoreOcclusionUpdatesBetweenWindowFullscreenTransitionNotifications) {
InitWindowA();
SetWindowAWebContentsVisibility(remote_cocoa::mojom::Visibility::kHidden);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
// Fake a fullscreen transition notification.
PostNotification(NSWindowWillEnterFullScreenNotification, window_a);
// Updating visibility should have no effect while in transition.
PostNotification(NSWindowDidChangeOcclusionStateNotification, window_a);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
// End the transition.
PostNotification(NSWindowDidEnterFullScreenNotification, window_a);
PostNotification(NSWindowDidChangeOcclusionStateNotification, window_a);
WaitForOcclusionUpdate();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Reset.
SetWindowAWebContentsVisibility(remote_cocoa::mojom::Visibility::kHidden);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
// Fake the exit transition start.
PostNotification(NSWindowWillExitFullScreenNotification, window_a);
// Updating visibility should have no effect while in transition.
PostNotification(NSWindowDidChangeOcclusionStateNotification, window_a);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
// End the transition.
PostNotification(NSWindowDidExitFullScreenNotification, window_a);
PostNotification(NSWindowDidChangeOcclusionStateNotification, window_a);
WaitForOcclusionUpdate();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
}
// Tests that each web contents in a window receives an updated occlusion
// state updated.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
OcclusionDetectionForMultipleWebContents) {
InitWindowA();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Create a second web contents.
const NSRect kWebContentsBFrame = NSMakeRect(0.0, 0.0, 10.0, 10.0);
WebContentsViewCocoa* web_contents_b =
[[WebContentsViewCocoaForOcclusionTesting alloc]
initWithFrame:kWebContentsBFrame];
[[window_a contentView] addSubview:web_contents_b];
WebContentsNSViewHostStub host_2;
[web_contents_b setHost:&host_2];
host_2.OnWindowVisibilityChanged(remote_cocoa::mojom::Visibility::kVisible);
const NSRect kWebContentsCFrame = NSMakeRect(0.0, 20.0, 10.0, 10.0);
WebContentsViewCocoa* web_contents_c =
[[WebContentsViewCocoaForOcclusionTesting alloc]
initWithFrame:kWebContentsCFrame];
[[window_a contentView] addSubview:web_contents_c];
WebContentsNSViewHostStub host_3;
[web_contents_c setHost:&host_3];
host_3.OnWindowVisibilityChanged(remote_cocoa::mojom::Visibility::kVisible);
// Add window_b to occlude window_a and its web contentses.
InitWindowB([window_a frame]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
EXPECT_EQ(host_2.WebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
EXPECT_EQ(host_3.WebContentsVisibility(),
remote_cocoa::mojom::Visibility::kOccluded);
// Close window b, which should expose the web contentses.
CloseWindow(window_b);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
EXPECT_EQ(host_2.WebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
EXPECT_EQ(host_3.WebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
[web_contents_b setHost:nullptr];
[web_contents_c setHost:nullptr];
}
// Checks that web contentses are marked kHidden on WebContentsViewCocoa hide.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
OcclusionDetectionOnWebContentsViewCocoaHide) {
InitWindowA();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
SetViewHidden(window_a_web_contents_view_cocoa, YES);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
SetViewHidden(window_a_web_contents_view_cocoa, NO);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Hiding the superview should have the same effect.
SetViewHidden([window_a_web_contents_view_cocoa superview], YES);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
SetViewHidden([window_a_web_contents_view_cocoa superview], NO);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Test that this direct visibility update code path works correctly.
// Previously it omitted the check for the view or its ancestor
// being hidden.
[[window_a_web_contents_view_cocoa superview] setHidden:YES];
[[NSClassFromString(@"WebContentsOcclusionCheckerMac") sharedInstance]
updateWebContentsVisibility:window_a_web_contents_view_cocoa];
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
}
// Checks that web contentses are marked kHidden on WebContentsViewCocoa removal
// from the view hierarchy.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
OcclusionDetectionOnWebContentsViewCocoaRemoveFromSuperview) {
InitWindowA();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
RemoveViewFromSuperview(window_a_web_contents_view_cocoa);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
// Adding it back should make it visible.
AddSubviewOfView(window_a_web_contents_view_cocoa, [window_a contentView]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
// Try the same with its superview.
const NSRect kTmpViewFrame = NSMakeRect(0.0, 0.0, 10.0, 10.0);
base::scoped_nsobject<NSView> tmpView(
[[NSView alloc] initWithFrame:kTmpViewFrame]);
[[window_a contentView] addSubview:tmpView];
AddSubviewOfView(tmpView, [window_a contentView]);
RemoveViewFromSuperview(window_a_web_contents_view_cocoa);
AddSubviewOfView(window_a_web_contents_view_cocoa, tmpView);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
RemoveViewFromSuperview(tmpView);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kHidden);
AddSubviewOfView(tmpView, [window_a contentView]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
}
// Checks that web contentses are marked kHidden on window miniaturize.
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
OcclusionDetectionOnWindowMiniaturize) {
InitWindowA();
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
MiniaturizeWindow(window_a);
EXPECT_TRUE([window_a isMiniaturized]);
EXPECT_NE(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
DeminiaturizeWindow(window_a);
EXPECT_FALSE([window_a isMiniaturized]);
EXPECT_EQ(WindowAWebContentsVisibility(),
remote_cocoa::mojom::Visibility::kVisible);
}
// Tests that occlusion updates only occur after a child window has been
// added to or removed from a parent. In Chrome, some webcontents visibility
// watchers add child windows (bubbles) when visibility changes. We want to
// avoid the situation where a browser component adds a child window,
// triggering a visility update, which causes a visibility watcher to add
// a second child window (while we're still inside AppKit code adding the
// first).
IN_PROC_BROWSER_TEST_F(
WindowOcclusionBrowserTestMacWithOcclusionDetectionFeature,
ChildWindowListMutationDuringManualOcclusionDetection) {
InitWindowA();
const NSRect kContentRect = NSMakeRect(0.0, 0.0, 20.0, 20.0);
WebContentsViewCocoaForOcclusionTesting* child_window_web_contents =
WindowOcclusionBrowserTestMac::WebContentsInWindow(
kContentRect, NSWindowStyleMaskBorderless);
// Clear out any pending occlusion updates from the window creation.
WaitForOcclusionUpdate();
// Add the window with the webcontents as a child. The child window coming
// onscreen will trigger a visibility update. A check inside the webcontents
// will ensure the update isn't synchronous with the window as it modifies
// its child window list.
[window_a addChildWindow:[child_window_web_contents window]
ordered:NSWindowAbove];
// The visbility update should still be pending (deferred until the next
// pass of the run loop).
EXPECT_TRUE([[NSClassFromString(@"WebContentsOcclusionCheckerMac")
sharedInstance] willUpdateWebContentsVisibility]);
WaitForOcclusionUpdate();
// Modify the child window list by removing a child window.
[window_a removeChildWindow:[child_window_web_contents window]];
}
} // namespace content