find_in_page_js_unittest.mm 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. // Copyright 2019 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. #import <UIKit/UIKit.h>
  5. #import <WebKit/WebKit.h>
  6. #include "base/bind.h"
  7. #include "base/callback.h"
  8. #include "base/run_loop.h"
  9. #import "base/test/ios/wait_util.h"
  10. #import "ios/web/find_in_page/find_in_page_constants.h"
  11. #import "ios/web/find_in_page/find_in_page_java_script_feature.h"
  12. #import "ios/web/js_messaging/java_script_feature_manager.h"
  13. #include "ios/web/js_messaging/web_frame_impl.h"
  14. #import "ios/web/public/js_messaging/web_frame.h"
  15. #import "ios/web/public/js_messaging/web_frames_manager.h"
  16. #import "ios/web/public/test/js_test_util.h"
  17. #import "ios/web/public/test/web_test_with_web_state.h"
  18. #import "ios/web/public/ui/crw_web_view_proxy.h"
  19. #import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
  20. #import "ios/web/public/web_state.h"
  21. #import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
  22. #include "testing/gtest_mac.h"
  23. #if !defined(__has_feature) || !__has_feature(objc_arc)
  24. #error "This file requires ARC support."
  25. #endif
  26. using base::test::ios::kWaitForJSCompletionTimeout;
  27. using base::test::ios::kWaitForPageLoadTimeout;
  28. using base::test::ios::WaitUntilConditionOrTimeout;
  29. namespace {
  30. // Find strings.
  31. const char kFindStringFoo[] = "foo";
  32. const char kFindString12345[] = "12345";
  33. // Pump search timeout in milliseconds.
  34. const double kPumpSearchTimeout = 100.0;
  35. } // namespace
  36. namespace web {
  37. // Calls FindInPage Javascript handlers and checks that return values are
  38. // correct.
  39. class FindInPageJsTest : public WebTestWithWebState {
  40. protected:
  41. void SetUp() override {
  42. WebTestWithWebState::SetUp();
  43. WKWebViewConfigurationProvider& configuration_provider =
  44. WKWebViewConfigurationProvider::FromBrowserState(GetBrowserState());
  45. // Force the creation of the content worlds.
  46. configuration_provider.GetWebViewConfiguration();
  47. content_world_ =
  48. JavaScriptFeatureManager::FromBrowserState(GetBrowserState())
  49. ->GetContentWorldForFeature(
  50. FindInPageJavaScriptFeature::GetInstance());
  51. }
  52. bool WaitForWebFramesCount(unsigned long web_frames_count) {
  53. return WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  54. return all_web_frames().size() == web_frames_count;
  55. });
  56. }
  57. // Returns all web frames for |web_state()|.
  58. std::set<WebFrameImpl*> all_web_frames() {
  59. std::set<WebFrameImpl*> frames;
  60. for (WebFrame* frame :
  61. web_state()->GetWebFramesManager()->GetAllWebFrames()) {
  62. frames.insert(static_cast<WebFrameImpl*>(frame));
  63. }
  64. return frames;
  65. }
  66. // Returns main frame for |web_state_|.
  67. WebFrameInternal* main_web_frame() {
  68. WebFrame* main_frame =
  69. web_state()->GetWebFramesManager()->GetMainWebFrame();
  70. return main_frame->GetWebFrameInternal();
  71. }
  72. JavaScriptContentWorld* content_world_;
  73. };
  74. // Tests that FindInPage searches in main frame containing a match and responds
  75. // with 1 match.
  76. TEST_F(FindInPageJsTest, FindText) {
  77. ASSERT_TRUE(LoadHtml("<span>foo</span>"));
  78. ASSERT_TRUE(WaitForWebFramesCount(1));
  79. const base::TimeDelta kCallJavascriptFunctionTimeout =
  80. base::Seconds(kWaitForJSCompletionTimeout);
  81. __block bool message_received = false;
  82. std::vector<base::Value> params;
  83. params.push_back(base::Value(kFindStringFoo));
  84. params.push_back(base::Value(kPumpSearchTimeout));
  85. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  86. kFindInPageSearch, params, content_world_,
  87. base::BindOnce(^(const base::Value* result) {
  88. ASSERT_TRUE(result);
  89. ASSERT_TRUE(result->is_double());
  90. double count = result->GetDouble();
  91. ASSERT_EQ(1.0, count);
  92. message_received = true;
  93. }),
  94. kCallJavascriptFunctionTimeout);
  95. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  96. return message_received;
  97. }));
  98. }
  99. // Tests that FindInPage searches in main frame for text that exists but is
  100. // hidden and responds with 0 matches.
  101. TEST_F(FindInPageJsTest, FindTextNoResults) {
  102. ASSERT_TRUE(LoadHtml("<span style='display:none'>foo</span>"));
  103. ASSERT_TRUE(WaitForWebFramesCount(1));
  104. const base::TimeDelta kCallJavascriptFunctionTimeout =
  105. base::Seconds(kWaitForJSCompletionTimeout);
  106. __block bool message_received = false;
  107. std::vector<base::Value> params;
  108. params.push_back(base::Value(kFindStringFoo));
  109. params.push_back(base::Value(kPumpSearchTimeout));
  110. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  111. kFindInPageSearch, params, content_world_,
  112. base::BindOnce(^(const base::Value* result) {
  113. ASSERT_TRUE(result);
  114. ASSERT_TRUE(result->is_double());
  115. double count = result->GetDouble();
  116. ASSERT_EQ(0.0, count);
  117. message_received = true;
  118. }),
  119. kCallJavascriptFunctionTimeout);
  120. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  121. return message_received;
  122. }));
  123. }
  124. // Tests that FindInPage doesn't search in noscript elements.
  125. TEST_F(FindInPageJsTest, FindTextIgnoresNoscript) {
  126. ASSERT_TRUE(LoadHtml("<body><noscript>foo</noscript></body>"));
  127. ASSERT_TRUE(WaitForWebFramesCount(1));
  128. const base::TimeDelta kCallJavascriptFunctionTimeout =
  129. base::Seconds(kWaitForJSCompletionTimeout);
  130. __block bool message_received = false;
  131. std::vector<base::Value> params;
  132. params.push_back(base::Value(kFindStringFoo));
  133. params.push_back(base::Value(kPumpSearchTimeout));
  134. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  135. kFindInPageSearch, params, content_world_,
  136. base::BindOnce(^(const base::Value* result) {
  137. ASSERT_TRUE(result);
  138. ASSERT_TRUE(result->is_double());
  139. double count = result->GetDouble();
  140. ASSERT_EQ(0.0, count);
  141. message_received = true;
  142. }),
  143. kCallJavascriptFunctionTimeout);
  144. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  145. return message_received;
  146. }));
  147. }
  148. // Tests that FindInPage searches in child iframe and asserts that a result was
  149. // found.
  150. TEST_F(FindInPageJsTest, FindIFrameText) {
  151. ASSERT_TRUE(WebTestWithWebState::LoadHtml(
  152. "<iframe "
  153. "srcdoc='<html><body><span>foo</span></body></html>'></iframe>"));
  154. ASSERT_TRUE(WaitForWebFramesCount(2));
  155. const base::TimeDelta kCallJavascriptFunctionTimeout =
  156. base::Seconds(kWaitForJSCompletionTimeout);
  157. std::set<WebFrameImpl*> all_frames = all_web_frames();
  158. __block bool message_received = false;
  159. WebFrameInternal* child_frame = nullptr;
  160. for (auto* frame : all_frames) {
  161. if (frame->IsMainFrame()) {
  162. continue;
  163. }
  164. child_frame = frame->GetWebFrameInternal();
  165. }
  166. ASSERT_TRUE(child_frame);
  167. std::vector<base::Value> params;
  168. params.push_back(base::Value(kFindStringFoo));
  169. params.push_back(base::Value(kPumpSearchTimeout));
  170. child_frame->CallJavaScriptFunctionInContentWorld(
  171. kFindInPageSearch, params, content_world_,
  172. base::BindOnce(^(const base::Value* result) {
  173. ASSERT_TRUE(result);
  174. ASSERT_TRUE(result->is_double());
  175. double count = result->GetDouble();
  176. ASSERT_EQ(1.0, count);
  177. message_received = true;
  178. }),
  179. kCallJavascriptFunctionTimeout);
  180. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  181. return message_received;
  182. }));
  183. }
  184. // Tests that FindInPage works when searching for white space.
  185. TEST_F(FindInPageJsTest, FindWhiteSpace) {
  186. ASSERT_TRUE(LoadHtml("<span> </span>"));
  187. ASSERT_TRUE(WaitForWebFramesCount(1));
  188. const base::TimeDelta kCallJavascriptFunctionTimeout =
  189. base::Seconds(kWaitForJSCompletionTimeout);
  190. __block bool message_received = false;
  191. std::vector<base::Value> params;
  192. params.push_back(base::Value(" "));
  193. params.push_back(base::Value(kPumpSearchTimeout));
  194. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  195. kFindInPageSearch, params, content_world_,
  196. base::BindOnce(^(const base::Value* result) {
  197. ASSERT_TRUE(result);
  198. ASSERT_TRUE(result->is_double());
  199. double count = result->GetDouble();
  200. ASSERT_EQ(1.0, count);
  201. message_received = true;
  202. }),
  203. kCallJavascriptFunctionTimeout);
  204. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  205. return message_received;
  206. }));
  207. }
  208. // Tests that FindInPage works when match results cover multiple HTML Nodes.
  209. TEST_F(FindInPageJsTest, FindAcrossMultipleNodes) {
  210. ASSERT_TRUE(
  211. LoadHtml("<p>xx1<span>2</span>3<a>4512345xxx12</a>34<a>5xxx12345xx</p>"));
  212. ASSERT_TRUE(WaitForWebFramesCount(1));
  213. const base::TimeDelta kCallJavascriptFunctionTimeout =
  214. base::Seconds(kWaitForJSCompletionTimeout);
  215. __block bool message_received = false;
  216. std::vector<base::Value> params;
  217. params.push_back(base::Value(kFindString12345));
  218. params.push_back(base::Value(kPumpSearchTimeout));
  219. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  220. kFindInPageSearch, params, content_world_,
  221. base::BindOnce(^(const base::Value* result) {
  222. ASSERT_TRUE(result);
  223. ASSERT_TRUE(result->is_double());
  224. double count = result->GetDouble();
  225. ASSERT_EQ(4.0, count);
  226. message_received = true;
  227. }),
  228. kCallJavascriptFunctionTimeout);
  229. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  230. return message_received;
  231. }));
  232. }
  233. // Tests that a FindInPage match can be highlighted and the correct
  234. // accessibility string is returned.
  235. TEST_F(FindInPageJsTest, FindHighlightMatch) {
  236. ASSERT_TRUE(LoadHtml("<span>some foo match</span>"));
  237. ASSERT_TRUE(WaitForWebFramesCount(1));
  238. const base::TimeDelta kCallJavascriptFunctionTimeout =
  239. base::Seconds(kWaitForJSCompletionTimeout);
  240. __block bool message_received = false;
  241. std::vector<base::Value> params;
  242. params.push_back(base::Value(kFindStringFoo));
  243. params.push_back(base::Value(kPumpSearchTimeout));
  244. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  245. kFindInPageSearch, params, content_world_,
  246. base::BindOnce(^(const base::Value* result) {
  247. ASSERT_TRUE(result);
  248. ASSERT_TRUE(result->is_double());
  249. double count = result->GetDouble();
  250. ASSERT_EQ(1.0, count);
  251. message_received = true;
  252. }),
  253. kCallJavascriptFunctionTimeout);
  254. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  255. return message_received;
  256. }));
  257. __block bool highlight_done = false;
  258. __block std::string context_string;
  259. std::vector<base::Value> highlight_params;
  260. highlight_params.push_back(base::Value(0));
  261. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  262. kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
  263. base::BindOnce(^(const base::Value* result) {
  264. highlight_done = true;
  265. context_string =
  266. result->FindKey(kSelectAndScrollResultContextString)->GetString();
  267. }),
  268. kCallJavascriptFunctionTimeout);
  269. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  270. return highlight_done;
  271. }));
  272. EXPECT_NSEQ(@1,
  273. ExecuteJavaScript(
  274. @"document.getElementsByClassName('find_selected').length"));
  275. EXPECT_EQ("some foo match", context_string);
  276. }
  277. // Tests that a FindInPage match can be highlighted and that a previous
  278. // highlight is removed when another match is highlighted.
  279. TEST_F(FindInPageJsTest, FindHighlightSeparateMatches) {
  280. ASSERT_TRUE(LoadHtml("<span>foo foo match</span>"));
  281. ASSERT_TRUE(WaitForWebFramesCount(1));
  282. const base::TimeDelta kCallJavascriptFunctionTimeout =
  283. base::Seconds(kWaitForJSCompletionTimeout);
  284. __block bool message_received = false;
  285. std::vector<base::Value> params;
  286. params.push_back(base::Value(kFindStringFoo));
  287. params.push_back(base::Value(kPumpSearchTimeout));
  288. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  289. kFindInPageSearch, params, content_world_,
  290. base::BindOnce(^(const base::Value* result) {
  291. ASSERT_TRUE(result);
  292. ASSERT_TRUE(result->is_double());
  293. double count = result->GetDouble();
  294. ASSERT_EQ(2.0, count);
  295. message_received = true;
  296. }),
  297. kCallJavascriptFunctionTimeout);
  298. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  299. return message_received;
  300. }));
  301. __block bool highlight_done = false;
  302. __block std::string context_string;
  303. std::vector<base::Value> highlight_params;
  304. highlight_params.push_back(base::Value(0));
  305. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  306. kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
  307. base::BindOnce(^(const base::Value* result) {
  308. highlight_done = true;
  309. context_string =
  310. result->FindKey(kSelectAndScrollResultContextString)->GetString();
  311. }),
  312. kCallJavascriptFunctionTimeout);
  313. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  314. return highlight_done;
  315. }));
  316. EXPECT_EQ("foo ", context_string);
  317. EXPECT_NSEQ(@1,
  318. ExecuteJavaScript(
  319. @"document.getElementsByClassName('find_selected').length"));
  320. highlight_done = false;
  321. std::vector<base::Value> highlight_second_params;
  322. highlight_second_params.push_back(base::Value(1));
  323. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  324. kFindInPageSelectAndScrollToMatch, highlight_second_params,
  325. content_world_, base::BindOnce(^(const base::Value* result) {
  326. highlight_done = true;
  327. context_string =
  328. result->FindKey(kSelectAndScrollResultContextString)->GetString();
  329. }),
  330. kCallJavascriptFunctionTimeout);
  331. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  332. return highlight_done;
  333. }));
  334. EXPECT_EQ(" foo match", context_string);
  335. id inner_html = ExecuteJavaScript(@"document.body.innerHTML");
  336. ASSERT_TRUE([inner_html isKindOfClass:[NSString class]]);
  337. EXPECT_TRUE([inner_html
  338. containsString:@"<chrome_find class=\"find_in_page\">foo</chrome_find> "
  339. @"<chrome_find class=\"find_in_page "
  340. @"find_selected\">foo</chrome_find>"]);
  341. EXPECT_TRUE(
  342. [inner_html containsString:@"find_selected{background-color:#ff9632"]);
  343. }
  344. // Tests that FindInPage does not highlight any matches given an invalid index.
  345. TEST_F(FindInPageJsTest, FindHighlightMatchAtInvalidIndex) {
  346. ASSERT_TRUE(LoadHtml("<span>invalid </span>"));
  347. ASSERT_TRUE(WaitForWebFramesCount(1));
  348. const base::TimeDelta kCallJavascriptFunctionTimeout =
  349. base::Seconds(kWaitForJSCompletionTimeout);
  350. __block bool message_received = false;
  351. std::vector<base::Value> params;
  352. params.push_back(base::Value(kFindStringFoo));
  353. params.push_back(base::Value(kPumpSearchTimeout));
  354. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  355. kFindInPageSearch, params, content_world_,
  356. base::BindOnce(^(const base::Value* result) {
  357. ASSERT_TRUE(result);
  358. ASSERT_TRUE(result->is_double());
  359. double count = result->GetDouble();
  360. ASSERT_TRUE(count == 0.0);
  361. message_received = true;
  362. }),
  363. kCallJavascriptFunctionTimeout);
  364. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  365. return message_received;
  366. }));
  367. __block bool highlight_done = false;
  368. std::vector<base::Value> highlight_params;
  369. highlight_params.push_back(base::Value(0));
  370. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  371. kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
  372. base::BindOnce(^(const base::Value* result) {
  373. highlight_done = true;
  374. }),
  375. kCallJavascriptFunctionTimeout);
  376. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  377. return highlight_done;
  378. }));
  379. EXPECT_NSEQ(@0,
  380. ExecuteJavaScript(
  381. @"document.getElementsByClassName('find_selected').length"));
  382. }
  383. // Tests that FindInPage works when searching for strings with non-ascii
  384. // characters.
  385. TEST_F(FindInPageJsTest, SearchForNonAscii) {
  386. ASSERT_TRUE(LoadHtml("<span>école francais</span>"));
  387. ASSERT_TRUE(WaitForWebFramesCount(1));
  388. const base::TimeDelta kCallJavascriptFunctionTimeout =
  389. base::Seconds(kWaitForJSCompletionTimeout);
  390. __block bool message_received = false;
  391. std::vector<base::Value> params;
  392. params.push_back(base::Value("école"));
  393. params.push_back(base::Value(kPumpSearchTimeout));
  394. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  395. kFindInPageSearch, params, content_world_,
  396. base::BindOnce(^(const base::Value* result) {
  397. ASSERT_TRUE(result);
  398. ASSERT_TRUE(result->is_double());
  399. double count = result->GetDouble();
  400. ASSERT_EQ(1.0, count);
  401. message_received = true;
  402. }),
  403. kCallJavascriptFunctionTimeout);
  404. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  405. return message_received;
  406. }));
  407. }
  408. // Tests that FindInPage scrolls page to bring selected match into view.
  409. TEST_F(FindInPageJsTest, CheckFindInPageScrollsToMatch) {
  410. // Set frame so that offset can be predictable across devices.
  411. web_state()->GetView().frame = CGRectMake(0, 0, 300, 200);
  412. // Create HTML with div of height 4000px followed by a span below with
  413. // searchable text in order to ensure that the text begins outside of screen
  414. // on all devices.
  415. ASSERT_TRUE(
  416. LoadHtml("<div style=\"height: 4000px;\"></div><span>foo</span>"));
  417. ASSERT_TRUE(WaitForWebFramesCount(1));
  418. const base::TimeDelta kCallJavascriptFunctionTimeout =
  419. base::Seconds(kWaitForJSCompletionTimeout);
  420. __block bool message_received = false;
  421. std::vector<base::Value> params;
  422. params.push_back(base::Value("foo"));
  423. params.push_back(base::Value(kPumpSearchTimeout));
  424. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  425. kFindInPageSearch, params, content_world_,
  426. base::BindOnce(^(const base::Value* result) {
  427. ASSERT_TRUE(result);
  428. ASSERT_TRUE(result->is_double());
  429. ASSERT_EQ(1.0, result->GetDouble());
  430. message_received = true;
  431. }),
  432. kCallJavascriptFunctionTimeout);
  433. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  434. return message_received;
  435. }));
  436. __block bool highlight_done = false;
  437. std::vector<base::Value> highlight_params;
  438. highlight_params.push_back(base::Value(0));
  439. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  440. kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
  441. base::BindOnce(^(const base::Value* result) {
  442. highlight_done = true;
  443. }),
  444. kCallJavascriptFunctionTimeout);
  445. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  446. return highlight_done;
  447. }));
  448. // Check that page has scrolled to the match.
  449. __block CGFloat top_scroll_after_select = 0.0;
  450. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  451. top_scroll_after_select =
  452. web_state()->GetWebViewProxy().scrollViewProxy.contentOffset.y;
  453. return top_scroll_after_select > 0;
  454. }));
  455. // Scroll offset should either be 1035.333 for most iPhone and 1035.5 for iPad
  456. // and 5S.
  457. EXPECT_NEAR(top_scroll_after_select, 1035, 1.0);
  458. }
  459. // Tests that FindInPage is able to clear CSS and match highlighting.
  460. TEST_F(FindInPageJsTest, StopFindInPage) {
  461. ASSERT_TRUE(LoadHtml("<span>foo foo</span>"));
  462. ASSERT_TRUE(WaitForWebFramesCount(1));
  463. const base::TimeDelta kCallJavascriptFunctionTimeout =
  464. base::Seconds(kWaitForJSCompletionTimeout);
  465. // Do a search to ensure match highlighting is cleared properly.
  466. __block bool message_received = false;
  467. std::vector<base::Value> params;
  468. params.push_back(base::Value("foo"));
  469. params.push_back(base::Value(kPumpSearchTimeout));
  470. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  471. kFindInPageSearch, params, content_world_,
  472. base::BindOnce(^(const base::Value* result) {
  473. message_received = true;
  474. }),
  475. kCallJavascriptFunctionTimeout);
  476. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  477. base::RunLoop().RunUntilIdle();
  478. return message_received;
  479. }));
  480. message_received = false;
  481. std::vector<base::Value> highlight_params;
  482. highlight_params.push_back(base::Value(0));
  483. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  484. kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
  485. base::BindOnce(^(const base::Value* result) {
  486. message_received = true;
  487. }),
  488. kCallJavascriptFunctionTimeout);
  489. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  490. base::RunLoop().RunUntilIdle();
  491. return message_received;
  492. }));
  493. message_received = false;
  494. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  495. kFindInPageStop, std::vector<base::Value>(), content_world_,
  496. base::BindOnce(^(const base::Value* result) {
  497. message_received = true;
  498. }),
  499. kCallJavascriptFunctionTimeout);
  500. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  501. base::RunLoop().RunUntilIdle();
  502. return message_received;
  503. }));
  504. id inner_html = ExecuteJavaScript(@"document.body.innerHTML");
  505. ASSERT_TRUE([inner_html isKindOfClass:[NSString class]]);
  506. EXPECT_FALSE([inner_html containsString:@"find_selected"]);
  507. EXPECT_FALSE([inner_html containsString:@"find_in_page"]);
  508. EXPECT_FALSE([inner_html containsString:@"chrome_find"]);
  509. }
  510. // Tests that FindInPage only selects the visible match when there is also a
  511. // hidden match.
  512. TEST_F(FindInPageJsTest, HiddenMatch) {
  513. ASSERT_TRUE(
  514. LoadHtml("<span style='display:none'>foo</span><span>foo bar</span>"));
  515. ASSERT_TRUE(WaitForWebFramesCount(1));
  516. const base::TimeDelta kCallJavascriptFunctionTimeout =
  517. base::Seconds(kWaitForJSCompletionTimeout);
  518. __block bool message_received = false;
  519. std::vector<base::Value> params;
  520. params.push_back(base::Value(kFindStringFoo));
  521. params.push_back(base::Value(kPumpSearchTimeout));
  522. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  523. kFindInPageSearch, params, content_world_,
  524. base::BindOnce(^(const base::Value* result) {
  525. ASSERT_TRUE(result);
  526. ASSERT_TRUE(result->is_double());
  527. double count = result->GetDouble();
  528. ASSERT_EQ(1.0, count);
  529. message_received = true;
  530. }),
  531. kCallJavascriptFunctionTimeout);
  532. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  533. return message_received;
  534. }));
  535. message_received = false;
  536. std::vector<base::Value> highlight_params;
  537. highlight_params.push_back(base::Value(0));
  538. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  539. kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
  540. base::BindOnce(^(const base::Value* result) {
  541. message_received = true;
  542. }),
  543. kCallJavascriptFunctionTimeout);
  544. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  545. base::RunLoop().RunUntilIdle();
  546. return message_received;
  547. }));
  548. id inner_html = ExecuteJavaScript(@"document.body.innerHTML");
  549. ASSERT_TRUE([inner_html isKindOfClass:[NSString class]]);
  550. NSRange visible_match =
  551. [inner_html rangeOfString:@"find_in_page find_selected"];
  552. NSRange hidden_match = [inner_html rangeOfString:@"find_in_page"];
  553. // Assert that the selected match comes after the first match in the DOM since
  554. // it is expected the hidden match is skipped.
  555. EXPECT_GT(visible_match.location, hidden_match.location);
  556. }
  557. // Tests that FindInPage responds with an updated match count when a once
  558. // hidden match becomes visible after a search finishes.
  559. TEST_F(FindInPageJsTest, HiddenMatchBecomesVisible) {
  560. ASSERT_TRUE(LoadHtml("<span>foo</span><span id=\"hidden_match\" "
  561. "style='display:none'>foo</span>"));
  562. ASSERT_TRUE(WaitForWebFramesCount(1));
  563. const base::TimeDelta kCallJavascriptFunctionTimeout =
  564. base::Seconds(kWaitForJSCompletionTimeout);
  565. __block bool message_received = false;
  566. std::vector<base::Value> params;
  567. params.push_back(base::Value(kFindStringFoo));
  568. params.push_back(base::Value(kPumpSearchTimeout));
  569. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  570. kFindInPageSearch, params, content_world_,
  571. base::BindOnce(^(const base::Value* result) {
  572. ASSERT_TRUE(result);
  573. ASSERT_TRUE(result->is_double());
  574. double count = result->GetDouble();
  575. ASSERT_EQ(1.0, count);
  576. message_received = true;
  577. }),
  578. kCallJavascriptFunctionTimeout);
  579. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  580. return message_received;
  581. }));
  582. ExecuteJavaScript(
  583. @"document.getElementById('hidden_match').removeAttribute('style')");
  584. message_received = false;
  585. std::vector<base::Value> highlight_params;
  586. highlight_params.push_back(base::Value(0));
  587. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  588. kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
  589. base::BindOnce(^(const base::Value* result) {
  590. ASSERT_TRUE(result);
  591. ASSERT_TRUE(result->is_dict());
  592. const base::Value* count =
  593. result->FindKey(kSelectAndScrollResultMatches);
  594. ASSERT_TRUE(count->is_double());
  595. ASSERT_EQ(2.0, count->GetDouble());
  596. const base::Value* index = result->FindKey(kSelectAndScrollResultIndex);
  597. ASSERT_TRUE(index->is_double());
  598. ASSERT_EQ(0.0, index->GetDouble());
  599. message_received = true;
  600. }),
  601. kCallJavascriptFunctionTimeout);
  602. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  603. base::RunLoop().RunUntilIdle();
  604. return message_received;
  605. }));
  606. }
  607. // Tests that FindInPage highlights the next visible match when attempting to
  608. // select a match that was once visible but is no longer.
  609. TEST_F(FindInPageJsTest, MatchBecomesInvisible) {
  610. ASSERT_TRUE(LoadHtml(
  611. "<span>foo foo </span> <span id=\"matches_to_hide\">foo foo</span>"));
  612. ASSERT_TRUE(WaitForWebFramesCount(1));
  613. const base::TimeDelta kCallJavascriptFunctionTimeout =
  614. base::Seconds(kWaitForJSCompletionTimeout);
  615. __block bool message_received = false;
  616. std::vector<base::Value> params;
  617. params.push_back(base::Value(kFindStringFoo));
  618. params.push_back(base::Value(kPumpSearchTimeout));
  619. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  620. kFindInPageSearch, params, content_world_,
  621. base::BindOnce(^(const base::Value* result) {
  622. ASSERT_TRUE(result);
  623. ASSERT_TRUE(result->is_double());
  624. double count = result->GetDouble();
  625. EXPECT_EQ(4.0, count);
  626. message_received = true;
  627. }),
  628. kCallJavascriptFunctionTimeout);
  629. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  630. return message_received;
  631. }));
  632. __block bool select_last_match_message_received = false;
  633. std::vector<base::Value> select_params;
  634. select_params.push_back(base::Value(3));
  635. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  636. kFindInPageSelectAndScrollToMatch, select_params, content_world_,
  637. base::BindOnce(^(const base::Value* result) {
  638. ASSERT_TRUE(result);
  639. ASSERT_TRUE(result->is_dict());
  640. const base::Value* index = result->FindKey(kSelectAndScrollResultIndex);
  641. ASSERT_TRUE(index->is_double());
  642. EXPECT_EQ(3.0, index->GetDouble());
  643. select_last_match_message_received = true;
  644. }),
  645. kCallJavascriptFunctionTimeout);
  646. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  647. base::RunLoop().RunUntilIdle();
  648. return select_last_match_message_received;
  649. }));
  650. ExecuteJavaScript(
  651. @"document.getElementById('matches_to_hide').style.display = \"none\";");
  652. __block bool select_third_match_message_received = false;
  653. std::vector<base::Value> select_third_match_params;
  654. select_third_match_params.push_back(base::Value(2));
  655. main_web_frame()->CallJavaScriptFunctionInContentWorld(
  656. kFindInPageSelectAndScrollToMatch, select_third_match_params,
  657. content_world_, base::BindOnce(^(const base::Value* result) {
  658. ASSERT_TRUE(result);
  659. ASSERT_TRUE(result->is_dict());
  660. const base::Value* index = result->FindKey(kSelectAndScrollResultIndex);
  661. ASSERT_TRUE(index->is_double());
  662. // Since there are only two visible matches now and this
  663. // kFindInPageSelectAndScrollToMatch call is asking Find in Page to
  664. // traverse to a previous match, Find in Page should look for the next
  665. // previous visible match. This happens to be the 2nd match.
  666. EXPECT_EQ(1.0, index->GetDouble());
  667. select_third_match_message_received = true;
  668. }),
  669. kCallJavascriptFunctionTimeout);
  670. ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
  671. base::RunLoop().RunUntilIdle();
  672. return select_third_match_message_received;
  673. }));
  674. }
  675. } // namespace web