blob: 9fa98610bbca7ea416c23a57bba973c81040fdee [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/extensions/api/tabs/windows_event_router.h"
#include <utility>
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/tabs/app_base_window.h"
#include "chrome/browser/extensions/api/tabs/app_window_controller.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/api/tabs/windows_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/window_controller.h"
#include "chrome/browser/extensions/window_controller_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/windows.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/public/browser/notification_service.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/event_router.h"
#include "extensions/common/constants.h"
using content::BrowserContext;
namespace extensions {
namespace windows = extensions::api::windows;
namespace {
bool ControllerVisibleToListener(WindowController* window_controller,
const Extension* extension,
const base::DictionaryValue* listener_filter) {
if (!window_controller)
return false;
// If there is no filter the visibility is based on the extension.
const base::ListValue* filter_value = nullptr;
if (listener_filter)
listener_filter->GetList(extensions::tabs_constants::kWindowTypesKey,
&filter_value);
// TODO(https://crbug.com/807313): Remove this.
bool allow_dev_tools_windows = !!filter_value;
if (!window_controller->IsVisibleToTabsAPIForExtension(
extension, allow_dev_tools_windows)) {
return false;
}
return !filter_value ||
window_controller->MatchesFilter(
WindowController::GetFilterFromWindowTypesValues(filter_value));
}
bool WillDispatchWindowEvent(WindowController* window_controller,
BrowserContext* context,
const Extension* extension,
Event* event,
const base::DictionaryValue* listener_filter) {
bool has_filter =
listener_filter &&
listener_filter->HasKey(extensions::tabs_constants::kWindowTypesKey);
// TODO(https://crbug.com/807313): Remove this.
bool allow_dev_tools_windows = has_filter;
if (!window_controller->IsVisibleToTabsAPIForExtension(
extension, allow_dev_tools_windows)) {
return false;
}
// Cleanup previous values.
event->filter_info = EventFilteringInfo();
// Only set the window type if the listener has set a filter.
// Otherwise we set the window visibility relative to the extension.
if (has_filter) {
event->filter_info.window_type = window_controller->GetWindowTypeText();
} else {
event->filter_info.window_exposed_by_default = true;
}
return true;
}
bool WillDispatchWindowFocusedEvent(
WindowController* window_controller,
BrowserContext* context,
const Extension* extension,
Event* event,
const base::DictionaryValue* listener_filter) {
int window_id = extension_misc::kUnknownWindowId;
Profile* new_active_context = nullptr;
bool has_filter =
listener_filter &&
listener_filter->HasKey(extensions::tabs_constants::kWindowTypesKey);
// We might not have a window controller if the focus moves away
// from chromium's windows.
if (window_controller) {
window_id = window_controller->GetWindowId();
new_active_context = window_controller->profile();
}
// Cleanup previous values.
event->filter_info = EventFilteringInfo();
// Only set the window type if the listener has set a filter,
// otherwise set the visibility to true (if the window is not
// supposed to be visible by the extension, we will clear out the
// window id later).
if (has_filter) {
event->filter_info.window_type =
window_controller ? window_controller->GetWindowTypeText()
: extensions::tabs_constants::kWindowTypeValueNormal;
} else {
event->filter_info.window_exposed_by_default = true;
}
// When switching between windows in the default and incognito profiles,
// dispatch WINDOW_ID_NONE to extensions whose profile lost focus that
// can't see the new focused window across the incognito boundary.
// See crbug.com/46610.
bool cant_cross_incognito = new_active_context &&
new_active_context != context &&
!util::CanCrossIncognito(extension, context);
// If the window is not visible by the listener, we also need to
// clear out the window id from the event.
bool visible_to_listener = ControllerVisibleToListener(
window_controller, extension, listener_filter);
if (cant_cross_incognito || !visible_to_listener) {
event->event_args->Clear();
event->event_args->AppendInteger(extension_misc::kUnknownWindowId);
} else {
event->event_args->Clear();
event->event_args->AppendInteger(window_id);
}
return true;
}
} // namespace
WindowsEventRouter::WindowsEventRouter(Profile* profile)
: profile_(profile),
focused_profile_(nullptr),
focused_window_id_(extension_misc::kUnknownWindowId),
observed_app_registry_(this),
observed_controller_list_(this) {
DCHECK(!profile->IsOffTheRecord());
observed_app_registry_.Add(AppWindowRegistry::Get(profile_));
observed_controller_list_.Add(WindowControllerList::GetInstance());
// Needed for when no suitable window can be passed to an extension as the
// currently focused window. On Mac (even in a toolkit-views build) always
// rely on the notification sent by AppControllerMac after AppKit sends
// NSWindowDidBecomeKeyNotification and there is no [NSApp keyWindo7w]. This
// allows windows not created by toolkit-views to be tracked.
// TODO(tapted): Remove the ifdefs (and NOTIFICATION_NO_KEY_WINDOW) when
// Chrome on Mac only makes windows with toolkit-views.
#if defined(OS_MACOSX)
registrar_.Add(this, chrome::NOTIFICATION_NO_KEY_WINDOW,
content::NotificationService::AllSources());
#elif defined(TOOLKIT_VIEWS)
views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this);
#else
#error Unsupported
#endif
AppWindowRegistry* registry = AppWindowRegistry::Get(profile_);
for (AppWindow* app_window : registry->app_windows())
AddAppWindow(app_window);
}
WindowsEventRouter::~WindowsEventRouter() {
#if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX)
views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this);
#endif
}
void WindowsEventRouter::OnAppWindowAdded(extensions::AppWindow* app_window) {
if (!profile_->IsSameProfile(
Profile::FromBrowserContext(app_window->browser_context())))
return;
AddAppWindow(app_window);
}
void WindowsEventRouter::OnAppWindowRemoved(extensions::AppWindow* app_window) {
if (!profile_->IsSameProfile(
Profile::FromBrowserContext(app_window->browser_context())))
return;
app_windows_.erase(app_window->session_id().id());
}
void WindowsEventRouter::OnAppWindowActivated(
extensions::AppWindow* app_window) {
AppWindowMap::const_iterator iter =
app_windows_.find(app_window->session_id().id());
OnActiveWindowChanged(iter != app_windows_.end() ? iter->second.get()
: nullptr);
}
void WindowsEventRouter::OnWindowControllerAdded(
WindowController* window_controller) {
if (!HasEventListener(windows::OnCreated::kEventName))
return;
if (!profile_->IsSameProfile(window_controller->profile()))
return;
// Ignore any windows without an associated browser (e.g., AppWindows).
if (!window_controller->GetBrowser())
return;
std::unique_ptr<base::ListValue> args(new base::ListValue());
args->Append(ExtensionTabUtil::CreateWindowValueForExtension(
*window_controller->GetBrowser(), nullptr,
ExtensionTabUtil::kDontPopulateTabs));
DispatchEvent(events::WINDOWS_ON_CREATED, windows::OnCreated::kEventName,
window_controller, std::move(args));
}
void WindowsEventRouter::OnWindowControllerRemoved(
WindowController* window_controller) {
if (!HasEventListener(windows::OnRemoved::kEventName))
return;
if (!profile_->IsSameProfile(window_controller->profile()))
return;
// Ignore any windows without an associated browser (e.g., AppWindows).
if (!window_controller->GetBrowser())
return;
int window_id = window_controller->GetWindowId();
std::unique_ptr<base::ListValue> args(new base::ListValue());
args->AppendInteger(window_id);
DispatchEvent(events::WINDOWS_ON_REMOVED, windows::OnRemoved::kEventName,
window_controller, std::move(args));
}
#if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX)
void WindowsEventRouter::OnNativeFocusChanged(gfx::NativeView focused_now) {
if (!focused_now)
OnActiveWindowChanged(nullptr);
}
#endif
void WindowsEventRouter::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
#if defined(OS_MACOSX)
DCHECK_EQ(chrome::NOTIFICATION_NO_KEY_WINDOW, type);
OnActiveWindowChanged(nullptr);
#endif
}
void WindowsEventRouter::OnActiveWindowChanged(
WindowController* window_controller) {
Profile* window_profile = nullptr;
int window_id = extension_misc::kUnknownWindowId;
if (window_controller &&
profile_->IsSameProfile(window_controller->profile())) {
window_profile = window_controller->profile();
window_id = window_controller->GetWindowId();
}
if (focused_window_id_ == window_id)
return;
// window_profile is either the default profile for the active window, its
// incognito profile, or nullptr if the previous profile is losing focus.
focused_profile_ = window_profile;
focused_window_id_ = window_id;
if (!HasEventListener(windows::OnFocusChanged::kEventName))
return;
std::unique_ptr<Event> event = std::make_unique<Event>(
events::WINDOWS_ON_FOCUS_CHANGED, windows::OnFocusChanged::kEventName,
std::make_unique<base::ListValue>());
event->will_dispatch_callback =
base::BindRepeating(&WillDispatchWindowFocusedEvent, window_controller);
EventRouter::Get(profile_)->BroadcastEvent(std::move(event));
}
void WindowsEventRouter::DispatchEvent(events::HistogramValue histogram_value,
const std::string& event_name,
WindowController* window_controller,
std::unique_ptr<base::ListValue> args) {
auto event =
std::make_unique<Event>(histogram_value, event_name, std::move(args),
window_controller->profile());
event->will_dispatch_callback =
base::BindRepeating(&WillDispatchWindowEvent, window_controller);
EventRouter::Get(profile_)->BroadcastEvent(std::move(event));
}
bool WindowsEventRouter::HasEventListener(const std::string& event_name) {
return EventRouter::Get(profile_)->HasEventListener(event_name);
}
void WindowsEventRouter::AddAppWindow(extensions::AppWindow* app_window) {
std::unique_ptr<AppWindowController> controller(new AppWindowController(
app_window, std::make_unique<AppBaseWindow>(app_window), profile_));
app_windows_[app_window->session_id().id()] = std::move(controller);
}
} // namespace extensions