// Copyright 2017 The Chromium Authors
// 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>
#include <memory>
#include "base/memory/weak_ptr.h"
namespace ui {
struct BubbleCloser::ObjCStorage {
id __strong event_tap;
};
BubbleCloser::BubbleCloser(NSWindow* window,
base::RepeatingClosure on_click_outside)
: on_click_outside_(std::move(on_click_outside)),
objc_storage_(std::make_unique<ObjCStorage>()) {
// Capture a WeakPtr. This allows the block to detect another event monitor
// for the same event deleting |this|.
base::WeakPtr<BubbleCloser> weak_ptr = factory_.GetWeakPtr();
// Note that |window| will be retained when captured by the block below.
auto block = ^NSEvent*(NSEvent* event) {
NSWindow* event_window = event.window;
if (event_window.sheet) {
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 (weak_ptr) {
weak_ptr->OnClickOutside();
}
return event;
};
objc_storage_->event_tap =
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown |
NSEventMaskRightMouseDown
handler:block];
}
BubbleCloser::~BubbleCloser() {
[NSEvent removeMonitor:objc_storage_->event_tap];
}
void BubbleCloser::OnClickOutside() {
on_click_outside_.Run(); // Note: May delete |this|.
}
} // namespace ui