find_in_page_manager_impl.mm 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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 "ios/web/find_in_page/find_in_page_manager_impl.h"
  5. #include "base/metrics/user_metrics.h"
  6. #include "base/metrics/user_metrics_action.h"
  7. #import "base/strings/sys_string_conversions.h"
  8. #include "base/values.h"
  9. #import "ios/web/find_in_page/find_in_page_constants.h"
  10. #import "ios/web/find_in_page/find_in_page_java_script_feature.h"
  11. #import "ios/web/public/find_in_page/find_in_page_manager_delegate.h"
  12. #import "ios/web/public/js_messaging/web_frame.h"
  13. #include "ios/web/public/js_messaging/web_frame_util.h"
  14. #import "ios/web/public/js_messaging/web_frames_manager.h"
  15. #include "ios/web/public/thread/web_task_traits.h"
  16. #include "ios/web/public/thread/web_thread.h"
  17. #import "ios/web/web_state/web_state_impl.h"
  18. #if !defined(__has_feature) || !__has_feature(objc_arc)
  19. #error "This file requires ARC support."
  20. #endif
  21. namespace web {
  22. // static
  23. FindInPageManagerImpl::FindInPageManagerImpl(WebState* web_state)
  24. : web_state_(web_state), weak_factory_(this) {
  25. web_state_->AddObserver(this);
  26. }
  27. void FindInPageManagerImpl::CreateForWebState(WebState* web_state) {
  28. DCHECK(web_state);
  29. if (!FromWebState(web_state)) {
  30. web_state->SetUserData(UserDataKey(),
  31. std::make_unique<FindInPageManagerImpl>(web_state));
  32. }
  33. }
  34. FindInPageManagerImpl::~FindInPageManagerImpl() {
  35. if (web_state_) {
  36. web_state_->RemoveObserver(this);
  37. web_state_ = nullptr;
  38. }
  39. }
  40. FindInPageManagerDelegate* FindInPageManagerImpl::GetDelegate() {
  41. return delegate_;
  42. }
  43. void FindInPageManagerImpl::SetDelegate(FindInPageManagerDelegate* delegate) {
  44. delegate_ = delegate;
  45. }
  46. void FindInPageManagerImpl::WebFrameDidBecomeAvailable(WebState* web_state,
  47. WebFrame* web_frame) {
  48. const std::string frame_id = web_frame->GetFrameId();
  49. last_find_request_.AddFrame(web_frame);
  50. }
  51. void FindInPageManagerImpl::WebFrameWillBecomeUnavailable(WebState* web_state,
  52. WebFrame* web_frame) {
  53. int match_count =
  54. last_find_request_.GetMatchCountForFrame(web_frame->GetFrameId());
  55. last_find_request_.RemoveFrame(web_frame->GetFrameId());
  56. // Only notify the delegate if the match count has changed.
  57. if (delegate_ && last_find_request_.GetRequestQuery() && match_count > 0) {
  58. delegate_->DidHighlightMatches(web_state_,
  59. last_find_request_.GetTotalMatchCount(),
  60. last_find_request_.GetRequestQuery());
  61. }
  62. }
  63. void FindInPageManagerImpl::WebStateDestroyed(WebState* web_state) {
  64. web_state_->RemoveObserver(this);
  65. web_state_ = nullptr;
  66. }
  67. void FindInPageManagerImpl::Find(NSString* query, FindInPageOptions options) {
  68. DCHECK(CanSearchContent());
  69. switch (options) {
  70. case FindInPageOptions::FindInPageSearch:
  71. DCHECK(query);
  72. StartSearch(query);
  73. break;
  74. case FindInPageOptions::FindInPageNext:
  75. SelectNextMatch();
  76. break;
  77. case FindInPageOptions::FindInPagePrevious:
  78. SelectPreviousMatch();
  79. break;
  80. }
  81. }
  82. void FindInPageManagerImpl::StartSearch(NSString* query) {
  83. base::RecordAction(base::UserMetricsAction(kFindActionName));
  84. std::set<WebFrame*> all_frames =
  85. web_state_->GetWebFramesManager()->GetAllWebFrames();
  86. last_find_request_.Reset(query, all_frames.size());
  87. if (all_frames.size() == 0) {
  88. // No frames to search in.
  89. // Call asyncronously to match behavior if find was successful in frames.
  90. GetUIThreadTaskRunner({})->PostTask(
  91. FROM_HERE,
  92. base::BindOnce(&FindInPageManagerImpl::LastFindRequestCompleted,
  93. weak_factory_.GetWeakPtr()));
  94. return;
  95. }
  96. for (WebFrame* frame : all_frames) {
  97. bool result = FindInPageJavaScriptFeature::GetInstance()->Search(
  98. frame, base::SysNSStringToUTF8(query),
  99. base::BindOnce(&FindInPageManagerImpl::ProcessFindInPageResult,
  100. weak_factory_.GetWeakPtr(), frame->GetFrameId(),
  101. last_find_request_.GetRequestId()));
  102. if (!result) {
  103. // Calling JavaScript function failed or the frame does not support
  104. // messaging.
  105. last_find_request_.DidReceiveFindResponseFromOneFrame();
  106. if (last_find_request_.AreAllFindResponsesReturned()) {
  107. // Call asyncronously to match behavior if find was done in frames.
  108. GetUIThreadTaskRunner({})->PostTask(
  109. FROM_HERE,
  110. base::BindOnce(&FindInPageManagerImpl::LastFindRequestCompleted,
  111. weak_factory_.GetWeakPtr()));
  112. }
  113. }
  114. }
  115. }
  116. void FindInPageManagerImpl::StopFinding() {
  117. last_find_request_.Reset(/*new_query=*/nil,
  118. /*new_pending_frame_call_count=*/0);
  119. for (WebFrame* frame : web_state_->GetWebFramesManager()->GetAllWebFrames()) {
  120. FindInPageJavaScriptFeature::GetInstance()->Stop(frame);
  121. }
  122. if (delegate_) {
  123. delegate_->DidHighlightMatches(web_state_,
  124. last_find_request_.GetTotalMatchCount(),
  125. last_find_request_.GetRequestQuery());
  126. }
  127. }
  128. bool FindInPageManagerImpl::CanSearchContent() {
  129. return web_state_->ContentIsHTML();
  130. }
  131. void FindInPageManagerImpl::ProcessFindInPageResult(
  132. const std::string& frame_id,
  133. const int unique_id,
  134. absl::optional<int> result_matches) {
  135. if (unique_id != last_find_request_.GetRequestId()) {
  136. // New find was started or current find was stopped.
  137. return;
  138. }
  139. if (!web_state_) {
  140. // WebState was destroyed before find finished.
  141. return;
  142. }
  143. WebFrame* frame = GetWebFrameWithId(web_state_, frame_id);
  144. if (!result_matches || !frame) {
  145. // The frame no longer exists or the function call timed out. In both cases,
  146. // result will be null.
  147. // Zero out count to ensure every frame is updated for every find.
  148. last_find_request_.SetMatchCountForFrame(0, frame_id);
  149. } else {
  150. // If response is equal to kFindInPagePending, find did not finish in the
  151. // JavaScript. Call pumpSearch to continue find.
  152. if (result_matches.value() == find_in_page::kFindInPagePending) {
  153. FindInPageJavaScriptFeature::GetInstance()->Pump(
  154. frame,
  155. base::BindOnce(&FindInPageManagerImpl::ProcessFindInPageResult,
  156. weak_factory_.GetWeakPtr(), frame_id, unique_id));
  157. return;
  158. }
  159. last_find_request_.SetMatchCountForFrame(result_matches.value(), frame_id);
  160. }
  161. last_find_request_.DidReceiveFindResponseFromOneFrame();
  162. if (last_find_request_.AreAllFindResponsesReturned()) {
  163. LastFindRequestCompleted();
  164. }
  165. }
  166. void FindInPageManagerImpl::LastFindRequestCompleted() {
  167. if (delegate_) {
  168. delegate_->DidHighlightMatches(web_state_,
  169. last_find_request_.GetTotalMatchCount(),
  170. last_find_request_.GetRequestQuery());
  171. }
  172. int total_matches = last_find_request_.GetTotalMatchCount();
  173. if (total_matches == 0) {
  174. return;
  175. }
  176. if (last_find_request_.GoToFirstMatch()) {
  177. SelectCurrentMatch();
  178. }
  179. }
  180. void FindInPageManagerImpl::SelectDidFinish(const base::Value* result) {
  181. std::string match_context_string;
  182. if (result && result->is_dict()) {
  183. // Get updated match count.
  184. const base::Value* matches = result->FindKey(kSelectAndScrollResultMatches);
  185. if (matches && matches->is_double()) {
  186. int match_count = static_cast<int>(matches->GetDouble());
  187. if (match_count != last_find_request_.GetMatchCountForSelectedFrame()) {
  188. last_find_request_.SetMatchCountForSelectedFrame(match_count);
  189. if (delegate_) {
  190. delegate_->DidHighlightMatches(
  191. web_state_, last_find_request_.GetTotalMatchCount(),
  192. last_find_request_.GetRequestQuery());
  193. }
  194. }
  195. }
  196. // Get updated currently selected index.
  197. const base::Value* index = result->FindKey(kSelectAndScrollResultIndex);
  198. if (index && index->is_double()) {
  199. int current_index = static_cast<int>(index->GetDouble());
  200. last_find_request_.SetCurrentSelectedMatchFrameIndex(current_index);
  201. }
  202. // Get context string.
  203. const base::Value* context_string =
  204. result->FindKey(kSelectAndScrollResultContextString);
  205. if (context_string && context_string->is_string()) {
  206. match_context_string =
  207. static_cast<std::string>(context_string->GetString());
  208. }
  209. }
  210. if (delegate_) {
  211. delegate_->DidSelectMatch(
  212. web_state_, last_find_request_.GetCurrentSelectedMatchPageIndex(),
  213. base::SysUTF8ToNSString(match_context_string));
  214. }
  215. }
  216. void FindInPageManagerImpl::SelectNextMatch() {
  217. base::RecordAction(base::UserMetricsAction(kFindNextActionName));
  218. if (last_find_request_.GoToNextMatch()) {
  219. SelectCurrentMatch();
  220. }
  221. }
  222. void FindInPageManagerImpl::SelectPreviousMatch() {
  223. base::RecordAction(base::UserMetricsAction(kFindPreviousActionName));
  224. if (last_find_request_.GoToPreviousMatch()) {
  225. SelectCurrentMatch();
  226. }
  227. }
  228. void FindInPageManagerImpl::SelectCurrentMatch() {
  229. web::WebFrame* frame =
  230. GetWebFrameWithId(web_state_, last_find_request_.GetSelectedFrameId());
  231. if (frame) {
  232. FindInPageJavaScriptFeature::GetInstance()->SelectMatch(
  233. frame, last_find_request_.GetCurrentSelectedMatchFrameIndex(),
  234. base::BindOnce(&FindInPageManagerImpl::SelectDidFinish,
  235. weak_factory_.GetWeakPtr()));
  236. }
  237. }
  238. WEB_STATE_USER_DATA_KEY_IMPL(FindInPageManager)
  239. } // namespace web