1 // Copyright 2016 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 #ifndef CONTENT_BROWSER_FIND_REQUEST_MANAGER_H_
6 #define CONTENT_BROWSER_FIND_REQUEST_MANAGER_H_
7 
8 #include <memory>
9 #include <unordered_map>
10 #include <unordered_set>
11 #include <vector>
12 
13 #include "base/containers/queue.h"
14 #include "content/common/content_export.h"
15 #include "content/public/browser/web_contents_observer.h"
16 #include "content/public/common/stop_find_action.h"
17 #include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
18 #include "ui/gfx/geometry/rect.h"
19 #include "ui/gfx/geometry/rect_f.h"
20 
21 namespace content {
22 
23 class FindInPageClient;
24 class RenderFrameHost;
25 class RenderFrameHostImpl;
26 class WebContentsImpl;
27 
28 // FindRequestManager manages all of the find-in-page requests/replies
29 // initiated/received through a WebContents. It coordinates searching across
30 // multiple (potentially out-of-process) frames, handles the aggregation of find
31 // results from each frame, and facilitates active match traversal. It is
32 // instantiated once per top-level WebContents, and is owned by that
33 // WebContents.
34 class CONTENT_EXPORT FindRequestManager {
35  public:
36   explicit FindRequestManager(WebContentsImpl* web_contents);
37   ~FindRequestManager();
38 
39   // Initiates a find operation for |search_text| with the options specified in
40   // |options|. |request_id| uniquely identifies the find request.
41   void Find(int request_id,
42             const base::string16& search_text,
43             blink::mojom::FindOptionsPtr options);
44 
45   // Stops the active find session and clears the general highlighting of the
46   // matches. |action| determines whether the last active match (if any) will be
47   // activated, cleared, or remain highlighted.
48   void StopFinding(StopFindAction action);
49 
50   // Handles the final update from |rfh| for the find request with id
51   // |request_id|.
52   void HandleFinalUpdateForFrame(RenderFrameHostImpl* rfh, int request_id);
53 
54   // The number of matches on |rfh| has changed from |old_count| to |new_count|.
55   // This method updates the total number of matches and also updates
56   // |active_match_ordinal_| accordingly.
57   void UpdatedFrameNumberOfMatches(RenderFrameHostImpl* rfh,
58                                    unsigned int old_count,
59                                    unsigned int new_count);
60 
61   bool ShouldIgnoreReply(RenderFrameHostImpl* rfh, int request_id);
62 
63   void SetActiveMatchRect(const gfx::Rect& active_match_rect);
64 
65   void SetActiveMatchOrdinal(RenderFrameHostImpl* rfh,
66                              int request_id,
67                              int active_match_ordinal);
68 
69   // Sends the find results (as they currently are) to the WebContents.
70   // |final_update| is true if we have received all of the updates from
71   // every frame for this request.
72   void NotifyFindReply(int request_id, bool final_update);
73 
74   // Removes a frame from the set of frames being searched. This should be
75   // called whenever a frame is discovered to no longer exist.
76   void RemoveFrame(RenderFrameHost* rfh);
77 
78   // Tells active frame to clear the active match highlighting.
79   void ClearActiveFindMatch();
80 
81 #if defined(OS_ANDROID)
82   // Selects and zooms to the find result nearest to the point (x, y), defined
83   // in find-in-page coordinates.
84   void ActivateNearestFindResult(float x, float y);
85 
86   // Called when a reply is received from a frame in response to the
87   // GetNearestFindResult mojo call.
88   void OnGetNearestFindResultReply(RenderFrameHostImpl* rfh,
89                                    int request_id,
90                                    float distance);
91 
92   // Requests the rects of the current find matches from the renderer process.
93   void RequestFindMatchRects(int current_version);
94 
95   // Called when a reply is received from a frame in response to a request for
96   // find match rects.
97   void OnFindMatchRectsReply(RenderFrameHost* rfh,
98                              int version,
99                              const std::vector<gfx::RectF>& rects,
100                              const gfx::RectF& active_rect);
101 #endif
102 
103   const std::unordered_set<RenderFrameHost*>
render_frame_hosts_pending_initial_reply_for_testing()104   render_frame_hosts_pending_initial_reply_for_testing() const {
105     return pending_initial_replies_;
106   }
107 
GetSelectionRectForTesting()108   gfx::Rect GetSelectionRectForTesting() { return selection_rect_; }
109 
110  private:
111   // An invalid ID. This value is invalid for any render process ID, render
112   // frame ID, find request ID, or find match rects version number.
113   static const int kInvalidId;
114 
115   class FrameObserver;
116 
117   // The request data for a single find request.
118   struct FindRequest {
119     // The find request ID that uniquely identifies this find request.
120     int id = kInvalidId;
121 
122     // The text that is being searched for in this find request.
123     base::string16 search_text;
124 
125     // The set of find options in effect for this find request.
126     blink::mojom::FindOptionsPtr options;
127 
128     FindRequest();
129     FindRequest(int id,
130                 const base::string16& search_text,
131                 blink::mojom::FindOptionsPtr options);
132     FindRequest(const FindRequest& request);
133     ~FindRequest();
134 
135     FindRequest& operator=(const FindRequest& request);
136   };
137 
138   // Resets all of the per-session state for a new find-in-page session.
139   void Reset(const FindRequest& initial_request);
140 
141   // Called internally as find requests come up in the queue.
142   void FindInternal(const FindRequest& request);
143 
144   // Called when an informative response (a response with enough information to
145   // be able to route subsequent find requests) comes in for the find request
146   // with ID |request_id|. Advances the |find_request_queue_| if appropriate.
147   void AdvanceQueue(int request_id);
148 
149   // Sends find request |request| through mojo to the RenderFrame associated
150   // with |rfh|.
151   void SendFindRequest(const FindRequest& request, RenderFrameHost* rfh);
152 
153   // Returns the initial frame in search order. This will be either the first
154   // frame, if searching forward, or the last frame, if searching backward.
155   RenderFrameHost* GetInitialFrame(bool forward) const;
156 
157   // Traverses the frame tree to find and return the next RenderFrameHost after
158   // |from_rfh| in search order. |forward| indicates whether the frame tree
159   // should be traversed forward (if true) or backward (if false). If
160   // |matches_only| is set, then the frame tree will be traversed until the
161   // first frame is found for which matches have been found. If |wrap| is set,
162   // then the traversal can wrap around past the last frame to the first one (or
163   // vice-versa, if |forward| == false). If no frame can be found under these
164   // conditions, nullptr is returned.
165   RenderFrameHost* Traverse(RenderFrameHost* from_rfh,
166                             bool forward,
167                             bool matches_only,
168                             bool wrap) const;
169 
170   // Adds a frame to the set of frames that are being searched. The new frame
171   // will automatically be searched when added, using the same options (stored
172   // in |current_request_.options|). |force| should be set to true when a
173   // dynamic content change is suspected, which will treat the frame as a newly
174   // added frame even if it has already been searched. This will force a
175   // re-search of the frame.
176   void AddFrame(RenderFrameHost* rfh, bool force);
177 
178   // Returns whether |rfh| is in the set of frames being searched in the current
179   // find session.
180   bool CheckFrame(RenderFrameHost* rfh) const;
181 
182   // Computes and updates |active_match_ordinal_| based on |active_frame_| and
183   // |relative_active_match_ordinal_|.
184   void UpdateActiveMatchOrdinal();
185 
186   // Called when all pending find replies have been received for the find
187   // request with ID |request_id|. The final update was received from |rfh|.
188   //
189   // Note that this is the final update for this particular find request, but
190   // not necessarily for all issued requests. If there are still pending replies
191   // expected for a previous find request, then the outgoing find reply issued
192   // from this function will not be marked final.
193   void FinalUpdateReceived(int request_id, RenderFrameHost* rfh);
194 
195 #if defined(OS_ANDROID)
196   // Called when a nearest find result reply is no longer pending for a frame.
197   void RemoveNearestFindResultPendingReply(RenderFrameHost* rfh);
198 
199   // Called when a find match rects reply is no longer pending for a frame.
200   void RemoveFindMatchRectsPendingReply(RenderFrameHost* rfh);
201 
202   // State related to ActivateNearestFindResult requests.
203   struct ActivateNearestFindResultState {
204     // An ID to uniquely identify the current nearest find result request and
205     // its replies.
206     int current_request_id = kInvalidId;
207 
208     // The value of the requested point, in find-in-page coordinates.
209     gfx::PointF point = gfx::PointF(0.0f, 0.0f);
210 
211     float nearest_distance = FLT_MAX;
212 
213     // The frame containing the nearest result found so far.
214     RenderFrameHostImpl* nearest_frame = nullptr;
215 
216     // Nearest find result replies are still pending for these frames.
217     std::unordered_set<RenderFrameHost*> pending_replies;
218 
219     ActivateNearestFindResultState();
220     ActivateNearestFindResultState(float x, float y);
221     ~ActivateNearestFindResultState();
222 
GetNextIDActivateNearestFindResultState223     static int GetNextID() {
224       static int next_id = 0;
225       return next_id++;
226     }
227   } activate_;
228 
229   // Data for find match rects in a single frame.
230   struct FrameRects {
231     // The rects contained in a single frame.
232     std::vector<gfx::RectF> rects;
233 
234     // The version number for these rects, as reported by their containing
235     // frame. This version is incremented independently in each frame.
236     int version = kInvalidId;
237 
238     FrameRects();
239     FrameRects(const std::vector<gfx::RectF>& rects, int version);
240     ~FrameRects();
241   };
242 
243   // State related to FindMatchRects requests.
244   struct FindMatchRectsState {
245     // The latest find match rects version known by the requester. This will be
246     // compared to |known_version_| after polling frames for updates to their
247     // match rects, in order to determine if the requester already has the
248     // latest version of rects or not.
249     int request_version = kInvalidId;
250 
251     // The current overall find match rects version known by
252     // FindRequestManager. This version should be incremented whenever
253     // |frame_rects| is updated.
254     int known_version = 0;
255 
256     // A map from each frame to its find match rects.
257     std::unordered_map<RenderFrameHost*, FrameRects> frame_rects;
258 
259     // The active find match rect.
260     gfx::RectF active_rect;
261 
262     // Find match rects replies are still pending for these frames.
263     std::unordered_set<RenderFrameHost*> pending_replies;
264 
265     FindMatchRectsState();
266     ~FindMatchRectsState();
267   } match_rects_;
268 #endif
269 
270   // The WebContents that owns this FindRequestManager. This also defines the
271   // scope of all find sessions. Only frames in |contents_| and any inner
272   // WebContentses within it will be searched.
273   WebContentsImpl* const contents_;
274 
275   // The request ID of the initial find request in the current find-in-page
276   // session, which uniquely identifies this session. Request IDs are included
277   // in all find-related IPCs, which allows reply IPCs containing results from
278   // previous sessions (with |request_id| < |current_session_id_|) to be easily
279   // identified and ignored.
280   int current_session_id_ = kInvalidId;
281 
282   // The current find request.
283   FindRequest current_request_;
284 
285   // The set of frames that are still expected to reply to a pending initial
286   // find request. Frames are removed from |pending_initial_replies_| when their
287   // reply to the initial find request is received with |final_update| set to
288   // true.
289   std::unordered_set<RenderFrameHost*> pending_initial_replies_;
290 
291   // The frame (if any) that is still expected to reply to the last pending
292   // "find next" request.
293   RenderFrameHost* pending_find_next_reply_ = nullptr;
294 
295   // Indicates whether an update to the active match ordinal is expected. Once
296   // set, |pending_active_match_ordinal_| will not reset until an update to the
297   // active match ordinal is received in response to the find request with ID
298   // |current_request_.id| (the latest request).
299   bool pending_active_match_ordinal_ = false;
300 
301   // The FindInPageClient associated with each frame. There will necessarily be
302   // entries in this map for every frame that is being (or has been) searched in
303   // the current find session, and no other frames.
304   std::unordered_map<RenderFrameHost*, std::unique_ptr<FindInPageClient>>
305       find_in_page_clients_;
306 
307   // The total number of matches found in the current find-in-page session. This
308   // should always be equal to the sum of all the entries in
309   // |matches_per_frame_|.
310   int number_of_matches_ = 0;
311 
312   // The frame containing the active match, if one exists, or nullptr otherwise.
313   RenderFrameHostImpl* active_frame_ = nullptr;
314 
315   // The active match ordinal relative to the matches found in its own frame.
316   int relative_active_match_ordinal_ = 0;
317 
318   // The overall active match ordinal for the current find-in-page session.
319   int active_match_ordinal_ = 0;
320 
321   // The rectangle around the active match, in screen coordinates.
322   gfx::Rect selection_rect_;
323 
324   // Find requests are queued here when previous requests need to be handled
325   // before these ones can be properly routed.
326   base::queue<FindRequest> find_request_queue_;
327 
328   // Keeps track of the find request ID of the last find reply reported via
329   // NotifyFindReply().
330   int last_reported_id_ = kInvalidId;
331 
332   // WebContentsObservers to observe frame changes in |contents_| and its inner
333   // WebContentses.
334   std::vector<std::unique_ptr<FrameObserver>> frame_observers_;
335 
336   DISALLOW_COPY_AND_ASSIGN(FindRequestManager);
337 };
338 
339 }  // namespace content
340 
341 #endif  // CONTENT_BROWSER_FIND_REQUEST_MANAGER_H_
342