// 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 #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_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 keys = extensions::tabs_constants; 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(keys::kWindowTypesKey, &filter_value)) return window_controller->IsVisibleToExtension(extension); // Otherwise it's based on the type filter. WindowController::TypeFilter filter = WindowController::GetFilterFromWindowTypesValues(filter_value); return window_controller->MatchesFilter(filter); } 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(keys::kWindowTypesKey); // 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.SetWindowType(window_controller->GetWindowTypeText()); else event->filter_info.SetWindowExposedByDefault( window_controller->IsVisibleToExtension(extension)); 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(keys::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.SetWindowType( window_controller ? window_controller->GetWindowTypeText() : keys::kWindowTypeValueNormal); else event->filter_info.SetWindowExposedByDefault(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->Append( new base::FundamentalValue(extension_misc::kUnknownWindowId)); } else { event->event_args->Clear(); event->event_args->Append(new base::FundamentalValue(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(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; scoped_ptr args(new base::ListValue()); base::DictionaryValue* window_dictionary = window_controller->CreateWindowValue(); args->Append(window_dictionary); 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; int window_id = window_controller->GetWindowId(); scoped_ptr args(new base::ListValue()); args->Append(new base::FundamentalValue(window_id)); DispatchEvent(events::WINDOWS_ON_REMOVED, windows::OnRemoved::kEventName, window_controller, std::move(args)); } #if !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) if (chrome::NOTIFICATION_NO_KEY_WINDOW == type) { OnActiveWindowChanged(nullptr); return; } #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; scoped_ptr event(new Event(events::WINDOWS_ON_FOCUS_CHANGED, windows::OnFocusChanged::kEventName, make_scoped_ptr(new base::ListValue()))); event->will_dispatch_callback = base::Bind(&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, scoped_ptr args) { scoped_ptr event( new Event(histogram_value, event_name, std::move(args))); event->restrict_to_browser_context = window_controller->profile(); event->will_dispatch_callback = base::Bind(&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) { scoped_ptr controller(new AppWindowController( app_window, make_scoped_ptr(new AppBaseWindow(app_window)), profile_)); app_windows_[app_window->session_id().id()] = std::move(controller); } } // namespace extensions