blob: 502a18576edf34c848be0b9ae6c39de81fbbb125 [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.
#include "content/public/test/test_navigation_observer.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_url_handler.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test_utils.h"
namespace content {
class TestNavigationObserver::TestWebContentsObserver
: public WebContentsObserver {
public:
TestWebContentsObserver(TestNavigationObserver* parent,
WebContents* web_contents)
: WebContentsObserver(web_contents),
parent_(parent) {
}
TestWebContentsObserver(const TestWebContentsObserver&) = delete;
TestWebContentsObserver& operator=(const TestWebContentsObserver&) = delete;
private:
// WebContentsObserver:
void NavigationEntryCommitted(
const LoadCommittedDetails& load_details) override {
parent_->OnNavigationEntryCommitted(this, web_contents(), load_details);
}
void WebContentsDestroyed() override {
parent_->OnWebContentsDestroyed(this, web_contents());
}
void DidStartLoading() override {
parent_->OnDidStartLoading(web_contents());
}
void DidStopLoading() override {
parent_->OnDidStopLoading(web_contents());
}
void DidStartNavigation(NavigationHandle* navigation_handle) override {
if (navigation_handle->IsSameDocument())
return;
parent_->OnDidStartNavigation(navigation_handle);
}
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
parent_->OnDidFinishNavigation(navigation_handle);
}
raw_ptr<TestNavigationObserver> parent_;
};
TestNavigationObserver::WebContentsState::WebContentsState() = default;
TestNavigationObserver::WebContentsState::WebContentsState(
WebContentsState&& other) = default;
TestNavigationObserver::WebContentsState&
TestNavigationObserver::WebContentsState::operator=(WebContentsState&& other) =
default;
TestNavigationObserver::WebContentsState::~WebContentsState() = default;
TestNavigationObserver::TestNavigationObserver(
WebContents* web_contents,
int expected_number_of_navigations,
MessageLoopRunner::QuitMode quit_mode,
bool ignore_uncommitted_navigations)
: TestNavigationObserver(web_contents,
expected_number_of_navigations,
absl::nullopt /* target_url */,
absl::nullopt /* target_error */,
quit_mode,
ignore_uncommitted_navigations) {}
TestNavigationObserver::TestNavigationObserver(
WebContents* web_contents,
MessageLoopRunner::QuitMode quit_mode,
bool ignore_uncommitted_navigations)
: TestNavigationObserver(web_contents,
1,
quit_mode,
ignore_uncommitted_navigations) {}
TestNavigationObserver::TestNavigationObserver(
WebContents* web_contents,
net::Error expected_target_error,
MessageLoopRunner::QuitMode quit_mode,
bool ignore_uncommitted_navigations)
: TestNavigationObserver(web_contents,
1 /* num_of_navigations */,
absl::nullopt,
expected_target_error,
quit_mode,
ignore_uncommitted_navigations) {}
TestNavigationObserver::TestNavigationObserver(
const GURL& expected_target_url,
MessageLoopRunner::QuitMode quit_mode,
bool ignore_uncommitted_navigations)
: TestNavigationObserver(nullptr,
1 /* num_of_navigations */,
expected_target_url,
absl::nullopt /* target_error */,
quit_mode,
ignore_uncommitted_navigations) {}
TestNavigationObserver::~TestNavigationObserver() = default;
void TestNavigationObserver::Wait() {
was_event_consumed_ = false;
TRACE_EVENT1("test", "TestNavigationObserver::Wait", "params",
[&](perfetto::TracedValue ctx) {
// TODO(crbug.com/1183371): Replace this with passing more
// parameters to TRACE_EVENT directly when available.
auto dict = std::move(ctx).WriteDictionary();
dict.Add("wait_event", wait_event_);
dict.Add("ignore_uncommitted_navigations",
ignore_uncommitted_navigations_);
dict.Add("expected_target_url", expected_target_url_);
dict.Add("expected_initial_url", expected_initial_url_);
dict.Add("expected_target_error", expected_target_error_);
});
message_loop_runner_->Run();
}
void TestNavigationObserver::WaitForNavigationFinished() {
wait_event_ = WaitEvent::kNavigationFinished;
Wait();
}
void TestNavigationObserver::StartWatchingNewWebContents() {
creation_subscription_ = RegisterWebContentsCreationCallback(
base::BindRepeating(&TestNavigationObserver::OnWebContentsCreated,
base::Unretained(this)));
}
void TestNavigationObserver::StopWatchingNewWebContents() {
creation_subscription_ = base::CallbackListSubscription();
}
void TestNavigationObserver::WatchExistingWebContents() {
for (auto* web_contents : WebContentsImpl::GetAllWebContents())
RegisterAsObserver(web_contents);
}
void TestNavigationObserver::RegisterAsObserver(WebContents* web_contents) {
web_contents_state_[web_contents].observer =
std::make_unique<TestWebContentsObserver>(this, web_contents);
}
TestNavigationObserver::TestNavigationObserver(
WebContents* web_contents,
int expected_number_of_navigations,
const absl::optional<GURL>& expected_target_url,
absl::optional<net::Error> expected_target_error,
MessageLoopRunner::QuitMode quit_mode,
bool ignore_uncommitted_navigations)
: wait_event_(WaitEvent::kLoadStopped),
navigations_completed_(0),
expected_number_of_navigations_(expected_number_of_navigations),
expected_target_url_(expected_target_url),
expected_initial_url_(absl::nullopt),
expected_target_error_(expected_target_error),
ignore_uncommitted_navigations_(ignore_uncommitted_navigations),
last_navigation_succeeded_(false),
last_net_error_code_(net::OK),
message_loop_runner_(new MessageLoopRunner(quit_mode)) {
if (web_contents)
RegisterAsObserver(web_contents);
}
void TestNavigationObserver::OnWebContentsCreated(WebContents* web_contents) {
RegisterAsObserver(web_contents);
}
void TestNavigationObserver::OnWebContentsDestroyed(
TestWebContentsObserver* observer,
WebContents* web_contents) {
auto web_contents_state_iter = web_contents_state_.find(web_contents);
DCHECK(web_contents_state_iter != web_contents_state_.end());
DCHECK_EQ(web_contents_state_iter->second.observer.get(), observer);
web_contents_state_.erase(web_contents_state_iter);
}
void TestNavigationObserver::OnNavigationEntryCommitted(
TestWebContentsObserver* observer,
WebContents* web_contents,
const LoadCommittedDetails& load_details) {
WebContentsState* web_contents_state = GetWebContentsState(web_contents);
web_contents_state->navigation_started = true;
}
void TestNavigationObserver::OnDidStartLoading(WebContents* web_contents) {
WebContentsState* web_contents_state = GetWebContentsState(web_contents);
web_contents_state->navigation_started = true;
}
void TestNavigationObserver::OnDidStopLoading(WebContents* web_contents) {
WebContentsState* web_contents_state = GetWebContentsState(web_contents);
if (!web_contents_state->navigation_started)
return;
if (wait_event_ == WaitEvent::kLoadStopped)
EventTriggered(web_contents_state);
}
void TestNavigationObserver::OnDidStartNavigation(
NavigationHandle* navigation_handle) {
if (expected_target_url_.has_value() &&
expected_target_url_.value() != navigation_handle->GetURL()) {
return;
}
if (!DoesNavigationMatchExpectedInitialUrl(
NavigationRequest::From(navigation_handle))) {
return;
}
WebContentsState* web_contents_state =
GetWebContentsState(navigation_handle->GetWebContents());
if (!web_contents_state->navigation_started)
return;
last_navigation_succeeded_ = false;
}
void TestNavigationObserver::OnDidFinishNavigation(
NavigationHandle* navigation_handle) {
if (ignore_uncommitted_navigations_ && !navigation_handle->HasCommitted())
return;
NavigationRequest* request = NavigationRequest::From(navigation_handle);
if (expected_target_url_.has_value() &&
expected_target_url_.value() != navigation_handle->GetURL()) {
return;
}
if (!DoesNavigationMatchExpectedInitialUrl(request))
return;
if (expected_target_error_.has_value() &&
expected_target_error_.value() != navigation_handle->GetNetErrorCode()) {
return;
}
WebContentsState* web_contents_state =
GetWebContentsState(navigation_handle->GetWebContents());
// TODO(crbug.com/1233764): It is generally the case that we've received load
// started events by this point, but we don't send load events for prerendered
// pages (by design). It's also the case that frame tree nodes don't report
// load start if the tree is already loading. For all of prerendering,
// subframes and fenced frames (i.e., the cases where we cannot rely on
// navigation_started being set correctly), we're not in the primary main
// frame, so the DCHECK has been updated to ignore these cases. We also only
// enforce this check if we haven't already called EventTriggered (since this
// will reset navigation_started and can cause errors in subsequent
// DidFinishNavigation calls). All this being said, we should, in general,
// move away from NotificationService and related events.
DCHECK(was_event_consumed_ || !navigation_handle->IsInPrimaryMainFrame() ||
web_contents_state->navigation_started);
if (HasFilter())
web_contents_state->last_navigation_matches_filter = true;
last_navigation_url_ = navigation_handle->GetURL();
last_navigation_initiator_origin_ = request->common_params().initiator_origin;
last_initiator_frame_token_ = navigation_handle->GetInitiatorFrameToken();
last_initiator_process_id_ = navigation_handle->GetInitiatorProcessId();
last_navigation_succeeded_ =
navigation_handle->HasCommitted() && !navigation_handle->IsErrorPage();
last_navigation_initiator_activation_and_ad_status_ =
navigation_handle->GetNavigationInitiatorActivationAndAdStatus();
last_net_error_code_ = navigation_handle->GetNetErrorCode();
last_nav_entry_id_ =
NavigationRequest::From(navigation_handle)->nav_entry_id();
last_source_site_instance_ = navigation_handle->GetSourceSiteInstance();
next_page_ukm_source_id_ = navigation_handle->GetNextPageUkmSourceId();
// Allow extending classes to fetch data available via navigation_handle.
NavigationOfInterestDidFinish(navigation_handle);
if (wait_event_ == WaitEvent::kNavigationFinished)
EventTriggered(web_contents_state);
}
void TestNavigationObserver::NavigationOfInterestDidFinish(NavigationHandle*) {
// Nothing in the base class.
}
void TestNavigationObserver::EventTriggered(
WebContentsState* web_contents_state) {
if (HasFilter() && !web_contents_state->last_navigation_matches_filter)
return;
DCHECK_GE(navigations_completed_, 0);
++navigations_completed_;
if (navigations_completed_ != expected_number_of_navigations_) {
return;
}
was_event_consumed_ = true;
web_contents_state->navigation_started = false;
message_loop_runner_->Quit();
}
bool TestNavigationObserver::DoesNavigationMatchExpectedInitialUrl(
NavigationRequest* navigation_request) {
if (!expected_initial_url_.has_value())
return true;
// Find the real URL being navigated to (e.g. stripping the "view-source:"
// prefix if necessary).
GURL expected_url = *expected_initial_url_;
BrowserContext* browser_context = navigation_request->frame_tree_node()
->navigator()
.controller()
.GetBrowserContext();
BrowserURLHandler::GetInstance()->RewriteURLIfNecessary(&expected_url,
browser_context);
// Debug URLs do not go through NavigationRequest and therefore cannot be used
// as an `expected_url`.
DCHECK(!blink::IsRendererDebugURL(expected_url));
GURL actual_url = navigation_request->GetOriginalRequestURL();
return actual_url == expected_url;
}
bool TestNavigationObserver::HasFilter() {
return expected_target_url_.has_value() ||
expected_initial_url_.has_value() ||
expected_target_error_.has_value();
}
TestNavigationObserver::WebContentsState*
TestNavigationObserver::GetWebContentsState(WebContents* web_contents) {
auto web_contents_state_iter = web_contents_state_.find(web_contents);
DCHECK(web_contents_state_iter != web_contents_state_.end());
return &(web_contents_state_iter->second);
}
} // namespace content