Commit 7dfb9b6d authored by Robert Liao's avatar Robert Liao Committed by Commit Bot

Add Child Resize Support to the BubbleAnchorHelper

The ExtensionPopup resizes its view dynamically after it can be
anchored and shown.

This change adds child resize support to BubbleAnchorHelper to
accommodate that scenario.

BUG=728174

Change-Id: I4422e5b1e0410ef6bed1ec7d1c77961cef061ced
Reviewed-on: https://chromium-review.googlesource.com/922785
Commit-Queue: Robert Liao <robliao@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537408}
parent 903cf4d0
......@@ -6,6 +6,7 @@
#import <Cocoa/Cocoa.h>
#include "base/bind.h"
#import "base/mac/scoped_nsobject.h"
#import "chrome/browser/ui/cocoa/browser_window_controller.h"
#import "chrome/browser/ui/cocoa/location_bar/location_bar_decoration.h"
......@@ -31,8 +32,18 @@ class BubbleAnchorHelper final : public views::WidgetObserver {
AnchorType type);
private:
// Observe |name| on the bubble parent window with a block to call ReAnchor().
void Observe(NSString* name);
// Observe |notification| on the bubble parent window with a block to call
// RepositionFromAnchor().
void ObserveParentWindow(NSString* notification);
// Observe |notification| on the bubble window with a block to call
// RecalculateAnchor();
void ObserveBubbleWindow(NSString* notification);
// Use |callback| to observe |notification| on |window|.
void ObserveWindow(NSWindow* window,
NSString* notification,
base::RepeatingClosure callback);
// Whether offset from the left of the parent window is fixed.
bool IsMinXFixed() const {
......@@ -41,7 +52,10 @@ class BubbleAnchorHelper final : public views::WidgetObserver {
// Re-positions |bubble_| so that the offset to the parent window at
// construction time is preserved.
void ReAnchor();
void RepositionFromAnchor();
// Recalculates the offsets for |bubble_|.
void RecalculateAnchor();
// WidgetObserver:
void OnWidgetDestroying(views::Widget* widget) override;
......@@ -101,40 +115,54 @@ BubbleAnchorHelper::BubbleAnchorHelper(views::BubbleDialogDelegateView* bubble,
if (type == IGNORE_PARENT)
return;
NSRect parent_frame = [[bubble->parent_window() window] frame];
NSRect bubble_frame = [bubble->GetWidget()->GetNativeWindow() frame];
// Note: when anchored on the right, this doesn't support changes to the
// bubble size, just the parent size.
horizontal_offset_ =
(IsMinXFixed() ? NSMinX(parent_frame) : NSMaxX(parent_frame)) -
NSMinX(bubble_frame);
vertical_offset_ = NSMaxY(parent_frame) - NSMinY(bubble_frame);
RecalculateAnchor();
Observe(NSWindowDidEnterFullScreenNotification);
Observe(NSWindowDidExitFullScreenNotification);
Observe(NSWindowDidResizeNotification);
ObserveParentWindow(NSWindowDidEnterFullScreenNotification);
ObserveParentWindow(NSWindowDidExitFullScreenNotification);
ObserveParentWindow(NSWindowDidResizeNotification);
// Also monitor move. Note that for user-initiated window moves this is not
// necessary: the bubble's child window status keeps the position pinned to
// the parent during the move (which is handy since AppKit doesn't send out
// notifications until the move completes). Programmatic -[NSWindow
// setFrame:..] calls, however, do not update child window positions for us.
Observe(NSWindowDidMoveNotification);
ObserveParentWindow(NSWindowDidMoveNotification);
ObserveBubbleWindow(NSWindowDidResizeNotification);
// Don't ObserveBubbleWindow(NSWindowDidMoveNotification) unless it is
// possible to distinguish between when BubbleAnchorHelper sets the position
// of the bubble and when the existing NSWindow machinery sets the position of
// the bubble. The NSWindow adjustments only occur if |bubble| is an actual
// child of bubble->parent_window(). As of High Sierra, the BubbleAnchorHelper
// and NSWindow disagree on the final positioning.
}
void BubbleAnchorHelper::ObserveParentWindow(NSString* notification) {
ObserveWindow([bubble_->parent_window() window], notification,
base::BindRepeating(&BubbleAnchorHelper::RepositionFromAnchor,
base::Unretained(this)));
}
void BubbleAnchorHelper::Observe(NSString* name) {
void BubbleAnchorHelper::ObserveBubbleWindow(NSString* notification) {
ObserveWindow(bubble_->GetWidget()->GetNativeWindow(), notification,
base::BindRepeating(&BubbleAnchorHelper::RecalculateAnchor,
base::Unretained(this)));
}
void BubbleAnchorHelper::ObserveWindow(NSWindow* window,
NSString* notification,
base::RepeatingClosure callback) {
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
id token = [center addObserverForName:name
object:[bubble_->parent_window() window]
id token = [center addObserverForName:notification
object:window
queue:nil
usingBlock:^(NSNotification* notification) {
ReAnchor();
callback.Run();
}];
[observer_tokens_ addObject:token];
}
void BubbleAnchorHelper::ReAnchor() {
void BubbleAnchorHelper::RepositionFromAnchor() {
NSRect bubble_frame = [bubble_->GetWidget()->GetNativeWindow() frame];
NSRect parent_frame = [[bubble_->parent_window() window] frame];
if (IsMinXFixed())
......@@ -147,6 +175,15 @@ void BubbleAnchorHelper::ReAnchor() {
animate:NO];
}
void BubbleAnchorHelper::RecalculateAnchor() {
NSRect parent_frame = [[bubble_->parent_window() window] frame];
NSRect bubble_frame = [bubble_->GetWidget()->GetNativeWindow() frame];
horizontal_offset_ =
(IsMinXFixed() ? NSMinX(parent_frame) : NSMaxX(parent_frame)) -
NSMinX(bubble_frame);
vertical_offset_ = NSMaxY(parent_frame) - NSMinY(bubble_frame);
}
void BubbleAnchorHelper::OnWidgetDestroying(views::Widget* widget) {
// NativeWidgetMac guarantees that a child's OnWidgetDestroying() is invoked
// before the parent NSWindow has closed.
......
......@@ -121,3 +121,46 @@ TEST_F(BubbleAnchorHelperViewsTest, AnchoringFixed) {
[parent close]; // Takes |child| with it.
}
// Test that KeepBubbleAnchored(..) actually keeps the bubble anchored upon
// resizing the child window.
TEST_F(BubbleAnchorHelperViewsTest, AnchoringChildResize) {
// Use MD anchoring since the arithmetic is simpler (no arrows).
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kSecondaryUiMd);
NSRect parent_frame = NSMakeRect(100, 200, 300, 400);
// Released when closed.
NSWindow* parent =
[[NSWindow alloc] initWithContentRect:parent_frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[parent makeKeyAndOrderFront:nil];
NSWindow* child = ShowAnchoredBubble(parent, views::BubbleBorder::TOP_RIGHT);
// Anchored TOP_RIGHT, so Max X/Y should be anchored.
ASSERT_EQ(kHorizOffset, NSMaxX([child frame]));
ASSERT_EQ(kVertOffset, NSMaxY([child frame]));
// Resize the bubble and maintain the old anchor position.
NSRect child_frame = [child frame];
child_frame.origin.x -= 20;
child_frame.size.width += 20;
child_frame.origin.y -= 30;
child_frame.size.height += 30;
[child setFrame:child_frame display:YES animate:NO];
// Verify the anchor is still the same.
EXPECT_EQ(kHorizOffset, NSMaxX([child frame]));
EXPECT_EQ(kVertOffset, NSMaxY([child frame]));
// Move the parent window and verify the bubble is still anchored.
parent_frame = NSOffsetRect(parent_frame, 50, 60);
[parent setFrame:parent_frame display:YES animate:NO];
EXPECT_EQ(kHorizOffset + 50, NSMaxX([child frame]));
EXPECT_EQ(kVertOffset + 60, NSMaxY([child frame]));
[parent close]; // Takes |child| with it.
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment