blob: 8587e504fd22979043a41eacbf7eda73d5ffad6f [file] [log] [blame]
// Copyright 2017 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.
#include "ui/base/cocoa/bubble_closer.h"
#import <AppKit/AppKit.h>
namespace ui {
BubbleCloser::BubbleCloser(NSWindow* window,
base::RepeatingClosure on_click_outside)
: on_click_outside_(std::move(on_click_outside)), factory_(this) {
// Capture a WeakPtr via NSObject. This allows the block to detect another
// event monitor for the same event deleting |this|.
WeakPtrNSObject* handle = factory_.handle();
// Note that |window| will be retained when captured by the block below.
// |this| is captured, but not retained.
auto block = ^NSEvent*(NSEvent* event) {
NSWindow* event_window = [event window];
if ([event_window isSheet])
return event;
// Do not close the bubble if the event happened on a window with a
// higher level. For example, the content of a browser action bubble
// opens a calendar picker window with NSPopUpMenuWindowLevel, and a
// date selection closes the picker window, but it should not close
// the bubble.
if ([event_window level] > [window level])
return event;
// If the event is in |window|'s hierarchy, do not close the bubble.
NSWindow* ancestor = event_window;
while (ancestor) {
if (ancestor == window)
return event;
ancestor = [ancestor parentWindow];
}
if (BubbleCloser* owner = WeakPtrNSObjectFactory<BubbleCloser>::Get(handle))
owner->OnClickOutside();
return event;
};
event_tap_ =
[NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask |
NSRightMouseDownMask
handler:block];
}
BubbleCloser::~BubbleCloser() {
[NSEvent removeMonitor:event_tap_];
}
void BubbleCloser::OnClickOutside() {
on_click_outside_.Run(); // Note: May delete |this|.
}
} // namespace ui