blob: 0839afbe0ca1fe72ff87c694e1ca6d671bdb2be6 [file] [log] [blame]
paulmeyerda992f52017-01-27 17:11:281// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/command_line.h"
Lukasz Anforowiczebee674e2020-01-08 05:26:316#include "base/files/file_util.h"
7#include "base/path_service.h"
Jan Wilken Dörrief05bb102020-08-18 19:35:568#include "base/strings/string_util.h"
Lei Zhange02299a2021-04-26 23:12:249#include "base/strings/stringprintf.h"
paulmeyerda992f52017-01-27 17:11:2810#include "base/strings/utf_string_conversions.h"
K. Moon565fc1c8e2020-09-10 15:53:1411#include "base/test/scoped_feature_list.h"
Lukasz Anforowiczebee674e2020-01-08 05:26:3112#include "base/threading/thread_restrictions.h"
Takumi Fujimotoa5094102019-12-03 23:45:1313#include "build/build_config.h"
paulmeyerda992f52017-01-27 17:11:2814#include "chrome/browser/pdf/pdf_extension_test_util.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/tabs/tab_strip_model.h"
17#include "chrome/test/base/in_process_browser_test.h"
18#include "chrome/test/base/ui_test_utils.h"
paulmeyerfeafc2d2017-04-25 21:46:4019#include "content/public/browser/browser_context.h"
20#include "content/public/browser/browser_plugin_guest_manager.h"
Lukasz Anforowiczebee674e2020-01-08 05:26:3121#include "content/public/browser/navigation_controller.h"
22#include "content/public/browser/web_contents.h"
23#include "content/public/common/content_paths.h"
paulmeyerda992f52017-01-27 17:11:2824#include "content/public/common/content_switches.h"
Peter Kasting919ce652020-05-07 10:22:3625#include "content/public/test/browser_test.h"
paulmeyerda992f52017-01-27 17:11:2826#include "content/public/test/browser_test_utils.h"
27#include "content/public/test/find_test_utils.h"
28#include "content/public/test/test_navigation_observer.h"
29#include "net/dns/mock_host_resolver.h"
Lukasz Anforowiczebee674e2020-01-08 05:26:3130#include "net/test/embedded_test_server/controllable_http_response.h"
31#include "pdf/document_loader_impl.h"
K. Moon565fc1c8e2020-09-10 15:53:1432#include "pdf/pdf_features.h"
Rakina Zata Amni3f77dff2018-09-08 16:19:4333#include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
paulmeyerda992f52017-01-27 17:11:2834
35namespace content {
36
37class ChromeFindRequestManagerTest : public InProcessBrowserTest {
38 public:
39 ChromeFindRequestManagerTest()
40 : normal_delegate_(nullptr),
41 last_request_id_(0) {}
42 ~ChromeFindRequestManagerTest() override {}
43
44 void SetUpOnMainThread() override {
45 host_resolver()->AddRule("*", "127.0.0.1");
46 embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
paulmeyerda992f52017-01-27 17:11:2847
48 // Swap the WebContents's delegate for our test delegate.
49 normal_delegate_ = contents()->GetDelegate();
50 contents()->SetDelegate(&test_delegate_);
51 }
52
53 void TearDownOnMainThread() override {
54 // Swap the WebContents's delegate back to its usual delegate.
55 contents()->SetDelegate(normal_delegate_);
56 }
57
58 protected:
59 // Navigates to |url| and waits for it to finish loading.
60 void LoadAndWait(const std::string& url) {
61 TestNavigationObserver navigation_observer(contents());
62 ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
63 browser(), embedded_test_server()->GetURL("a.com", url), 1);
64 ASSERT_TRUE(navigation_observer.last_navigation_succeeded());
65 }
66
67 void Find(const std::string& search_text,
Rakina Zata Amni3f77dff2018-09-08 16:19:4368 blink::mojom::FindOptionsPtr options) {
paulmeyerda992f52017-01-27 17:11:2869 delegate()->UpdateLastRequest(++last_request_id_);
Rakina Zata Amni3f77dff2018-09-08 16:19:4370 contents()->Find(last_request_id_, base::UTF8ToUTF16(search_text),
71 std::move(options));
paulmeyerda992f52017-01-27 17:11:2872 }
73
74 WebContents* contents() const {
75 return browser()->tab_strip_model()->GetActiveWebContents();
76 }
77
78 FindTestWebContentsDelegate* delegate() const {
79 return static_cast<FindTestWebContentsDelegate*>(contents()->GetDelegate());
80 }
81
82 int last_request_id() const {
83 return last_request_id_;
84 }
85
86 private:
87 FindTestWebContentsDelegate test_delegate_;
88 WebContentsDelegate* normal_delegate_;
89
90 // The ID of the last find request requested.
91 int last_request_id_;
92
93 DISALLOW_COPY_AND_ASSIGN(ChromeFindRequestManagerTest);
94};
95
K. Moon565fc1c8e2020-09-10 15:53:1496class ChromeFindRequestManagerTestWithPdfPartialLoading
97 : public ChromeFindRequestManagerTest {
98 public:
99 ChromeFindRequestManagerTestWithPdfPartialLoading() {
100 feature_list_.InitWithFeatures(
101 {chrome_pdf::features::kPdfIncrementalLoading,
102 chrome_pdf::features::kPdfPartialLoading},
103 {});
104 }
105
106 private:
107 base::test::ScopedFeatureList feature_list_;
108};
109
paulmeyerda992f52017-01-27 17:11:28110// Tests searching in a full-page PDF.
Takumi Fujimotoa5094102019-12-03 23:45:13111// Flaky on Windows ASAN: crbug.com/1030368.
112#if defined(OS_WIN) && defined(ADDRESS_SANITIZER)
113#define MAYBE_FindInPDF DISABLED_FindInPDF
114#else
115#define MAYBE_FindInPDF FindInPDF
116#endif
117IN_PROC_BROWSER_TEST_F(ChromeFindRequestManagerTest, MAYBE_FindInPDF) {
Lukasz Anforowiczebee674e2020-01-08 05:26:31118 ASSERT_TRUE(embedded_test_server()->Start());
paulmeyerda992f52017-01-27 17:11:28119 LoadAndWait("/find_in_pdf_page.pdf");
paulmeyerfeafc2d2017-04-25 21:46:40120 ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(contents()));
paulmeyerda992f52017-01-27 17:11:28121
Rakina Zata Amni3f77dff2018-09-08 16:19:43122 auto options = blink::mojom::FindOptions::New();
Rakina Zata Amni3f77dff2018-09-08 16:19:43123 Find("result", options.Clone());
W. James MacLean031de922020-03-26 18:39:31124 delegate()->MarkNextReply();
125 delegate()->WaitForNextReply();
126
Russell Davis8a36226c2020-06-05 17:09:50127 options->new_session = false;
Rakina Zata Amni3f77dff2018-09-08 16:19:43128 Find("result", options.Clone());
W. James MacLean031de922020-03-26 18:39:31129 delegate()->MarkNextReply();
130 delegate()->WaitForNextReply();
131
Rakina Zata Amni3f77dff2018-09-08 16:19:43132 Find("result", options.Clone());
paulmeyerda992f52017-01-27 17:11:28133 delegate()->WaitForFinalReply();
134
135 FindResults results = delegate()->GetFindResults();
136 EXPECT_EQ(last_request_id(), results.request_id);
137 EXPECT_EQ(5, results.number_of_matches);
138 EXPECT_EQ(3, results.active_match_ordinal);
139}
140
Lukasz Anforowiczebee674e2020-01-08 05:26:31141void SendRangeResponse(net::test_server::ControllableHttpResponse* response,
142 const std::string& pdf_contents) {
143 int range_start = -1;
144 int range_end = -1;
145 {
146 auto it = response->http_request()->headers.find("Range");
147 ASSERT_NE(response->http_request()->headers.end(), it);
148 base::StringPiece range_header = it->second;
149 base::StringPiece kBytesPrefix = "bytes=";
Jan Wilken Dörrief05bb102020-08-18 19:35:56150 ASSERT_TRUE(base::StartsWith(range_header, kBytesPrefix));
Lukasz Anforowiczebee674e2020-01-08 05:26:31151 range_header.remove_prefix(kBytesPrefix.size());
152 auto dash_pos = range_header.find('-');
153 ASSERT_NE(std::string::npos, dash_pos);
154 ASSERT_LT(0u, dash_pos);
155 ASSERT_LT(dash_pos, range_header.size() - 1);
156 ASSERT_TRUE(
157 base::StringToInt(range_header.substr(0, dash_pos), &range_start));
158 ASSERT_TRUE(
159 base::StringToInt(range_header.substr(dash_pos + 1), &range_end));
160 }
161 ASSERT_LT(0, range_start);
162 ASSERT_LT(range_start, range_end);
163 ASSERT_LT(static_cast<size_t>(range_end), pdf_contents.size());
164 int range_length = range_end - range_start + 1;
165 response->Send("HTTP/1.1 206 Partial Content\r\n");
166 response->Send(base::StringPrintf("Content-Range: bytes %d-%d/%zu\r\n",
167 range_start, range_end,
168 pdf_contents.size()));
169 response->Send(base::StringPrintf("Content-Length: %d\r\n", range_length));
170 response->Send("\r\n");
171 response->Send(pdf_contents.substr(range_start, range_length));
172 response->Done();
173}
174
175// Tests searching in a PDF received in chunks via range-requests. See also
176// https://crbug.com/1027173.
K. Moon565fc1c8e2020-09-10 15:53:14177IN_PROC_BROWSER_TEST_F(ChromeFindRequestManagerTestWithPdfPartialLoading,
178 FindInChunkedPDF) {
Lukasz Anforowiczebee674e2020-01-08 05:26:31179 constexpr uint32_t kStalledResponseSize =
180 chrome_pdf::DocumentLoaderImpl::kDefaultRequestSize + 123;
181
182 // Load contents of a big, linearized pdf test file.
183 // See also //content/test/data/linearized.pdf.README file.
184 std::string pdf_contents;
185 {
186 base::ScopedAllowBlockingForTesting allow_blocking_io;
187 base::FilePath content_test_dir;
188 ASSERT_TRUE(
189 base::PathService::Get(content::DIR_TEST_DATA, &content_test_dir));
190 base::FilePath real_pdf_path =
191 content_test_dir.AppendASCII("linearized.pdf");
192 ASSERT_TRUE(base::ReadFileToString(real_pdf_path, &pdf_contents));
193 }
194 DCHECK_GT(pdf_contents.size(), kStalledResponseSize);
195
196 // Set up handling of HTTP responses from within the test.
197 const char kSimulatedPdfPath[] = "/simulated/chunked.pdf";
198 net::test_server::ControllableHttpResponse nav_response(
199 embedded_test_server(), kSimulatedPdfPath);
200 net::test_server::ControllableHttpResponse range_response1(
201 embedded_test_server(), kSimulatedPdfPath);
202 net::test_server::ControllableHttpResponse range_response2(
203 embedded_test_server(), kSimulatedPdfPath);
204 ASSERT_TRUE(embedded_test_server()->Start());
205 GURL pdf_url = embedded_test_server()->GetURL("a.com", kSimulatedPdfPath);
206
207 // Kick-off browser-initiated navigation to a PDF file.
208 content::WebContents* web_contents =
209 browser()->tab_strip_model()->GetActiveWebContents();
210 TestNavigationObserver navigation_observer(web_contents);
211 content::NavigationController::LoadURLParams params(pdf_url);
212 params.transition_type = ui::PageTransitionFromInt(
213 ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
214 web_contents->GetController().LoadURLWithParams(params);
215
216 // Have the test HTTP server handle the 1st request (navigation). This
217 // request is handler in the test, rather than by embedded_test_server, to
218 // stall the request after it delivers the first kStalledResponseSize bytes of
219 // data (the stalling ensures that the range request will be processed in the
220 // next test steps).
221 nav_response.WaitForRequest();
222 nav_response.Send("HTTP/1.1 200 OK\r\n");
223 nav_response.Send("Accept-Ranges: bytes\r\n");
224 nav_response.Send(
225 base::StringPrintf("Content-Length: %zu\r\n", pdf_contents.size()));
226 nav_response.Send("Content-Type: application/pdf\r\n");
227 nav_response.Send("Pragma: no-cache\r\n");
228 nav_response.Send("Cache-Control: no-cache, no-store, must-revalidate\r\n");
229 nav_response.Send("\r\n");
230 nav_response.Send(pdf_contents.substr(0, kStalledResponseSize));
231
232 // At this point the navigation should be considered successful (even though
233 // we haven't loaded all the bytes of the PDF yet).
234 navigation_observer.Wait();
235 ASSERT_TRUE(navigation_observer.last_navigation_succeeded());
236
237 // Have the test handle the 2 range requests (subresource requests initiated
238 // by the PDF plugin and proxied through a renderer process for
239 // MimeHandlerView extension). These requests are handled in the test, rather
240 // than by embedded_test_server, to verify that we are indeed getting range
241 // requests (i.e. this is a sanity check that the test still tests the right
242 // thing).
243 range_response1.WaitForRequest();
244 SendRangeResponse(&range_response1, pdf_contents);
245 range_response2.WaitForRequest();
246 SendRangeResponse(&range_response2, pdf_contents);
247
248 // Finish the first HTTP response and verify that the PDF has loaded
249 // successfully.
250 nav_response.Done();
251 ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(contents()));
252
253 // Verify that find-in-page works fine.
254 auto options = blink::mojom::FindOptions::New();
Lukasz Anforowiczebee674e2020-01-08 05:26:31255 Find("FXCMAP_CMap", options.Clone());
Russell Davis8a36226c2020-06-05 17:09:50256 options->new_session = false;
Lukasz Anforowiczebee674e2020-01-08 05:26:31257 Find("FXCMAP_CMap", options.Clone());
258 Find("FXCMAP_CMap", options.Clone());
259 delegate()->WaitForFinalReply();
260
261 FindResults results = delegate()->GetFindResults();
262 EXPECT_EQ(last_request_id(), results.request_id);
263 EXPECT_EQ(15, results.number_of_matches);
264 EXPECT_EQ(3, results.active_match_ordinal);
265}
266
paulmeyerfeafc2d2017-04-25 21:46:40267// Tests searching in a page with embedded PDFs. Note that this test, the
268// FindInPDF test, and the find tests in web_view_browsertest.cc ensure that
269// find-in-page works across GuestViews.
270//
271// TODO(paulmeyer): Note that this is left disabled for now since
272// EnsurePDFHasLoaded() currently does not work for embedded PDFs. This will be
273// fixed and enabled in a subsequent patch.
274IN_PROC_BROWSER_TEST_F(ChromeFindRequestManagerTest,
275 DISABLED_FindInEmbeddedPDFs) {
Lukasz Anforowiczebee674e2020-01-08 05:26:31276 ASSERT_TRUE(embedded_test_server()->Start());
paulmeyerfeafc2d2017-04-25 21:46:40277 LoadAndWait("/find_in_embedded_pdf_page.html");
278 ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(contents()));
279
Rakina Zata Amni3f77dff2018-09-08 16:19:43280 auto options = blink::mojom::FindOptions::New();
Russell Davis8a36226c2020-06-05 17:09:50281 options->new_session = false;
Rakina Zata Amni3f77dff2018-09-08 16:19:43282 Find("result", options.Clone());
283 options->forward = false;
284 Find("result", options.Clone());
285 Find("result", options.Clone());
286 Find("result", options.Clone());
paulmeyerfeafc2d2017-04-25 21:46:40287 delegate()->WaitForFinalReply();
288
289 FindResults results = delegate()->GetFindResults();
290 EXPECT_EQ(last_request_id(), results.request_id);
291 EXPECT_EQ(13, results.number_of_matches);
292 EXPECT_EQ(11, results.active_match_ordinal);
293}
294
Manoj Biswas7a1f1fb2019-07-18 18:17:41295IN_PROC_BROWSER_TEST_F(ChromeFindRequestManagerTest, FindMissingStringInPDF) {
Lukasz Anforowiczebee674e2020-01-08 05:26:31296 ASSERT_TRUE(embedded_test_server()->Start());
Manoj Biswas7a1f1fb2019-07-18 18:17:41297 LoadAndWait("/find_in_pdf_page.pdf");
298 ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(contents()));
299
300 auto options = blink::mojom::FindOptions::New();
Manoj Biswas7a1f1fb2019-07-18 18:17:41301 Find("missing", options.Clone());
302 delegate()->WaitForFinalReply();
303
304 FindResults results = delegate()->GetFindResults();
305 EXPECT_EQ(last_request_id(), results.request_id);
306 EXPECT_EQ(0, results.number_of_matches);
307 EXPECT_EQ(0, results.active_match_ordinal);
308}
309
310// Tests searching for a word character-by-character, as would typically be
311// done by a user typing into the find bar.
312IN_PROC_BROWSER_TEST_F(ChromeFindRequestManagerTest,
313 CharacterByCharacterFindInPDF) {
Lukasz Anforowiczebee674e2020-01-08 05:26:31314 ASSERT_TRUE(embedded_test_server()->Start());
Manoj Biswas7a1f1fb2019-07-18 18:17:41315 LoadAndWait("/find_in_pdf_page.pdf");
316 ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(contents()));
317
318 auto options = blink::mojom::FindOptions::New();
Manoj Biswas7a1f1fb2019-07-18 18:17:41319 Find("r", options.Clone());
320 delegate()->MarkNextReply();
321 delegate()->WaitForNextReply();
322 Find("re", options.Clone());
323 delegate()->MarkNextReply();
324 delegate()->WaitForNextReply();
325 Find("res", options.Clone());
326 delegate()->MarkNextReply();
327 delegate()->WaitForNextReply();
328 Find("resu", options.Clone());
329 delegate()->MarkNextReply();
330 delegate()->WaitForNextReply();
331 Find("resul", options.Clone());
332 delegate()->MarkNextReply();
333 delegate()->WaitForNextReply();
334 Find("result", options.Clone());
335 delegate()->WaitForFinalReply();
336
337 FindResults results = delegate()->GetFindResults();
338 EXPECT_EQ(last_request_id(), results.request_id);
339 EXPECT_EQ(5, results.number_of_matches);
340 EXPECT_EQ(1, results.active_match_ordinal);
341}
342
paulmeyerda992f52017-01-27 17:11:28343} // namespace content