blob: 3946769b02d6f758193141d82c6991a4918e0541 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Implements the Chrome Extensions WebNavigation API.
#include "chrome/browser/extensions/api/web_navigation/web_navigation_api.h"
#include <memory>
#include "base/lazy_instance.h"
#include "chrome/browser/extensions/api/web_navigation/web_navigation_api_constants.h"
#include "chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/common/extensions/api/web_navigation.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/view_type_utils.h"
#include "extensions/common/mojom/view_type.mojom.h"
#include "net/base/net_errors.h"
#include "pdf/buildflags.h"
#if BUILDFLAG(ENABLE_PDF)
#include "components/pdf/common/pdf_util.h"
#include "pdf/pdf_features.h"
#endif // BUILDFLAG(ENABLE_PDF)
namespace GetFrame = extensions::api::web_navigation::GetFrame;
namespace GetAllFrames = extensions::api::web_navigation::GetAllFrames;
namespace extensions {
namespace web_navigation = api::web_navigation;
// WebNavigtionEventRouter -------------------------------------------
WebNavigationEventRouter::PendingWebContents::PendingWebContents() = default;
WebNavigationEventRouter::PendingWebContents::~PendingWebContents() = default;
void WebNavigationEventRouter::PendingWebContents::Set(
int source_tab_id,
int source_render_process_id,
int source_extension_frame_id,
content::WebContents* target_web_contents,
const GURL& target_url,
base::OnceCallback<void(content::WebContents*)> on_destroy) {
Observe(target_web_contents);
source_tab_id_ = source_tab_id;
source_render_process_id_ = source_render_process_id;
source_extension_frame_id_ = source_extension_frame_id;
target_web_contents_ = target_web_contents;
target_url_ = target_url;
on_destroy_ = std::move(on_destroy);
}
void WebNavigationEventRouter::PendingWebContents::WebContentsDestroyed() {
std::move(on_destroy_).Run(target_web_contents_.get());
// |this| is deleted!
}
WebNavigationEventRouter::WebNavigationEventRouter(Profile* profile)
: profile_(profile), browser_tab_strip_tracker_(this, this) {
browser_tab_strip_tracker_.Init();
}
WebNavigationEventRouter::~WebNavigationEventRouter() = default;
bool WebNavigationEventRouter::ShouldTrackBrowser(
BrowserWindowInterface* browser) {
return profile_->IsSameOrParent(browser->GetProfile());
}
void WebNavigationEventRouter::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (change.type() == TabStripModelChange::kReplaced) {
auto* replace = change.GetReplace();
WebNavigationTabObserver* tab_observer =
WebNavigationTabObserver::Get(replace->old_contents);
if (!tab_observer) {
// If you hit this DCHECK(), please add reproduction steps to
// http://crbug.com/109464.
DCHECK(GetViewType(replace->old_contents) !=
mojom::ViewType::kTabContents);
return;
}
if (!FrameNavigationState::IsValidUrl(
replace->old_contents->GetLastCommittedURL()) ||
!FrameNavigationState::IsValidUrl(
replace->new_contents->GetLastCommittedURL()))
return;
web_navigation_api_helpers::DispatchOnTabReplaced(
replace->old_contents, profile_, replace->new_contents);
} else if (change.type() == TabStripModelChange::kInserted) {
for (auto& tab : change.GetInsert()->contents)
TabAdded(tab.contents);
}
}
void WebNavigationEventRouter::RecordNewWebContents(
content::WebContents* source_web_contents,
int source_render_process_id,
int source_render_frame_id,
GURL target_url,
content::WebContents* target_web_contents,
bool not_yet_in_tabstrip) {
if (source_render_frame_id == 0)
return;
WebNavigationTabObserver* tab_observer =
WebNavigationTabObserver::Get(source_web_contents);
if (!tab_observer) {
// If you hit this DCHECK(), please add reproduction steps to
// http://crbug.com/109464.
DCHECK(GetViewType(source_web_contents) != mojom::ViewType::kTabContents);
return;
}
auto* frame_host = content::RenderFrameHost::FromID(source_render_process_id,
source_render_frame_id);
auto* frame_navigation_state =
FrameNavigationState::GetForCurrentDocument(frame_host);
if (!frame_navigation_state || !frame_navigation_state->CanSendEvents())
return;
int source_extension_frame_id =
ExtensionApiFrameIdMap::GetFrameId(frame_host);
int source_tab_id = ExtensionTabUtil::GetTabId(source_web_contents);
// If the WebContents isn't yet inserted into a tab strip, we need to delay
// the extension event until the WebContents is fully initialized.
if (not_yet_in_tabstrip) {
pending_web_contents_[target_web_contents].Set(
source_tab_id, source_render_process_id, source_extension_frame_id,
target_web_contents, target_url,
base::BindOnce(&WebNavigationEventRouter::PendingWebContentsDestroyed,
base::Unretained(this)));
} else {
web_navigation_api_helpers::DispatchOnCreatedNavigationTarget(
source_tab_id, source_render_process_id, source_extension_frame_id,
target_web_contents->GetBrowserContext(), target_web_contents,
target_url);
}
}
void WebNavigationEventRouter::TabAdded(content::WebContents* tab) {
auto iter = pending_web_contents_.find(tab);
if (iter == pending_web_contents_.end())
return;
const PendingWebContents& pending_tab = iter->second;
web_navigation_api_helpers::DispatchOnCreatedNavigationTarget(
pending_tab.source_tab_id(), pending_tab.source_render_process_id(),
pending_tab.source_extension_frame_id(),
pending_tab.target_web_contents()->GetBrowserContext(),
pending_tab.target_web_contents(), pending_tab.target_url());
pending_web_contents_.erase(iter);
}
void WebNavigationEventRouter::PendingWebContentsDestroyed(
content::WebContents* tab) {
pending_web_contents_.erase(tab);
}
// WebNavigationTabObserver ------------------------------------------
WebNavigationTabObserver::WebNavigationTabObserver(
content::WebContents* web_contents)
: WebContentsObserver(web_contents),
content::WebContentsUserData<WebNavigationTabObserver>(*web_contents) {}
WebNavigationTabObserver::~WebNavigationTabObserver() = default;
// static
WebNavigationTabObserver* WebNavigationTabObserver::Get(
content::WebContents* web_contents) {
return FromWebContents(web_contents);
}
void WebNavigationTabObserver::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
auto* navigation_state =
FrameNavigationState::GetForCurrentDocument(render_frame_host);
if (navigation_state && navigation_state->CanSendEvents() &&
!navigation_state->GetDocumentLoadCompleted()) {
web_navigation_api_helpers::DispatchOnErrorOccurred(
web_contents(), render_frame_host, navigation_state->GetUrl(),
net::ERR_ABORTED);
navigation_state->SetErrorOccurredInFrame();
}
}
void WebNavigationTabObserver::RenderFrameHostChanged(
content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) {
if (old_host)
RenderFrameHostPendingDeletion(old_host);
}
void WebNavigationTabObserver::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
if (navigation_handle->IsSameDocument() ||
!FrameNavigationState::IsValidUrl(navigation_handle->GetURL())) {
return;
}
pending_on_before_navigate_event_ =
web_navigation_api_helpers::CreateOnBeforeNavigateEvent(
navigation_handle);
// Only dispatch the onBeforeNavigate event if the associated WebContents
// is already added to the tab strip. Otherwise the event should be delayed
// and sent after the addition, to preserve the ordering of events.
//
// TODO(nasko|devlin): This check is necessary because chrome::Navigate()
// begins the navigation before adding the tab to the TabStripModel, and it
// is used an indication of that. It would be best if instead it was known
// when the tab was created and immediately sent the created event instead of
// waiting for the later TabStripModel kInserted change, but this appears to
// work for now.
if (ExtensionTabUtil::GetTabById(ExtensionTabUtil::GetTabId(web_contents()),
web_contents()->GetBrowserContext(), false,
nullptr)) {
DispatchCachedOnBeforeNavigate();
}
}
void WebNavigationTabObserver::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
// If there has been a DidStartNavigation call before the tab was ready to
// dispatch events, ensure that it is sent before processing the
// DidFinishNavigation.
// Note: This is exercised by WebNavigationApiTest.TargetBlankIncognito.
DispatchCachedOnBeforeNavigate();
if (navigation_handle->HasCommitted() && !navigation_handle->IsErrorPage()) {
HandleCommit(navigation_handle);
return;
}
HandleError(navigation_handle);
}
void WebNavigationTabObserver::DOMContentLoaded(
content::RenderFrameHost* render_frame_host) {
auto* navigation_state =
FrameNavigationState::GetForCurrentDocument(render_frame_host);
if (!navigation_state || !navigation_state->CanSendEvents())
return;
navigation_state->SetParsingFinished();
web_navigation_api_helpers::DispatchOnDOMContentLoaded(
web_contents(), render_frame_host, navigation_state->GetUrl());
if (!navigation_state->GetDocumentLoadCompleted())
return;
// The load might already have finished by the time we finished parsing. For
// compatibility reasons, we artifically delay the load completed signal until
// after parsing was completed.
web_navigation_api_helpers::DispatchOnCompleted(
web_contents(), render_frame_host, navigation_state->GetUrl());
}
void WebNavigationTabObserver::DidFinishLoad(
content::RenderFrameHost* render_frame_host,
const GURL& validated_url) {
auto* navigation_state =
FrameNavigationState::GetForCurrentDocument(render_frame_host);
// When showing replacement content, we might get load signals for frames
// that weren't regularly loaded.
if (!navigation_state)
return;
navigation_state->SetDocumentLoadCompleted();
if (!navigation_state->CanSendEvents())
return;
// A new navigation might have started before the old one completed.
// Ignore the old navigation completion in that case.
if (navigation_state->GetUrl() != validated_url)
return;
// The load might already have finished by the time we finished parsing. For
// compatibility reasons, we artifically delay the load completed signal until
// after parsing was completed.
if (!navigation_state->GetParsingFinished())
return;
web_navigation_api_helpers::DispatchOnCompleted(
web_contents(), render_frame_host, navigation_state->GetUrl());
}
void WebNavigationTabObserver::DidFailLoad(
content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code) {
auto* navigation_state =
FrameNavigationState::GetForCurrentDocument(render_frame_host);
// When showing replacement content, we might get load signals for frames
// that weren't regularly loaded.
if (!navigation_state)
return;
if (navigation_state->CanSendEvents()) {
web_navigation_api_helpers::DispatchOnErrorOccurred(
web_contents(), render_frame_host, navigation_state->GetUrl(),
error_code);
}
navigation_state->SetErrorOccurredInFrame();
}
void WebNavigationTabObserver::DidOpenRequestedURL(
content::WebContents* new_contents,
content::RenderFrameHost* source_render_frame_host,
const GURL& url,
const content::Referrer& referrer,
WindowOpenDisposition disposition,
ui::PageTransition transition,
bool started_from_context_menu,
bool renderer_initiated) {
auto* navigation_state =
FrameNavigationState::GetForCurrentDocument(source_render_frame_host);
if (!navigation_state || !navigation_state->CanSendEvents())
return;
// We only send the onCreatedNavigationTarget if we end up creating a new
// window.
if (disposition != WindowOpenDisposition::SINGLETON_TAB &&
disposition != WindowOpenDisposition::NEW_FOREGROUND_TAB &&
disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB &&
disposition != WindowOpenDisposition::NEW_POPUP &&
disposition != WindowOpenDisposition::NEW_WINDOW &&
disposition != WindowOpenDisposition::OFF_THE_RECORD)
return;
WebNavigationAPI* api = WebNavigationAPI::GetFactoryInstance()->Get(
web_contents()->GetBrowserContext());
if (!api)
return; // Possible in unit tests.
WebNavigationEventRouter* router = api->web_navigation_event_router_.get();
if (!router)
return;
TabStripModel* ignored_tab_strip_model = nullptr;
int ignored_tab_index = -1;
bool new_contents_is_present_in_tabstrip = ExtensionTabUtil::GetTabStripModel(
new_contents, &ignored_tab_strip_model, &ignored_tab_index);
router->RecordNewWebContents(
web_contents(), source_render_frame_host->GetProcess()->GetDeprecatedID(),
source_render_frame_host->GetRoutingID(), url, new_contents,
!new_contents_is_present_in_tabstrip);
}
void WebNavigationTabObserver::DispatchCachedOnBeforeNavigate() {
if (!pending_on_before_navigate_event_)
return;
// EventRouter can be null in unit tests.
EventRouter* event_router =
EventRouter::Get(web_contents()->GetBrowserContext());
if (event_router)
event_router->BroadcastEvent(std::move(pending_on_before_navigate_event_));
}
void WebNavigationTabObserver::HandleCommit(
content::NavigationHandle* navigation_handle) {
bool is_reference_fragment_navigation =
navigation_handle->IsSameDocument() &&
IsReferenceFragmentNavigation(navigation_handle->GetRenderFrameHost(),
navigation_handle->GetURL());
FrameNavigationState::GetOrCreateForCurrentDocument(
navigation_handle->GetRenderFrameHost())
->StartTrackingDocumentLoad(
navigation_handle->GetURL(), navigation_handle->IsSameDocument(),
navigation_handle->IsServedFromBackForwardCache(),
/*is_error_page=*/false);
events::HistogramValue histogram_value = events::UNKNOWN;
std::string event_name;
if (is_reference_fragment_navigation) {
histogram_value = events::WEB_NAVIGATION_ON_REFERENCE_FRAGMENT_UPDATED;
event_name = web_navigation::OnReferenceFragmentUpdated::kEventName;
} else if (navigation_handle->IsSameDocument()) {
histogram_value = events::WEB_NAVIGATION_ON_HISTORY_STATE_UPDATED;
event_name = web_navigation::OnHistoryStateUpdated::kEventName;
} else {
histogram_value = events::WEB_NAVIGATION_ON_COMMITTED;
event_name = web_navigation::OnCommitted::kEventName;
}
web_navigation_api_helpers::DispatchOnCommitted(histogram_value, event_name,
navigation_handle);
if (navigation_handle->IsServedFromBackForwardCache()) {
web_navigation_api_helpers::DispatchOnCompleted(
navigation_handle->GetWebContents(),
navigation_handle->GetRenderFrameHost(), navigation_handle->GetURL());
}
}
void WebNavigationTabObserver::HandleError(
content::NavigationHandle* navigation_handle) {
if (navigation_handle->HasCommitted()) {
FrameNavigationState::GetOrCreateForCurrentDocument(
navigation_handle->GetRenderFrameHost())
->StartTrackingDocumentLoad(navigation_handle->GetURL(),
navigation_handle->IsSameDocument(),
/*is_from_back_forward_cache=*/false,
/*is_error_page=*/true);
}
web_navigation_api_helpers::DispatchOnErrorOccurred(navigation_handle);
}
bool WebNavigationTabObserver::IsReferenceFragmentNavigation(
content::RenderFrameHost* render_frame_host,
const GURL& url) {
auto* navigation_state =
FrameNavigationState::GetForCurrentDocument(render_frame_host);
GURL existing_url = navigation_state ? navigation_state->GetUrl() : GURL();
if (existing_url == url)
return false;
return existing_url.EqualsIgnoringRef(url);
}
void WebNavigationTabObserver::RenderFrameHostPendingDeletion(
content::RenderFrameHost* pending_delete_render_frame_host) {
// The |pending_delete_render_frame_host| and its children are now pending
// deletion. Stop tracking them.
pending_delete_render_frame_host->ForEachRenderFrameHost(
[this](content::RenderFrameHost* render_frame_host) {
auto* navigation_state =
FrameNavigationState::GetForCurrentDocument(render_frame_host);
if (navigation_state) {
RenderFrameDeleted(render_frame_host);
FrameNavigationState::DeleteForCurrentDocument(render_frame_host);
}
});
}
ExtensionFunction::ResponseAction WebNavigationGetFrameFunction::Run() {
std::optional<GetFrame::Params> params = GetFrame::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
int tab_id = api::tabs::TAB_ID_NONE;
int frame_id = -1;
content::RenderFrameHost* render_frame_host = nullptr;
if (params->details.document_id) {
ExtensionApiFrameIdMap::DocumentId document_id =
ExtensionApiFrameIdMap::DocumentIdFromString(
*params->details.document_id);
if (!document_id)
return RespondNow(Error("Invalid documentId."));
// Note that we will globally find a RenderFrameHost but validate that
// we are in the right context still as we may be in the wrong profile
// or in incognito mode.
render_frame_host =
ExtensionApiFrameIdMap::Get()->GetRenderFrameHostByDocumentId(
document_id);
if (!render_frame_host)
return RespondNow(WithArguments(base::Value()));
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
// We found the RenderFrameHost through a generic lookup so we must test to
// see if the WebContents is actually in our BrowserContext.
if (!ExtensionTabUtil::IsWebContentsInContext(
web_contents, browser_context(), include_incognito_information())) {
return RespondNow(WithArguments(base::Value()));
}
tab_id = ExtensionTabUtil::GetTabId(web_contents);
frame_id = ExtensionApiFrameIdMap::GetFrameId(render_frame_host);
// If the provided tab_id and frame_id do not match the calculated ones
// return.
if ((params->details.tab_id && *params->details.tab_id != tab_id) ||
(params->details.frame_id && *params->details.frame_id != frame_id)) {
return RespondNow(WithArguments(base::Value()));
}
} else {
// If documentId is not provided, tab_id and frame_id must be. Return early
// if not.
if (!params->details.tab_id || !params->details.frame_id) {
return RespondNow(Error(
"Either documentId or both tabId and frameId must be specified."));
}
tab_id = *params->details.tab_id;
frame_id = *params->details.frame_id;
content::WebContents* web_contents = nullptr;
if (!ExtensionTabUtil::GetTabById(tab_id, browser_context(),
include_incognito_information(),
&web_contents) ||
!web_contents) {
return RespondNow(WithArguments(base::Value()));
}
render_frame_host = ExtensionApiFrameIdMap::Get()->GetRenderFrameHostById(
web_contents, frame_id);
}
auto* frame_navigation_state =
render_frame_host
? FrameNavigationState::GetForCurrentDocument(render_frame_host)
: nullptr;
if (!frame_navigation_state)
return RespondNow(WithArguments(base::Value()));
GURL frame_url = frame_navigation_state->GetUrl();
if (!FrameNavigationState::IsValidUrl(frame_url))
return RespondNow(WithArguments(base::Value()));
GetFrame::Results::Details frame_details;
frame_details.url = frame_url.spec();
frame_details.error_occurred =
frame_navigation_state->GetErrorOccurredInFrame();
frame_details.parent_frame_id =
ExtensionApiFrameIdMap::GetParentFrameId(render_frame_host);
frame_details.document_id =
ExtensionApiFrameIdMap::GetDocumentId(render_frame_host).ToString();
// Only set the parentDocumentId value if we have a parent.
if (content::RenderFrameHost* parent_frame_host =
render_frame_host->GetParentOrOuterDocument()) {
frame_details.parent_document_id =
ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host).ToString();
}
frame_details.frame_type =
ExtensionApiFrameIdMap::GetFrameType(render_frame_host);
frame_details.document_lifecycle =
ExtensionApiFrameIdMap::GetDocumentLifecycle(render_frame_host);
return RespondNow(ArgumentList(GetFrame::Results::Create(frame_details)));
}
ExtensionFunction::ResponseAction WebNavigationGetAllFramesFunction::Run() {
std::optional<GetAllFrames::Params> params =
GetAllFrames::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
int tab_id = params->details.tab_id;
content::WebContents* web_contents = nullptr;
if (!ExtensionTabUtil::GetTabById(tab_id, browser_context(),
include_incognito_information(),
&web_contents) ||
!web_contents) {
return RespondNow(WithArguments(base::Value()));
}
std::vector<GetAllFrames::Results::DetailsType> result_list;
// We currently do not expose back/forward cached frames in the GetAllFrames
// API, but we do explicitly include prerendered frames.
web_contents
->ForEachRenderFrameHostWithAction(
[web_contents,
&result_list](content::RenderFrameHost* render_frame_host) {
// Don't expose inner WebContents for the getFrames API.
if (content::WebContents::FromRenderFrameHost(render_frame_host) !=
web_contents) {
return content::RenderFrameHost::FrameIterationAction::
kSkipChildren;
}
#if BUILDFLAG(ENABLE_PDF)
if (chrome_pdf::features::IsOopifPdfEnabled()) {
// Don't expose any child frames of the PDF extension frame, such
// as the PDF content frame.
content::RenderFrameHost* parent = render_frame_host->GetParent();
if (parent &&
IsPdfExtensionOrigin(parent->GetLastCommittedOrigin())) {
return content::RenderFrameHost::FrameIterationAction::
kSkipChildren;
}
}
#endif // BUILDFLAG(ENABLE_PDF)
auto* navigation_state =
FrameNavigationState::GetForCurrentDocument(render_frame_host);
if (!navigation_state ||
!FrameNavigationState::IsValidUrl(navigation_state->GetUrl())) {
return content::RenderFrameHost::FrameIterationAction::kContinue;
}
// Skip back/forward cached frames.
if (render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::
kInBackForwardCache)) {
return content::RenderFrameHost::FrameIterationAction::
kSkipChildren;
}
GetAllFrames::Results::DetailsType frame;
frame.url = navigation_state->GetUrl().spec();
frame.frame_id =
ExtensionApiFrameIdMap::GetFrameId(render_frame_host);
frame.parent_frame_id =
ExtensionApiFrameIdMap::GetParentFrameId(render_frame_host);
frame.document_id =
ExtensionApiFrameIdMap::GetDocumentId(render_frame_host)
.ToString();
// Only set the parentDocumentId value if we have a parent.
if (content::RenderFrameHost* parent_frame_host =
render_frame_host->GetParentOrOuterDocument()) {
frame.parent_document_id =
ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host)
.ToString();
}
frame.frame_type =
ExtensionApiFrameIdMap::GetFrameType(render_frame_host);
frame.document_lifecycle =
ExtensionApiFrameIdMap::GetDocumentLifecycle(render_frame_host);
frame.process_id =
render_frame_host->GetProcess()->GetDeprecatedID();
frame.error_occurred = navigation_state->GetErrorOccurredInFrame();
result_list.push_back(std::move(frame));
return content::RenderFrameHost::FrameIterationAction::kContinue;
});
return RespondNow(ArgumentList(GetAllFrames::Results::Create(result_list)));
}
WebNavigationAPI::WebNavigationAPI(content::BrowserContext* context)
: browser_context_(context) {
EventRouter* event_router = EventRouter::Get(browser_context_);
event_router->RegisterObserver(this,
web_navigation::OnBeforeNavigate::kEventName);
event_router->RegisterObserver(this, web_navigation::OnCommitted::kEventName);
event_router->RegisterObserver(this, web_navigation::OnCompleted::kEventName);
event_router->RegisterObserver(
this, web_navigation::OnCreatedNavigationTarget::kEventName);
event_router->RegisterObserver(
this, web_navigation::OnDOMContentLoaded::kEventName);
event_router->RegisterObserver(
this, web_navigation::OnHistoryStateUpdated::kEventName);
event_router->RegisterObserver(this,
web_navigation::OnErrorOccurred::kEventName);
event_router->RegisterObserver(
this, web_navigation::OnReferenceFragmentUpdated::kEventName);
event_router->RegisterObserver(this,
web_navigation::OnTabReplaced::kEventName);
}
WebNavigationAPI::~WebNavigationAPI() = default;
void WebNavigationAPI::Shutdown() {
EventRouter::Get(browser_context_)->UnregisterObserver(this);
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<WebNavigationAPI>>::
DestructorAtExit g_web_navigation_api_factory = LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<WebNavigationAPI>*
WebNavigationAPI::GetFactoryInstance() {
return g_web_navigation_api_factory.Pointer();
}
void WebNavigationAPI::OnListenerAdded(const EventListenerInfo& details) {
web_navigation_event_router_ = std::make_unique<WebNavigationEventRouter>(
Profile::FromBrowserContext(browser_context_));
EventRouter::Get(browser_context_)->UnregisterObserver(this);
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(WebNavigationTabObserver);
} // namespace extensions