blob: 180e77cf30c87c8b3128a604c076bf67d1de2e2a [file] [log] [blame]
// Copyright 2023 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/shell_dialogs/auto_close_dialog_event_handler_win.h"
#include <windows.h>
#include "base/check.h"
#include "base/memory/raw_ptr.h"
#include "base/threading/thread_checker.h"
namespace ui {
AutoCloseDialogEventHandler::AutoCloseDialogEventHandler(HWND owner_window)
: owner_window_(owner_window) {
CHECK(!instance_);
instance_ = this;
CHECK(owner_window_);
}
AutoCloseDialogEventHandler::~AutoCloseDialogEventHandler() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(instance_);
instance_ = nullptr;
if (event_hook_) {
::UnhookWinEvent(event_hook_);
}
}
HRESULT AutoCloseDialogEventHandler::Initialize(IFileDialog* file_dialog) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(!initialize_called_);
initialize_called_ = true;
CHECK(!event_hook_);
CHECK(!dialog_window_);
Microsoft::WRL::ComPtr<IOleWindow> ole_window;
HRESULT hr = file_dialog->QueryInterface(IID_PPV_ARGS(&ole_window));
if (FAILED(hr)) {
return hr;
}
HWND dialog_window;
hr = ole_window->GetWindow(&dialog_window);
if (FAILED(hr)) {
return hr;
}
// Get the process id and the thread id of the owner window to limit the
// scope of the event hook.
DWORD process_id = 0;
DWORD thread_id = ::GetWindowThreadProcessId(owner_window_, &process_id);
if (!process_id || !thread_id) {
return E_FAIL;
}
// `SetWinEventHook` is used to be notified when the owner window is closed.
// See https://devblogs.microsoft.com/oldnewthing/20111026-00/?p=9263
CHECK(!event_hook_);
event_hook_ = ::SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY,
nullptr, &EventHookCallback, process_id,
thread_id, WINEVENT_OUTOFCONTEXT);
if (!event_hook_) {
return E_FAIL;
}
dialog_window_ = dialog_window;
return S_OK;
}
void AutoCloseDialogEventHandler::OnWindowDestroyedNotification(HWND window) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Ignore unrelated notifications.
if (window != owner_window_) {
return;
}
// IFileDialog::Close() expects to be called from a IFileDialogEvents callback
// so it can't be used here. Send WM_CLOSE instead.
::PostMessage(dialog_window_, WM_CLOSE, 0, 0);
}
// static
void CALLBACK
AutoCloseDialogEventHandler::EventHookCallback(HWINEVENTHOOK handle,
DWORD event,
HWND hwnd,
LONG id_object,
LONG id_child,
DWORD event_thread,
DWORD event_time) {
CHECK(event == EVENT_OBJECT_DESTROY);
// Only care about window objects.
if (id_object != OBJID_WINDOW) {
return;
}
// This is safe thread-wise because WINEVENT_OUTOFCONTEXT guarantee that the
// hook callback will be invoked on the same thread that set the hook.
CHECK(instance_);
instance_->OnWindowDestroyedNotification(hwnd);
}
HRESULT AutoCloseDialogEventHandler::OnTypeChange(IFileDialog* file_dialog) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// OnTypeChange will be invoked multiple times during the lifecycle of the
// dialog. Only do the initialization once.
if (initialize_called_) {
return S_OK;
}
return Initialize(file_dialog);
}
HRESULT AutoCloseDialogEventHandler::OnFileOk(IFileDialog*) {
return E_NOTIMPL;
}
HRESULT AutoCloseDialogEventHandler::OnFolderChange(IFileDialog*) {
return E_NOTIMPL;
}
HRESULT AutoCloseDialogEventHandler::OnFolderChanging(IFileDialog*,
IShellItem*) {
return E_NOTIMPL;
}
HRESULT AutoCloseDialogEventHandler::OnSelectionChange(IFileDialog*) {
return E_NOTIMPL;
}
HRESULT AutoCloseDialogEventHandler::OnShareViolation(
IFileDialog*,
IShellItem*,
FDE_SHAREVIOLATION_RESPONSE*) {
return E_NOTIMPL;
}
HRESULT AutoCloseDialogEventHandler::OnOverwrite(IFileDialog*,
IShellItem*,
FDE_OVERWRITE_RESPONSE*) {
return E_NOTIMPL;
}
// static
raw_ptr<AutoCloseDialogEventHandler> AutoCloseDialogEventHandler::instance_ =
nullptr;
} // namespace ui