1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/ukm/ios/ukm_url_recorder.h"
#include <optional>
#include "base/functional/bind.h"
#import "base/test/ios/wait_util.h"
#include "components/ukm/test_ukm_recorder.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/web_state.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "url/gurl.h"
namespace ukm {
namespace {
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
if (request.GetURL().path() == "/title1.html") {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_content_type("text/html");
result->set_content("<html><head></head><body>NoTitle</body></html>");
return std::move(result);
}
if (request.GetURL().path() == "/page_with_iframe.html") {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_content_type("text/html");
result->set_content(
"<html><head></head><body><iframe src=\"title1.html\"></body></html>");
return std::move(result);
}
if (request.GetURL().path() == "/download") {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_content_type("application/vnd.test");
result->set_content("TestDownloadContent");
return std::move(result);
}
if (request.GetURL().path() == "/redirect") {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_code(net::HTTP_MOVED_PERMANENTLY);
result->AddCustomHeader("Location", "/title1.html");
return std::move(result);
}
return nullptr;
}
} // namespace
class UkmUrlRecorderTest : public web::WebTestWithWebState {
protected:
UkmUrlRecorderTest() {
server_.RegisterDefaultHandler(base::BindRepeating(&HandleRequest));
}
void SetUp() override {
web::WebTestWithWebState::SetUp();
ASSERT_TRUE(server_.Start());
ukm::InitializeSourceUrlRecorderForWebState(web_state());
}
bool LoadUrlAndWait(const GURL& url) {
web::NavigationManager::WebLoadParams params(url);
web_state()->GetNavigationManager()->LoadURLWithParams(params);
return base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForPageLoadTimeout, ^{
return !web_state()->IsLoading();
});
}
void MaybeRecordUrl(web::NavigationContext* context, const GURL& url) {
internal::SourceUrlRecorderWebStateObserver* observer =
GetSourceUrlRecorderForWebStateForWebState(web_state());
observer->MaybeRecordUrl(context, url);
}
testing::AssertionResult RecordedUrl(
ukm::SourceId source_id,
GURL expected_url,
std::optional<GURL> expected_initial_url) {
auto* source = test_ukm_recorder_.GetSourceForSourceId(source_id);
if (!source)
return testing::AssertionFailure() << "No URL recorded";
if (source->url() != expected_url)
return testing::AssertionFailure()
<< "Url was " << source->url() << ", expected: " << expected_url;
std::optional<GURL> initial_url;
if (source->urls().size() > 1u)
initial_url = source->urls().front();
if (expected_initial_url != initial_url) {
return testing::AssertionFailure()
<< "Initial Url was " << initial_url.value_or(GURL())
<< ", expected: " << expected_initial_url.value_or(GURL());
}
return testing::AssertionSuccess();
}
testing::AssertionResult DidNotRecordUrl(GURL url) {
const auto& sources = test_ukm_recorder_.GetSources();
for (const auto& kv : sources) {
if (kv.second->url() == url)
return testing::AssertionFailure()
<< "Url " << url << " was recorded with SourceId: " << kv.first;
if (kv.second->url() == url)
return testing::AssertionFailure()
<< "Url " << url
<< " was recorded as an initial URL with SourceId: " << kv.first;
}
return testing::AssertionSuccess();
}
protected:
net::EmbeddedTestServer server_;
ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
};
// Tests that URLs get recorded for pages visited.
TEST_F(UkmUrlRecorderTest, Basic) {
GURL url = server_.GetURL("/title1.html");
EXPECT_TRUE(LoadUrlAndWait(url));
ukm::SourceId source_id = ukm::GetSourceIdForWebStateDocument(web_state());
EXPECT_TRUE(RecordedUrl(source_id, url, std::nullopt));
}
// Tests that subframe URLs do not get recorded.
TEST_F(UkmUrlRecorderTest, IgnoreUrlInSubframe) {
GURL main_url = server_.GetURL("/page_with_iframe.html");
GURL subframe_url = server_.GetURL("/title1.html");
EXPECT_TRUE(LoadUrlAndWait(main_url));
ukm::SourceId source_id = ukm::GetSourceIdForWebStateDocument(web_state());
EXPECT_TRUE(RecordedUrl(source_id, main_url, std::nullopt));
EXPECT_TRUE(DidNotRecordUrl(subframe_url));
}
// Tests that download URLs do not get recorded.
TEST_F(UkmUrlRecorderTest, IgnoreDownloadUrl) {
GURL url = server_.GetURL("/download");
EXPECT_TRUE(LoadUrlAndWait(url));
EXPECT_TRUE(DidNotRecordUrl(url));
}
// Tests that redirected URLs record initial and final URL.
TEST_F(UkmUrlRecorderTest, InitialUrl) {
GURL redirect_url = server_.GetURL("/redirect");
GURL target_url = server_.GetURL("/title1.html");
EXPECT_TRUE(LoadUrlAndWait(redirect_url));
ukm::SourceId source_id = ukm::GetSourceIdForWebStateDocument(web_state());
EXPECT_TRUE(RecordedUrl(source_id, target_url, redirect_url));
}
// Checks that if a NavigationContext is erroneously reused, its reuse is
// handled gracefully by the UKM recorder. See crbug.com/346017703.
TEST_F(UkmUrlRecorderTest, ReusedNavigationContext) {
EXPECT_EQ(0u, test_ukm_recorder_.sources_count());
web::FakeNavigationContext context_1;
const int64_t navigation_id_1 = context_1.GetNavigationId();
const GURL url_1("https://example.com#foo");
context_1.SetIsSameDocument(false);
context_1.SetUrl(url_1);
MaybeRecordUrl(&context_1, url_1);
EXPECT_EQ(1u, test_ukm_recorder_.sources_count());
web::FakeNavigationContext context_2;
const int64_t navigation_id_2 = context_2.GetNavigationId();
const GURL url_2("https://example.com#bar");
context_2.SetIsSameDocument(false);
context_2.SetUrl(url_2);
EXPECT_NE(navigation_id_1, navigation_id_2);
MaybeRecordUrl(&context_2, url_2);
EXPECT_EQ(2u, test_ukm_recorder_.sources_count());
// If a NavigationContext is reused, UKM recorder should not crash on
// receiving the same navigation id, hence also the UKM source id, again.
// Instead no new source should be added to the UKM recorder.
MaybeRecordUrl(&context_1, url_1);
EXPECT_EQ(2u, test_ukm_recorder_.sources_count());
}
} // namespace ukm
|