blob: ca5bb784fc3685b8bdef92ff90a31f3ada3a270c [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// 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/tabs_event_router.h"
#include <stddef.h>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/api/tabs/tabs_windows_api.h"
#include "chrome/browser/extensions/api/tabs/windows_event_router.h"
#include "chrome/browser/extensions/browser_extension_window_controller.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/lifecycle_unit.h"
#include "chrome/browser/resource_coordinator/lifecycle_unit_state.mojom-shared.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h"
#include "chrome/browser/resource_coordinator/utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/recently_audible_helper.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/common/extensions/extension_constants.h"
#include "components/favicon/content/content_favicon_driver.h"
#include "components/performance_manager/public/decorators/page_live_state_decorator.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/tabs/public/tab_group.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "ui/gfx/range/range.h"
using base::Value;
using content::WebContents;
using zoom::ZoomController;
namespace extensions {
namespace {
constexpr char kFromIndexKey[] = "fromIndex";
constexpr char kGroupIdKey[] = "groupId";
constexpr char kSplitIdKey[] = "splitViewId";
constexpr char kNewPositionKey[] = "newPosition";
constexpr char kNewWindowIdKey[] = "newWindowId";
constexpr char kOldPositionKey[] = "oldPosition";
constexpr char kOldWindowIdKey[] = "oldWindowId";
constexpr char kPinnedKey[] = "pinned";
constexpr char kAudibleKey[] = "audible";
constexpr char kFrozenKey[] = "frozen";
constexpr char kDiscardedKey[] = "discarded";
constexpr char kAutoDiscardableKey[] = "autoDiscardable";
constexpr char kMutedInfoKey[] = "mutedInfo";
constexpr char kTabIdKey[] = "tabId";
constexpr char kTabIdsKey[] = "tabIds";
constexpr char kToIndexKey[] = "toIndex";
bool WillDispatchTabUpdatedEvent(
WebContents* contents,
const std::set<std::string>& changed_property_names,
content::BrowserContext* browser_context,
mojom::ContextType target_context,
const Extension* extension,
const base::Value::Dict* listener_filter,
std::optional<base::Value::List>& event_args_out,
mojom::EventFilteringInfoPtr& event_filtering_info_out) {
ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
ExtensionTabUtil::GetScrubTabBehavior(extension, target_context,
contents);
api::tabs::Tab tab_object = ExtensionTabUtil::CreateTabObject(
contents, scrub_tab_behavior, extension);
base::Value::Dict tab_value = tab_object.ToValue();
base::Value::Dict changed_properties;
for (const auto& property : changed_property_names) {
if (const base::Value* value = tab_value.Find(property))
changed_properties.Set(property, value->Clone());
}
event_args_out.emplace();
event_args_out->Append(ExtensionTabUtil::GetTabId(contents));
event_args_out->Append(std::move(changed_properties));
event_args_out->Append(std::move(tab_value));
return true;
}
bool WillDispatchTabCreatedEvent(
WebContents* contents,
bool active,
content::BrowserContext* browser_context,
mojom::ContextType target_context,
const Extension* extension,
const base::Value::Dict* listener_filter,
std::optional<base::Value::List>& event_args_out,
mojom::EventFilteringInfoPtr& event_filtering_info_out) {
ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
ExtensionTabUtil::GetScrubTabBehavior(extension, target_context,
contents);
base::Value::Dict tab_value =
ExtensionTabUtil::CreateTabObject(contents, scrub_tab_behavior, extension)
.ToValue();
tab_value.Set(tabs_constants::kSelectedKey, active);
tab_value.Set(tabs_constants::kActiveKey, active);
event_args_out.emplace();
event_args_out->Append(std::move(tab_value));
return true;
}
} // namespace
TabsEventRouter::TabEntry::TabEntry(TabsEventRouter* router,
content::WebContents* contents)
: WebContentsObserver(contents),
complete_waiting_on_load_(false),
was_audible_(false),
was_muted_(contents->IsAudioMuted()),
router_(router) {
auto* audible_helper = RecentlyAudibleHelper::FromWebContents(contents);
was_audible_ = audible_helper->WasRecentlyAudible();
}
std::set<std::string> TabsEventRouter::TabEntry::UpdateLoadState() {
// The tab may go in & out of loading (for instance if iframes navigate).
// We only want to respond to the first change from loading to !loading after
// the NavigationEntryCommitted() was fired.
if (!complete_waiting_on_load_ || web_contents()->IsLoading()) {
return std::set<std::string>();
}
// Send 'status' of tab change. Expecting 'complete' is fired.
complete_waiting_on_load_ = false;
std::set<std::string> changed_property_names;
changed_property_names.insert(tabs_constants::kStatusKey);
return changed_property_names;
}
bool TabsEventRouter::TabEntry::SetAudible(bool new_val) {
if (was_audible_ == new_val)
return false;
was_audible_ = new_val;
return true;
}
bool TabsEventRouter::TabEntry::SetMuted(bool new_val) {
if (was_muted_ == new_val)
return false;
was_muted_ = new_val;
return true;
}
void TabsEventRouter::TabEntry::NavigationEntryCommitted(
const content::LoadCommittedDetails& load_details) {
// Send 'status' of tab change. Expecting 'loading' is fired.
complete_waiting_on_load_ = true;
std::set<std::string> changed_property_names;
changed_property_names.insert(tabs_constants::kStatusKey);
if (web_contents()->GetURL() != url_) {
url_ = web_contents()->GetURL();
changed_property_names.insert(tabs_constants::kUrlKey);
}
router_->TabUpdated(this, std::move(changed_property_names));
}
void TabsEventRouter::TabEntry::TitleWasSet(content::NavigationEntry* entry) {
std::set<std::string> changed_property_names;
changed_property_names.insert(tabs_constants::kTitleKey);
router_->TabUpdated(this, std::move(changed_property_names));
}
void TabsEventRouter::TabEntry::WebContentsDestroyed() {
// This is necessary because it's possible for tabs to be created, detached
// and then destroyed without ever having been re-attached and closed. This
// happens in the case of a devtools WebContents that is opened in window,
// docked, then closed.
// Warning: |this| will be deleted after this call.
router_->UnregisterForTabNotifications(web_contents());
}
TabsEventRouter::TabsEventRouter(Profile* profile)
: profile_(profile), browser_tab_strip_tracker_(this, this) {
DCHECK(!profile->IsOffTheRecord());
BrowserList::AddObserver(this);
browser_tab_strip_tracker_.Init();
tab_source_scoped_observation_.Observe(
resource_coordinator::GetTabLifecycleUnitSource());
performance_manager::PageLiveStateDecorator::AddAllPageObserver(this);
}
TabsEventRouter::~TabsEventRouter() {
performance_manager::PageLiveStateDecorator::RemoveAllPageObserver(this);
BrowserList::RemoveObserver(this);
}
bool TabsEventRouter::ShouldTrackBrowser(BrowserWindowInterface* browser) {
return profile_->IsSameOrParent(browser->GetProfile()) &&
ExtensionTabUtil::BrowserSupportsTabs(
browser->GetBrowserForMigrationOnly());
}
void TabsEventRouter::OnBrowserSetLastActive(Browser* browser) {
TabsWindowsAPI* tabs_window_api = TabsWindowsAPI::Get(profile_);
if (tabs_window_api) {
tabs_window_api->windows_event_router()->OnActiveWindowChanged(
browser ? BrowserExtensionWindowController::From(browser) : nullptr);
}
}
void TabsEventRouter::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
switch (change.type()) {
case TabStripModelChange::kInserted: {
for (const auto& contents : change.GetInsert()->contents) {
DispatchTabInsertedAt(tab_strip_model, contents.contents,
contents.index,
selection.new_contents == contents.contents);
}
break;
}
case TabStripModelChange::kRemoved: {
for (const auto& contents : change.GetRemove()->contents) {
if (contents.remove_reason ==
TabStripModelChange::RemoveReason::kDeleted) {
DispatchTabClosingAt(tab_strip_model, contents.contents,
contents.index);
}
DispatchTabDetachedAt(contents.contents, contents.index,
selection.old_contents == contents.contents);
}
break;
}
case TabStripModelChange::kMoved: {
auto* move = change.GetMove();
DispatchTabMoved(move->contents, move->from_index, move->to_index);
break;
}
case TabStripModelChange::kReplaced: {
auto* replace = change.GetReplace();
DispatchTabReplacedAt(replace->old_contents, replace->new_contents,
replace->index);
break;
}
case TabStripModelChange::kSelectionOnly:
break;
}
if (tab_strip_model->empty())
return;
if (selection.active_tab_changed())
DispatchActiveTabChanged(selection.old_contents, selection.new_contents);
if (selection.selection_changed()) {
DispatchTabSelectionChanged(tab_strip_model, selection.old_model);
}
}
void TabsEventRouter::TabChangedAt(WebContents* contents,
int index,
TabChangeType change_type) {
TabEntry* entry = GetTabEntry(contents);
// TabClosingAt() may have already removed the entry for |contents| even
// though the tab has not yet been detached.
if (entry)
TabUpdated(entry, entry->UpdateLoadState());
}
void TabsEventRouter::TabPinnedStateChanged(TabStripModel* tab_strip_model,
WebContents* contents,
int index) {
std::set<std::string> changed_property_names;
changed_property_names.insert(kPinnedKey);
DispatchTabUpdatedEvent(contents, std::move(changed_property_names));
}
void TabsEventRouter::OnTabGroupChanged(const TabGroupChange& change) {
// Maintain the previous tabstrip observation call sequence for extension so
// that it does not cause a breaking change for clients during detaching and
// re-inserting tab groups.
if (change.type == TabGroupChange::kCreated &&
change.GetCreateChange()->reason() ==
TabGroupChange::TabGroupCreationReason::
kInsertedFromAnotherTabstrip) {
for (tabs::TabInterface* tab :
change.GetCreateChange()->GetDetachedTabs()) {
std::set<std::string> changed_property_names;
changed_property_names.insert(kGroupIdKey);
DispatchTabUpdatedEvent(tab->GetContents(),
std::move(changed_property_names));
}
} else if (change.type == TabGroupChange::kClosed &&
change.GetCloseChange()->reason() ==
TabGroupChange::TabGroupClosureReason::
kDetachedToAnotherTabstrip) {
for (tabs::TabInterface* tab : change.GetCloseChange()->GetDetachedTabs()) {
std::set<std::string> changed_property_names;
changed_property_names.insert(kGroupIdKey);
DispatchTabUpdatedEvent(tab->GetContents(),
std::move(changed_property_names));
}
}
}
void TabsEventRouter::OnSplitTabChanged(const SplitTabChange& change) {
if (change.type == SplitTabChange::Type::kAdded &&
change.GetAddedChange()->reason() !=
SplitTabChange::SplitTabAddReason::kInsertedFromAnotherTabstrip) {
for (const std::pair<tabs::TabInterface*, int>& tab :
change.GetAddedChange()->tabs()) {
std::set<std::string> changed_property_names;
changed_property_names.insert(kSplitIdKey);
DispatchTabUpdatedEvent(tab.first->GetContents(),
std::move(changed_property_names));
}
}
if (change.type == SplitTabChange::Type::kRemoved &&
change.GetRemovedChange()->reason() !=
SplitTabChange::SplitTabRemoveReason::kDetachedToAnotherTabstrip) {
for (const std::pair<tabs::TabInterface*, int>& tab :
change.GetRemovedChange()->tabs()) {
std::set<std::string> changed_property_names;
changed_property_names.insert(kSplitIdKey);
DispatchTabUpdatedEvent(tab.first->GetContents(),
std::move(changed_property_names));
}
}
}
void TabsEventRouter::TabGroupedStateChanged(
TabStripModel* tab_strip_model,
std::optional<tab_groups::TabGroupId> old_group,
std::optional<tab_groups::TabGroupId> new_group,
tabs::TabInterface* tab,
int index) {
std::set<std::string> changed_property_names;
changed_property_names.insert(kGroupIdKey);
DispatchTabUpdatedEvent(tab->GetContents(),
std::move(changed_property_names));
}
void TabsEventRouter::OnZoomControllerDestroyed(
zoom::ZoomController* zoom_controller) {
if (zoom_scoped_observations_.IsObservingSource(zoom_controller)) {
zoom_scoped_observations_.RemoveObservation(zoom_controller);
}
}
void TabsEventRouter::OnZoomChanged(
const ZoomController::ZoomChangedEventData& data) {
DCHECK(data.web_contents);
int tab_id = ExtensionTabUtil::GetTabId(data.web_contents);
if (tab_id < 0)
return;
// Prepare the zoom change information.
api::tabs::OnZoomChange::ZoomChangeInfo zoom_change_info;
zoom_change_info.tab_id = tab_id;
zoom_change_info.old_zoom_factor =
blink::ZoomLevelToZoomFactor(data.old_zoom_level);
zoom_change_info.new_zoom_factor =
blink::ZoomLevelToZoomFactor(data.new_zoom_level);
ZoomModeToZoomSettings(data.zoom_mode, &zoom_change_info.zoom_settings);
// Dispatch the |onZoomChange| event.
Profile* profile =
Profile::FromBrowserContext(data.web_contents->GetBrowserContext());
DispatchEvent(profile, events::TABS_ON_ZOOM_CHANGE,
api::tabs::OnZoomChange::kEventName,
api::tabs::OnZoomChange::Create(zoom_change_info),
EventRouter::UserGestureState::kUnknown);
}
void TabsEventRouter::OnFaviconUpdated(
favicon::FaviconDriver* favicon_driver,
NotificationIconType notification_icon_type,
const GURL& icon_url,
bool icon_url_changed,
const gfx::Image& image) {
if (notification_icon_type == NON_TOUCH_16_DIP && icon_url_changed) {
favicon::ContentFaviconDriver* content_favicon_driver =
static_cast<favicon::ContentFaviconDriver*>(favicon_driver);
FaviconUrlUpdated(content_favicon_driver->web_contents());
}
}
void TabsEventRouter::OnLifecycleUnitStateChanged(
resource_coordinator::LifecycleUnit* lifecycle_unit,
::mojom::LifecycleUnitState previous_state,
::mojom::LifecycleUnitStateChangeReason reason) {
const ::mojom::LifecycleUnitState new_state = lifecycle_unit->GetState();
auto previous_or_new_state_is = [&](::mojom::LifecycleUnitState state) {
return previous_state == state || new_state == state;
};
std::set<std::string> changed_property_names;
if (previous_or_new_state_is(::mojom::LifecycleUnitState::DISCARDED)) {
// If the "discarded" property changes, so does the "status" property:
// - a discarded tab has status "unloaded", and will transition to "loading"
// on un-discarding; and,
// - a tab can only be discarded if its status is "complete" or "loading",
// in which case it will transition to "unloaded".
changed_property_names.insert(kDiscardedKey);
changed_property_names.insert(tabs_constants::kStatusKey);
}
if (previous_or_new_state_is(::mojom::LifecycleUnitState::FROZEN)) {
changed_property_names.insert(kFrozenKey);
}
if (!changed_property_names.empty()) {
DispatchTabUpdatedEvent(
lifecycle_unit->AsTabLifecycleUnitExternal()->GetWebContents(),
std::move(changed_property_names));
}
}
void TabsEventRouter::OnIsAutoDiscardableChanged(
const performance_manager::PageNode* page_node) {
std::set<std::string> changed_property_names;
changed_property_names.insert(kAutoDiscardableKey);
DispatchTabUpdatedEvent(page_node->GetWebContents().get(),
std::move(changed_property_names));
}
void TabsEventRouter::DispatchTabInsertedAt(TabStripModel* tab_strip_model,
WebContents* contents,
int index,
bool active) {
if (!GetTabEntry(contents)) {
// We've never seen this tab, send create event as long as we're not in the
// constructor.
if (browser_tab_strip_tracker_.is_processing_initial_browsers())
RegisterForTabNotifications(contents);
else
TabCreatedAt(contents, index, active);
return;
}
int tab_id = ExtensionTabUtil::GetTabId(contents);
base::Value::List args;
args.Append(tab_id);
base::Value::Dict object_args;
object_args.Set(kNewWindowIdKey,
Value(ExtensionTabUtil::GetWindowIdOfTab(contents)));
object_args.Set(kNewPositionKey, Value(index));
args.Append(std::move(object_args));
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
DispatchEvent(profile, events::TABS_ON_ATTACHED,
api::tabs::OnAttached::kEventName, std::move(args),
EventRouter::UserGestureState::kUnknown);
}
void TabsEventRouter::DispatchTabClosingAt(TabStripModel* tab_strip_model,
WebContents* contents,
int index) {
int tab_id = ExtensionTabUtil::GetTabId(contents);
base::Value::List args;
args.Append(tab_id);
base::Value::Dict object_args;
object_args.Set(tabs_constants::kWindowIdKey,
ExtensionTabUtil::GetWindowIdOfTab(contents));
object_args.Set(tabs_constants::kIsWindowClosingKey,
tab_strip_model->closing_all());
args.Append(std::move(object_args));
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
DispatchEvent(profile, events::TABS_ON_REMOVED,
api::tabs::OnRemoved::kEventName, std::move(args),
EventRouter::UserGestureState::kUnknown);
UnregisterForTabNotifications(contents);
}
void TabsEventRouter::DispatchTabDetachedAt(WebContents* contents,
int index,
bool was_active) {
if (!GetTabEntry(contents)) {
// The tab was removed. Don't send detach event.
return;
}
base::Value::List args;
args.Append(ExtensionTabUtil::GetTabId(contents));
base::Value::Dict object_args;
object_args.Set(kOldWindowIdKey,
ExtensionTabUtil::GetWindowIdOfTab(contents));
object_args.Set(kOldPositionKey, index);
args.Append(std::move(object_args));
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
DispatchEvent(profile, events::TABS_ON_DETACHED,
api::tabs::OnDetached::kEventName, std::move(args),
EventRouter::UserGestureState::kUnknown);
}
void TabsEventRouter::DispatchActiveTabChanged(WebContents* old_contents,
WebContents* new_contents) {
base::Value::List args;
int tab_id = ExtensionTabUtil::GetTabId(new_contents);
args.Append(tab_id);
base::Value::Dict object_args;
object_args.Set(tabs_constants::kWindowIdKey,
ExtensionTabUtil::GetWindowIdOfTab(new_contents));
args.Append(object_args.Clone());
// The onActivated event replaced onActiveChanged and onSelectionChanged. The
// deprecated events take two arguments: tabId, {windowId}.
Profile* profile =
Profile::FromBrowserContext(new_contents->GetBrowserContext());
DispatchEvent(profile, events::TABS_ON_SELECTION_CHANGED,
api::tabs::OnSelectionChanged::kEventName, args.Clone(),
EventRouter::UserGestureState::kUnknown);
DispatchEvent(profile, events::TABS_ON_ACTIVE_CHANGED,
api::tabs::OnActiveChanged::kEventName, std::move(args),
EventRouter::UserGestureState::kUnknown);
// The onActivated event takes one argument: {windowId, tabId}.
base::Value::List on_activated_args;
object_args.Set(kTabIdKey, tab_id);
on_activated_args.Append(std::move(object_args));
DispatchEvent(
profile, events::TABS_ON_ACTIVATED, api::tabs::OnActivated::kEventName,
std::move(on_activated_args), EventRouter::UserGestureState::kUnknown);
}
void TabsEventRouter::DispatchTabSelectionChanged(
TabStripModel* tab_strip_model,
const ui::ListSelectionModel& old_model) {
ui::ListSelectionModel::SelectedIndices new_selection =
tab_strip_model->selection_model().selected_indices();
base::Value::List all_tabs;
for (int index : new_selection) {
WebContents* contents = tab_strip_model->GetWebContentsAt(index);
if (!contents)
break;
int tab_id = ExtensionTabUtil::GetTabId(contents);
all_tabs.Append(tab_id);
}
base::Value::List args;
base::Value::Dict select_info;
int window_id = -1;
for (Browser* browser : *BrowserList::GetInstance()) {
if (browser->tab_strip_model() == tab_strip_model) {
window_id = ExtensionTabUtil::GetWindowId(browser);
break;
}
}
select_info.Set(tabs_constants::kWindowIdKey, window_id);
select_info.Set(kTabIdsKey, std::move(all_tabs));
args.Append(std::move(select_info));
// The onHighlighted event replaced onHighlightChanged.
Profile* profile = tab_strip_model->profile();
DispatchEvent(profile, events::TABS_ON_HIGHLIGHT_CHANGED,
api::tabs::OnHighlightChanged::kEventName, args.Clone(),
EventRouter::UserGestureState::kUnknown);
DispatchEvent(profile, events::TABS_ON_HIGHLIGHTED,
api::tabs::OnHighlighted::kEventName, std::move(args),
EventRouter::UserGestureState::kUnknown);
}
void TabsEventRouter::DispatchTabMoved(WebContents* contents,
int from_index,
int to_index) {
base::Value::List args;
args.Append(ExtensionTabUtil::GetTabId(contents));
base::Value::Dict object_args;
object_args.Set(tabs_constants::kWindowIdKey,
ExtensionTabUtil::GetWindowIdOfTab(contents));
object_args.Set(kFromIndexKey, from_index);
object_args.Set(kToIndexKey, to_index);
args.Append(std::move(object_args));
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
DispatchEvent(profile, events::TABS_ON_MOVED, api::tabs::OnMoved::kEventName,
std::move(args), EventRouter::UserGestureState::kUnknown);
}
void TabsEventRouter::DispatchTabReplacedAt(WebContents* old_contents,
WebContents* new_contents,
int index) {
// Notify listeners that the next tabs closing or being added are due to
// WebContents being swapped.
const int new_tab_id = ExtensionTabUtil::GetTabId(new_contents);
const int old_tab_id = ExtensionTabUtil::GetTabId(old_contents);
base::Value::List args;
args.Append(new_tab_id);
args.Append(old_tab_id);
DispatchEvent(Profile::FromBrowserContext(new_contents->GetBrowserContext()),
events::TABS_ON_REPLACED, api::tabs::OnReplaced::kEventName,
std::move(args), EventRouter::UserGestureState::kUnknown);
UnregisterForTabNotifications(old_contents);
if (!GetTabEntry(new_contents))
RegisterForTabNotifications(new_contents);
}
void TabsEventRouter::TabCreatedAt(WebContents* contents,
int index,
bool active) {
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
auto event = std::make_unique<Event>(events::TABS_ON_CREATED,
api::tabs::OnCreated::kEventName,
base::Value::List(), profile);
event->user_gesture = EventRouter::UserGestureState::kNotEnabled;
event->will_dispatch_callback =
base::BindRepeating(&WillDispatchTabCreatedEvent, contents, active);
EventRouter::Get(profile)->BroadcastEvent(std::move(event));
RegisterForTabNotifications(contents);
}
void TabsEventRouter::TabUpdated(TabEntry* entry,
std::set<std::string> changed_property_names) {
auto* audible_helper =
RecentlyAudibleHelper::FromWebContents(entry->web_contents());
bool audible = audible_helper->WasRecentlyAudible();
if (entry->SetAudible(audible)) {
changed_property_names.insert(kAudibleKey);
}
bool muted = entry->web_contents()->IsAudioMuted();
if (entry->SetMuted(muted)) {
changed_property_names.insert(kMutedInfoKey);
}
if (!changed_property_names.empty()) {
DispatchTabUpdatedEvent(entry->web_contents(),
std::move(changed_property_names));
}
}
void TabsEventRouter::FaviconUrlUpdated(WebContents* contents) {
content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
if (!entry || !entry->GetFavicon().valid)
return;
std::set<std::string> changed_property_names;
changed_property_names.insert(tabs_constants::kFaviconUrlKey);
DispatchTabUpdatedEvent(contents, std::move(changed_property_names));
}
void TabsEventRouter::DispatchEvent(
Profile* profile,
events::HistogramValue histogram_value,
const std::string& event_name,
base::Value::List args,
EventRouter::UserGestureState user_gesture) {
EventRouter* event_router = EventRouter::Get(profile);
if (!profile_->IsSameOrParent(profile) || !event_router)
return;
auto event = std::make_unique<Event>(histogram_value, event_name,
std::move(args), profile);
event->user_gesture = user_gesture;
event_router->BroadcastEvent(std::move(event));
}
void TabsEventRouter::DispatchTabUpdatedEvent(
WebContents* contents,
std::set<std::string> changed_property_names) {
DCHECK(!changed_property_names.empty());
DCHECK(contents);
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
auto event = std::make_unique<Event>(
events::TABS_ON_UPDATED, api::tabs::OnUpdated::kEventName,
// The event arguments depend on the extension's permission. They are set
// in WillDispatchTabUpdatedEvent().
base::Value::List(), profile);
event->user_gesture = EventRouter::UserGestureState::kNotEnabled;
event->will_dispatch_callback =
base::BindRepeating(&WillDispatchTabUpdatedEvent, contents,
std::move(changed_property_names));
EventRouter::Get(profile)->BroadcastEvent(std::move(event));
}
void TabsEventRouter::RegisterForTabNotifications(WebContents* contents) {
favicon_scoped_observations_.AddObservation(
favicon::ContentFaviconDriver::FromWebContents(contents));
zoom_scoped_observations_.AddObservation(
ZoomController::FromWebContents(contents));
int tab_id = ExtensionTabUtil::GetTabId(contents);
DCHECK(tab_entries_.find(tab_id) == tab_entries_.end());
tab_entries_[tab_id] = std::make_unique<TabEntry>(this, contents);
}
void TabsEventRouter::UnregisterForTabNotifications(WebContents* contents) {
if (auto* zoom_controller = ZoomController::FromWebContents(contents);
zoom_scoped_observations_.IsObservingSource(zoom_controller)) {
zoom_scoped_observations_.RemoveObservation(zoom_controller);
}
favicon_scoped_observations_.RemoveObservation(
favicon::ContentFaviconDriver::FromWebContents(contents));
int tab_id = ExtensionTabUtil::GetTabId(contents);
int removed_count = tab_entries_.erase(tab_id);
DCHECK_GT(removed_count, 0);
}
TabsEventRouter::TabEntry* TabsEventRouter::GetTabEntry(WebContents* contents) {
const auto it = tab_entries_.find(ExtensionTabUtil::GetTabId(contents));
return it == tab_entries_.end() ? nullptr : it->second.get();
}
} // namespace extensions