Commit b36af4e1 authored by Robert Liao's avatar Robert Liao Committed by Commit Bot

Add Child Move and 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. Move is brought along for free.

BUG=728174

Change-Id: I7ca12b92da339263ca5558254d2bbb0595a57b4c
Reviewed-on: https://chromium-review.googlesource.com/915010
Commit-Queue: Robert Liao <robliao@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#536753}
parent 2304254b
......@@ -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 |name| on the bubble parent window with a block to call
// RepositionFromAnchor().
void ObserveParentWindow(NSString* name);
// Observe |name| on the bubble window with a block to call
// RecalculateAnchor();
void ObserveBubbleWindow(NSString* name);
// Use |callback| to observe |name| on |window|.
void ObserveWindow(NSWindow* window,
NSString* name,
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,49 @@ 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];
RecalculateAnchor();
// 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);
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);
ObserveBubbleWindow(NSWindowDidMoveNotification);
}
void BubbleAnchorHelper::ObserveParentWindow(NSString* name) {
ObserveWindow([bubble_->parent_window() window], name,
base::BindRepeating(&BubbleAnchorHelper::RepositionFromAnchor,
base::Unretained(this)));
}
void BubbleAnchorHelper::Observe(NSString* name) {
void BubbleAnchorHelper::ObserveBubbleWindow(NSString* name) {
ObserveWindow(bubble_->GetWidget()->GetNativeWindow(), name,
base::BindRepeating(&BubbleAnchorHelper::RecalculateAnchor,
base::Unretained(this)));
}
void BubbleAnchorHelper::ObserveWindow(NSWindow* window,
NSString* name,
base::RepeatingClosure callback) {
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
id token = [center addObserverForName:name
object:[bubble_->parent_window() window]
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 +170,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,86 @@ 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.
}
// Test that KeepBubbleAnchored(..) actually keeps the bubble anchored upon
// moving the child window.
TEST_F(BubbleAnchorHelperViewsTest, AnchoringChildMove) {
// 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]));
// Move the bubble.
NSRect child_frame = [child frame];
child_frame = NSOffsetRect(child_frame, -10, -20);
[child setFrame:child_frame display:YES animate:NO];
// Verify the new anchor.
EXPECT_EQ(kHorizOffset - 10, NSMaxX([child frame]));
EXPECT_EQ(kVertOffset - 20, 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 - 10 + 50, NSMaxX([child frame]));
EXPECT_EQ(kVertOffset - 20 + 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