blob: 9bfac94df6bb15e78b11c7ee41c1620442b5fb7e [file] [log] [blame]
// Copyright 2024 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/ui/webui/webui_embedding_context.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
namespace webui {
namespace {
// Tracks changes to the wrapped `browser_window_interface_`, notifying clients
// of changes as approprirate.
class EmbeddingBrowserTracker {
public:
explicit EmbeddingBrowserTracker(base::RepeatingClosure browser_change_cb)
: browser_change_cb_(std::move(browser_change_cb)) {}
EmbeddingBrowserTracker(const EmbeddingBrowserTracker&) = delete;
EmbeddingBrowserTracker& operator=(const EmbeddingBrowserTracker&) = delete;
~EmbeddingBrowserTracker() = default;
// Updates `browser_window_interface_` and registers/notifies listeners if
// appropriate.
void SetBrowserWindowInterface(
BrowserWindowInterface* browser_window_interface) {
if (browser_window_interface_ == browser_window_interface) {
return;
}
browser_did_close_subscription_.reset();
browser_window_interface_ = browser_window_interface;
if (browser_window_interface_) {
browser_did_close_subscription_ =
browser_window_interface_->RegisterBrowserDidClose(
base::BindRepeating(&EmbeddingBrowserTracker::OnBrowserDidClose,
base::Unretained(this)));
}
browser_change_cb_.Run();
}
BrowserWindowInterface* browser_window_interface() {
return browser_window_interface_;
}
private:
void OnBrowserDidClose(BrowserWindowInterface* browser_window_interface) {
CHECK_EQ(browser_window_interface_, browser_window_interface);
SetBrowserWindowInterface(nullptr);
}
// The browser interface currently embedding the host contents.
raw_ptr<BrowserWindowInterface> browser_window_interface_ = nullptr;
// Notifies this when `browser_window_interface_` has closed.
std::optional<base::CallbackListSubscription> browser_did_close_subscription_;
// Notifies clients of changes to `browser_window_interface_`.
base::RepeatingClosure browser_change_cb_;
};
// Tracks changes to the wrapped `tab_interface_`, notifying clients of changes
// as approprirate.
class EmbeddingTabTracker {
public:
EmbeddingTabTracker(base::RepeatingClosure tab_change_cb,
base::RepeatingClosure browser_change_cb)
: tab_change_cb_(std::move(tab_change_cb)),
browser_change_cb_(std::move(browser_change_cb)) {}
EmbeddingTabTracker(const EmbeddingTabTracker&) = delete;
EmbeddingTabTracker& operator=(const EmbeddingTabTracker&) = delete;
~EmbeddingTabTracker() = default;
// Updates `tab_interface_` and registers/notifies listeners if appropriate.
void SetTabInterface(tabs::TabInterface* tab_interface) {
if (tab_interface_ == tab_interface) {
return;
}
tab_will_detach_subscription_.reset();
tab_did_insert_subscription_.reset();
tab_interface_ = tab_interface;
if (tab_interface) {
tab_will_detach_subscription_ =
tab_interface_->RegisterWillDetach(base::BindRepeating(
&EmbeddingTabTracker::OnTabWillDetach, base::Unretained(this)));
tab_did_insert_subscription_ =
tab_interface_->RegisterDidInsert(base::BindRepeating(
&EmbeddingTabTracker::OnTabDidInsert, base::Unretained(this)));
}
// Both browser and tab changes should be propagated.
browser_change_cb_.Run();
tab_change_cb_.Run();
}
tabs::TabInterface* tab_interface() { return tab_interface_; }
private:
void OnTabWillDetach(tabs::TabInterface* tab_interface,
tabs::TabInterface::DetachReason detach_reason) {
CHECK_EQ(tab_interface_, tab_interface);
if (detach_reason == tabs::TabInterface::DetachReason::kDelete) {
SetTabInterface(nullptr);
}
}
void OnTabDidInsert(tabs::TabInterface* tab_interface) {
CHECK_EQ(tab_interface_, tab_interface);
browser_change_cb_.Run();
}
// The tab interface currently embedding the host contents.
raw_ptr<tabs::TabInterface> tab_interface_ = nullptr;
// Notifies this when `tab_interface_` will detach, tracked to invalidate
// the tab before destruction.
std::optional<base::CallbackListSubscription> tab_will_detach_subscription_;
// Notifies this when `tab_interface_` is inserted into a new browser window,
// tracked to ensure clients are notified of browser window changes.
std::optional<base::CallbackListSubscription> tab_did_insert_subscription_;
// Notifies clients of changes to `tab_interface_`.
base::RepeatingClosure tab_change_cb_;
// Notifies clients of changes to the `tab_interface_`'s browser.
base::RepeatingClosure browser_change_cb_;
};
// Responsible for managing embedding interface changes for the hosted
// WebContents, notifing downstream clients of embedding context changes.
class EmbedderContextData
: public content::WebContentsUserData<EmbedderContextData> {
public:
EmbedderContextData(const EmbedderContextData&) = delete;
EmbedderContextData& operator=(const EmbedderContextData&) = delete;
~EmbedderContextData() override = default;
static EmbedderContextData* GetOrCreate(content::WebContents* web_contents) {
EmbedderContextData::CreateForWebContents(web_contents);
return EmbedderContextData::FromWebContents(web_contents);
}
void SetBrowserWindowInterface(
BrowserWindowInterface* browser_window_interface) {
// Tabs will always belong to a browser and are tracked together, both
// browser and tab trackers should not be set independently.
CHECK(!tab_tracker_)
<< "Browser and tab trackers should not be set independently";
if (!browser_tracker_) {
browser_tracker_ = std::make_unique<EmbeddingBrowserTracker>(
base::BindRepeating(&EmbedderContextData::NotifyBrowserChanged,
base::Unretained(this)));
}
browser_tracker_->SetBrowserWindowInterface(browser_window_interface);
}
void SetTabInterface(tabs::TabInterface* tab_interface) {
// Tabs will always belong to a browser and are tracked together, both
// browser and tab trackers should not be set independently.
CHECK(!browser_tracker_)
<< "Browser and tab trackers should not be set independently";
if (!tab_tracker_) {
tab_tracker_ = std::make_unique<EmbeddingTabTracker>(
base::BindRepeating(&EmbedderContextData::NotifyTabChanged,
base::Unretained(this)),
base::BindRepeating(&EmbedderContextData::NotifyBrowserChanged,
base::Unretained(this)));
}
tab_tracker_->SetTabInterface(tab_interface);
}
BrowserWindowInterface* GetBrowserWindowInterface() {
// Source the browser interface either directly from the tracked browser or
// via the tracked tab.
if (tab_tracker_) {
return tab_tracker_->tab_interface()
? tab_tracker_->tab_interface()->GetBrowserWindowInterface()
: nullptr;
}
if (browser_tracker_) {
return browser_tracker_->browser_window_interface();
}
return nullptr;
}
tabs::TabInterface* GetTabInterface() {
return tab_tracker_ ? tab_tracker_->tab_interface() : nullptr;
}
base::CallbackListSubscription RegisterBrowserWindowInterfaceChanged(
base::RepeatingClosure browser_changed_cb) {
return browser_change_callbacks_.Add(std::move(browser_changed_cb));
}
base::CallbackListSubscription RegisterTabInterfaceChanged(
base::RepeatingClosure tab_change_cb) {
return tab_change_callbacks_.Add(std::move(tab_change_cb));
}
private:
friend class content::WebContentsUserData<EmbedderContextData>;
explicit EmbedderContextData(content::WebContents* web_contents)
: WebContentsUserData<EmbedderContextData>(*web_contents) {}
// Notifies clients their embedding browser has changed.
void NotifyBrowserChanged() { browser_change_callbacks_.Notify(); }
// Notifies clients their embedding tab has changed.
void NotifyTabChanged() { tab_change_callbacks_.Notify(); }
// Defined only if actively tracking an embedding browser.
std::unique_ptr<EmbeddingBrowserTracker> browser_tracker_;
// Defined only if actively tracking an embedding tab.
std::unique_ptr<EmbeddingTabTracker> tab_tracker_;
// Client registrations for changes to the embedding browser.
base::RepeatingCallbackList<void()> browser_change_callbacks_;
// Client registrations for changes to the embedding tab.
base::RepeatingCallbackList<void()> tab_change_callbacks_;
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
WEB_CONTENTS_USER_DATA_KEY_IMPL(EmbedderContextData);
} // namespace
base::CallbackListSubscription InitEmbeddingContext(
tabs::TabInterface* tab_interface) {
// Set the initial tab interface and configure updates for discard changes.
// TODO(crbug.com/371950942): Once the new discard implementation has landed
// there is no need to re-set the interface on discard and this can be inlined
// into TabModel.
SetTabInterface(tab_interface->GetContents(), tab_interface);
return tab_interface->RegisterWillDiscardContents(base::BindRepeating(
[](tabs::TabInterface* tab, content::WebContents* old_contents,
content::WebContents* new_contents) {
SetTabInterface(new_contents, tab);
}));
}
void SetBrowserWindowInterface(
content::WebContents* host_contents,
BrowserWindowInterface* browser_window_interface) {
EmbedderContextData::GetOrCreate(host_contents)
->SetBrowserWindowInterface(browser_window_interface);
}
void SetTabInterface(content::WebContents* host_contents,
tabs::TabInterface* tab_interface) {
EmbedderContextData::GetOrCreate(host_contents)
->SetTabInterface(tab_interface);
}
BrowserWindowInterface* GetBrowserWindowInterface(
content::WebContents* host_contents) {
return EmbedderContextData::GetOrCreate(host_contents)
->GetBrowserWindowInterface();
}
tabs::TabInterface* GetTabInterface(content::WebContents* host_contents) {
return EmbedderContextData::GetOrCreate(host_contents)->GetTabInterface();
}
base::CallbackListSubscription RegisterBrowserWindowInterfaceChanged(
content::WebContents* host_contents,
base::RepeatingClosure context_changed_cb) {
return EmbedderContextData::GetOrCreate(host_contents)
->RegisterBrowserWindowInterfaceChanged(std::move(context_changed_cb));
}
base::CallbackListSubscription RegisterTabInterfaceChanged(
content::WebContents* host_contents,
base::RepeatingClosure context_changed_cb) {
return EmbedderContextData::GetOrCreate(host_contents)
->RegisterTabInterfaceChanged(std::move(context_changed_cb));
}
} // namespace webui