blob: 7de1cda044a3738823a3256cdedb49b778fcdcbb [file] [log] [blame]
// Copyright 2013 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/ui/ash/launcher/browser_status_monitor.h"
#include <memory>
#include "ash/public/cpp/shelf_types.h"
#include "ash/shell.h"
#include "base/macros.h"
#include "chrome/browser/ui/ash/ash_util.h"
#include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/web_applications/web_app.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/wm/public/activation_client.h"
// This class monitors the WebContent of the all tab and notifies a navigation
// to the BrowserStatusMonitor.
class BrowserStatusMonitor::LocalWebContentsObserver
: public content::WebContentsObserver {
public:
LocalWebContentsObserver(content::WebContents* contents,
BrowserStatusMonitor* monitor)
: content::WebContentsObserver(contents), monitor_(monitor) {}
~LocalWebContentsObserver() override {}
// content::WebContentsObserver
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
if (!navigation_handle->IsInMainFrame() ||
!navigation_handle->HasCommitted())
return;
ChromeLauncherController::AppState state =
ChromeLauncherController::APP_STATE_INACTIVE;
Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
// Don't assume that |browser| still exists.
if (browser) {
if (browser->window()->IsActive() &&
browser->tab_strip_model()->GetActiveWebContents() == web_contents())
state = ChromeLauncherController::APP_STATE_WINDOW_ACTIVE;
else if (browser->window()->IsActive())
state = ChromeLauncherController::APP_STATE_ACTIVE;
}
monitor_->UpdateAppItemState(web_contents(), state);
monitor_->UpdateBrowserItemState();
// Navigating may change the ShelfID associated with the WebContents.
if (browser &&
browser->tab_strip_model()->GetActiveWebContents() == web_contents()) {
monitor_->SetShelfIDForBrowserWindowContents(browser, web_contents());
}
}
void WebContentsDestroyed() override {
// We can only come here when there was a non standard termination like
// an app got un-installed while running, etc.
monitor_->WebContentsDestroyed(web_contents());
// |this| is gone now.
}
private:
BrowserStatusMonitor* monitor_;
DISALLOW_COPY_AND_ASSIGN(LocalWebContentsObserver);
};
BrowserStatusMonitor::BrowserStatusMonitor(
ChromeLauncherController* launcher_controller)
: launcher_controller_(launcher_controller),
browser_tab_strip_tracker_(this, this, this) {
DCHECK(launcher_controller_);
// TODO(crbug.com/557406): Fix this interaction pattern in Mash.
if (!ash_util::IsRunningInMash())
ash::Shell::Get()->activation_client()->AddObserver(this);
}
BrowserStatusMonitor::~BrowserStatusMonitor() {
if (!ash_util::IsRunningInMash())
ash::Shell::Get()->activation_client()->RemoveObserver(this);
browser_tab_strip_tracker_.StopObservingAndSendOnBrowserRemoved();
}
void BrowserStatusMonitor::Initialize() {
DCHECK(!initialized_);
initialized_ = true;
browser_tab_strip_tracker_.Init();
}
void BrowserStatusMonitor::UpdateAppItemState(
content::WebContents* contents,
ChromeLauncherController::AppState app_state) {
DCHECK(contents);
DCHECK(initialized_);
// It is possible to come here from Browser::SwapTabContent where the contents
// cannot be associated with a browser. A removal however should be properly
// processed.
Browser* browser = chrome::FindBrowserWithWebContents(contents);
if (app_state == ChromeLauncherController::APP_STATE_REMOVED ||
(browser && multi_user_util::IsProfileFromActiveUser(browser->profile())))
launcher_controller_->UpdateAppState(contents, app_state);
}
void BrowserStatusMonitor::UpdateBrowserItemState() {
DCHECK(initialized_);
launcher_controller_->GetBrowserShortcutLauncherItemController()
->UpdateBrowserItemState();
}
void BrowserStatusMonitor::OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
DCHECK(initialized_);
Browser* browser = NULL;
content::WebContents* contents_from_gained = NULL;
content::WebContents* contents_from_lost = NULL;
// Update active webcontents's app item state of |lost_active|, if existed.
if (lost_active) {
browser = chrome::FindBrowserWithWindow(lost_active);
if (browser)
contents_from_lost = browser->tab_strip_model()->GetActiveWebContents();
if (contents_from_lost) {
UpdateAppItemState(contents_from_lost,
ChromeLauncherController::APP_STATE_INACTIVE);
}
}
// Update active webcontents's app item state of |gained_active|, if existed.
if (gained_active) {
browser = chrome::FindBrowserWithWindow(gained_active);
if (browser)
contents_from_gained = browser->tab_strip_model()->GetActiveWebContents();
if (contents_from_gained) {
UpdateAppItemState(contents_from_gained,
ChromeLauncherController::APP_STATE_WINDOW_ACTIVE);
}
}
if (contents_from_lost || contents_from_gained)
UpdateBrowserItemState();
}
bool BrowserStatusMonitor::ShouldTrackBrowser(Browser* browser) {
return true;
}
void BrowserStatusMonitor::OnBrowserAdded(Browser* browser) {
DCHECK(initialized_);
if (browser->is_type_popup() && browser->is_app()) {
// Note: A V1 application will set the tab strip observer when the app gets
// added to the shelf. This makes sure that in the multi user case we will
// only set the observer while the app item exists in the shelf.
AddV1AppToShelf(browser);
}
}
void BrowserStatusMonitor::OnBrowserRemoved(Browser* browser) {
DCHECK(initialized_);
if (browser->is_type_popup() && browser->is_app())
RemoveV1AppFromShelf(browser);
UpdateBrowserItemState();
}
void BrowserStatusMonitor::ActiveTabChanged(content::WebContents* old_contents,
content::WebContents* new_contents,
int index,
int reason) {
Browser* browser = NULL;
// Use |new_contents|. |old_contents| could be NULL.
DCHECK(new_contents);
browser = chrome::FindBrowserWithWebContents(new_contents);
ChromeLauncherController::AppState state =
ChromeLauncherController::APP_STATE_INACTIVE;
// Update immediately on a tab change.
if (old_contents &&
(TabStripModel::kNoTab !=
browser->tab_strip_model()->GetIndexOfWebContents(old_contents)))
UpdateAppItemState(old_contents, state);
if (new_contents) {
state = browser->window()->IsActive()
? ChromeLauncherController::APP_STATE_WINDOW_ACTIVE
: ChromeLauncherController::APP_STATE_ACTIVE;
UpdateAppItemState(new_contents, state);
UpdateBrowserItemState();
SetShelfIDForBrowserWindowContents(browser, new_contents);
}
}
void BrowserStatusMonitor::TabReplacedAt(TabStripModel* tab_strip_model,
content::WebContents* old_contents,
content::WebContents* new_contents,
int index) {
DCHECK(old_contents && new_contents);
Browser* browser = chrome::FindBrowserWithWebContents(new_contents);
UpdateAppItemState(old_contents, ChromeLauncherController::APP_STATE_REMOVED);
RemoveWebContentsObserver(old_contents);
ChromeLauncherController::AppState state =
ChromeLauncherController::APP_STATE_ACTIVE;
if (browser->window()->IsActive() &&
(tab_strip_model->GetActiveWebContents() == new_contents))
state = ChromeLauncherController::APP_STATE_WINDOW_ACTIVE;
UpdateAppItemState(new_contents, state);
UpdateBrowserItemState();
if (tab_strip_model->GetActiveWebContents() == new_contents)
SetShelfIDForBrowserWindowContents(browser, new_contents);
AddWebContentsObserver(new_contents);
}
void BrowserStatusMonitor::TabInsertedAt(TabStripModel* tab_strip_model,
content::WebContents* contents,
int index,
bool foreground) {
// An inserted tab is not active - ActiveTabChanged() will be called to
// activate. We initialize therefore with |APP_STATE_INACTIVE|.
UpdateAppItemState(contents, ChromeLauncherController::APP_STATE_INACTIVE);
AddWebContentsObserver(contents);
}
void BrowserStatusMonitor::TabClosingAt(TabStripModel* tab_strip_mode,
content::WebContents* contents,
int index) {
UpdateAppItemState(contents, ChromeLauncherController::APP_STATE_REMOVED);
RemoveWebContentsObserver(contents);
}
void BrowserStatusMonitor::WebContentsDestroyed(
content::WebContents* contents) {
UpdateAppItemState(contents, ChromeLauncherController::APP_STATE_REMOVED);
RemoveWebContentsObserver(contents);
}
void BrowserStatusMonitor::AddV1AppToShelf(Browser* browser) {
DCHECK(browser->is_type_popup() && browser->is_app());
DCHECK(initialized_);
std::string app_id =
web_app::GetExtensionIdFromApplicationName(browser->app_name());
if (!app_id.empty()) {
if (!IsV1AppInShelfWithAppId(app_id))
launcher_controller_->SetV1AppStatus(app_id, ash::STATUS_RUNNING);
browser_to_app_id_map_[browser] = app_id;
}
}
void BrowserStatusMonitor::RemoveV1AppFromShelf(Browser* browser) {
DCHECK(browser->is_type_popup() && browser->is_app());
DCHECK(initialized_);
auto iter = browser_to_app_id_map_.find(browser);
if (iter != browser_to_app_id_map_.end()) {
std::string app_id = iter->second;
browser_to_app_id_map_.erase(iter);
if (!IsV1AppInShelfWithAppId(app_id))
launcher_controller_->SetV1AppStatus(app_id, ash::STATUS_CLOSED);
}
}
bool BrowserStatusMonitor::IsV1AppInShelf(Browser* browser) {
return browser_to_app_id_map_.find(browser) != browser_to_app_id_map_.end();
}
bool BrowserStatusMonitor::IsV1AppInShelfWithAppId(const std::string& app_id) {
for (const auto& iter : browser_to_app_id_map_) {
if (iter.second == app_id)
return true;
}
return false;
}
void BrowserStatusMonitor::AddWebContentsObserver(
content::WebContents* contents) {
if (webcontents_to_observer_map_.find(contents) ==
webcontents_to_observer_map_.end()) {
webcontents_to_observer_map_[contents] =
std::make_unique<LocalWebContentsObserver>(contents, this);
}
}
void BrowserStatusMonitor::RemoveWebContentsObserver(
content::WebContents* contents) {
DCHECK(webcontents_to_observer_map_.find(contents) !=
webcontents_to_observer_map_.end());
webcontents_to_observer_map_.erase(contents);
}
ash::ShelfID BrowserStatusMonitor::GetShelfIDForWebContents(
content::WebContents* contents) {
return launcher_controller_->GetShelfIDForWebContents(contents);
}
void BrowserStatusMonitor::SetShelfIDForBrowserWindowContents(
Browser* browser,
content::WebContents* web_contents) {
launcher_controller_->GetBrowserShortcutLauncherItemController()
->SetShelfIDForBrowserWindowContents(browser, web_contents);
}