1 /*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "third_party/blink/renderer/core/frame/find_in_page.h"
32
33 #include <utility>
34
35 #include "third_party/blink/public/web/web_document.h"
36 #include "third_party/blink/public/web/web_local_frame_client.h"
37 #include "third_party/blink/public/web/web_plugin.h"
38 #include "third_party/blink/public/web/web_plugin_document.h"
39 #include "third_party/blink/public/web/web_widget_client.h"
40 #include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h"
41 #include "third_party/blink/renderer/core/editing/finder/text_finder.h"
42 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
43 #include "third_party/blink/renderer/core/layout/layout_view.h"
44 #include "third_party/blink/renderer/core/page/focus_controller.h"
45 #include "third_party/blink/renderer/core/page/page.h"
46
47 namespace blink {
48
FindInPage(WebLocalFrameImpl & frame,InterfaceRegistry * interface_registry)49 FindInPage::FindInPage(WebLocalFrameImpl& frame,
50 InterfaceRegistry* interface_registry)
51 : frame_(&frame) {
52 // TODO(rakina): Use InterfaceRegistry of |frame| directly rather than passing
53 // both of them.
54 if (!interface_registry)
55 return;
56 // TODO(crbug.com/800641): Use InterfaceValidator when it works for associated
57 // interfaces.
58 interface_registry->AddAssociatedInterface(WTF::BindRepeating(
59 &FindInPage::BindToReceiver, WrapWeakPersistent(this)));
60 }
61
Find(int request_id,const String & search_text,mojom::blink::FindOptionsPtr options)62 void FindInPage::Find(int request_id,
63 const String& search_text,
64 mojom::blink::FindOptionsPtr options) {
65 DCHECK(!search_text.IsEmpty());
66
67 // Record the fact that we have a find-in-page request.
68 frame_->GetFrame()->GetDocument()->MarkHasFindInPageRequest();
69
70 blink::WebPlugin* plugin = GetWebPluginForFind();
71 // Check if the plugin still exists in the document.
72 if (plugin) {
73 if (!options->new_session) {
74 // Just navigate back/forward.
75 plugin->SelectFindResult(options->forward, request_id);
76 LocalFrame* core_frame = frame_->GetFrame();
77 core_frame->GetPage()->GetFocusController().SetFocusedFrame(core_frame);
78 } else if (!plugin->StartFind(search_text, options->match_case,
79 request_id)) {
80 // Send "no results"
81 ReportFindInPageMatchCount(request_id, 0 /* count */,
82 true /* final_update */);
83 }
84 return;
85 }
86
87 // Send "no results" if this frame has no visible content.
88 if (!frame_->HasVisibleContent() && !options->force) {
89 ReportFindInPageMatchCount(request_id, 0 /* count */,
90 true /* final_update */);
91 return;
92 }
93
94 WebRange current_selection = frame_->SelectionRange();
95 bool result = false;
96 bool active_now = false;
97
98 if (options->new_session)
99 EnsureTextFinder().InitNewSession(*options);
100
101 // Search for an active match only if this frame is focused or if this is an
102 // existing session.
103 if (options->find_match &&
104 (frame_->IsFocused() || !options->new_session)) {
105 result = FindInternal(request_id, search_text, *options,
106 false /* wrap_within_frame */, &active_now);
107 }
108
109 if (result && options->new_session) {
110 // Indicate that at least one match has been found. 1 here means
111 // possibly more matches could be coming.
112 ReportFindInPageMatchCount(request_id, 1 /* count */,
113 false /* final_update */);
114 }
115
116 // There are three cases in which scoping is needed:
117 //
118 // (1) This is a new find session. This will be its first scoping effort.
119 //
120 // (2) Something has been selected since the last search. This means that we
121 // cannot just increment the current match ordinal; we need to re-generate
122 // it.
123 //
124 // (3) TextFinder::Find() found what should be the next match (|result| is
125 // true), but was unable to activate it (|activeNow| is false). This means
126 // that the text containing this match was dynamically added since the last
127 // scope of the frame. The frame needs to be re-scoped so that any matches
128 // in the new text can be highlighted and included in the reported number of
129 // matches.
130 //
131 // If none of these cases are true, then we just report the current match
132 // count without scoping.
133 if (/* (1) */ !options->new_session && /* (2) */ current_selection.IsNull() &&
134 /* (3) */ !(result && !active_now)) {
135 // Force report of the actual count.
136 EnsureTextFinder().IncreaseMatchCount(request_id, 0);
137 return;
138 }
139
140 // Start a new scoping If the scoping function determines that it
141 // needs to scope, it will defer until later.
142 EnsureTextFinder().StartScopingStringMatches(request_id, search_text,
143 *options);
144 }
145
FindForTesting(int identifier,const WebString & search_text,bool match_case,bool forward,bool new_session,bool force,bool wrap_within_frame,bool async)146 bool WebLocalFrameImpl::FindForTesting(int identifier,
147 const WebString& search_text,
148 bool match_case,
149 bool forward,
150 bool new_session,
151 bool force,
152 bool wrap_within_frame,
153 bool async) {
154 auto options = mojom::blink::FindOptions::New();
155 options->match_case = match_case;
156 options->forward = forward;
157 options->new_session = new_session;
158 options->force = force;
159 options->run_synchronously_for_testing = !async;
160 bool result = find_in_page_->FindInternal(identifier, search_text, *options,
161 wrap_within_frame, nullptr);
162 find_in_page_->StopFinding(
163 mojom::blink::StopFindAction::kStopFindActionKeepSelection);
164 return result;
165 }
166
FindInternal(int identifier,const WebString & search_text,const mojom::blink::FindOptions & options,bool wrap_within_frame,bool * active_now)167 bool FindInPage::FindInternal(int identifier,
168 const WebString& search_text,
169 const mojom::blink::FindOptions& options,
170 bool wrap_within_frame,
171 bool* active_now) {
172 if (!frame_->GetFrame())
173 return false;
174
175 // Unlikely, but just in case we try to find-in-page on a detached frame.
176 DCHECK(frame_->GetFrame()->GetPage());
177
178 auto forced_activatable_locks = frame_->GetFrame()
179 ->GetDocument()
180 ->GetDisplayLockDocumentState()
181 .GetScopedForceActivatableLocks();
182
183 // Up-to-date, clean tree is required for finding text in page, since it
184 // relies on TextIterator to look over the text.
185 frame_->GetFrame()->GetDocument()->UpdateStyleAndLayout(
186 DocumentUpdateReason::kFindInPage);
187
188 return EnsureTextFinder().Find(identifier, search_text, options,
189 wrap_within_frame, active_now);
190 }
191
StopFinding(mojom::StopFindAction action)192 void FindInPage::StopFinding(mojom::StopFindAction action) {
193 WebPlugin* const plugin = GetWebPluginForFind();
194 if (plugin) {
195 plugin->StopFind();
196 return;
197 }
198
199 const bool clear_selection =
200 action == mojom::StopFindAction::kStopFindActionClearSelection;
201 if (clear_selection)
202 frame_->ExecuteCommand(WebString::FromUTF8("Unselect"));
203
204 if (GetTextFinder()) {
205 if (!clear_selection)
206 GetTextFinder()->SetFindEndstateFocusAndSelection();
207 GetTextFinder()->StopFindingAndClearSelection();
208 }
209
210 if (action == mojom::StopFindAction::kStopFindActionActivateSelection &&
211 frame_->IsFocused()) {
212 WebDocument doc = frame_->GetDocument();
213 if (!doc.IsNull()) {
214 WebElement element = doc.FocusedElement();
215 if (!element.IsNull())
216 element.SimulateClick();
217 }
218 }
219 }
220
FindMatchMarkersVersion() const221 int FindInPage::FindMatchMarkersVersion() const {
222 if (GetTextFinder())
223 return GetTextFinder()->FindMatchMarkersVersion();
224 return 0;
225 }
226
ActiveFindMatchRect()227 gfx::RectF FindInPage::ActiveFindMatchRect() {
228 if (GetTextFinder())
229 return GetTextFinder()->ActiveFindMatchRect();
230 return gfx::RectF();
231 }
232
ActivateNearestFindResult(int request_id,const gfx::PointF & point)233 void FindInPage::ActivateNearestFindResult(int request_id,
234 const gfx::PointF& point) {
235 gfx::Rect active_match_rect;
236 const int ordinal =
237 EnsureTextFinder().SelectNearestFindMatch(point, &active_match_rect);
238 if (ordinal == -1) {
239 // Something went wrong, so send a no-op reply (force the frame to report
240 // the current match count) in case the host is waiting for a response due
241 // to rate-limiting.
242 EnsureTextFinder().IncreaseMatchCount(request_id, 0);
243 return;
244 }
245 ReportFindInPageSelection(request_id, ordinal, active_match_rect,
246 true /* final_update */);
247 }
248
SetClient(mojo::PendingRemote<mojom::blink::FindInPageClient> remote)249 void FindInPage::SetClient(
250 mojo::PendingRemote<mojom::blink::FindInPageClient> remote) {
251 // TODO(crbug.com/984878): Having to call reset() to try to bind a remote that
252 // might be bound is questionable behavior and suggests code may be buggy.
253 client_.reset();
254 client_.Bind(std::move(remote));
255 }
256
GetNearestFindResult(const gfx::PointF & point,GetNearestFindResultCallback callback)257 void FindInPage::GetNearestFindResult(const gfx::PointF& point,
258 GetNearestFindResultCallback callback) {
259 float distance;
260 EnsureTextFinder().NearestFindMatch(FloatPoint(point), &distance);
261 std::move(callback).Run(distance);
262 }
263
FindMatchRects(int current_version,FindMatchRectsCallback callback)264 void FindInPage::FindMatchRects(int current_version,
265 FindMatchRectsCallback callback) {
266 int rects_version = FindMatchMarkersVersion();
267 Vector<gfx::RectF> rects;
268 if (current_version != rects_version)
269 rects = EnsureTextFinder().FindMatchRects();
270 std::move(callback).Run(rects_version, rects, ActiveFindMatchRect());
271 }
272
ClearActiveFindMatch()273 void FindInPage::ClearActiveFindMatch() {
274 // TODO(rakina): Do collapse selection as this currently does nothing.
275 frame_->ExecuteCommand(WebString::FromUTF8("CollapseSelection"));
276 EnsureTextFinder().ClearActiveFindMatch();
277 }
278
SetTickmarks(const WebElement & target,const WebVector<WebRect> & tickmarks)279 void WebLocalFrameImpl::SetTickmarks(const WebElement& target,
280 const WebVector<WebRect>& tickmarks) {
281 find_in_page_->SetTickmarks(target, tickmarks);
282 }
283
SetTickmarks(const WebElement & target,const WebVector<WebRect> & tickmarks)284 void FindInPage::SetTickmarks(const WebElement& target,
285 const WebVector<WebRect>& tickmarks) {
286 Vector<IntRect> tickmarks_converted(SafeCast<wtf_size_t>(tickmarks.size()));
287 for (wtf_size_t i = 0; i < tickmarks.size(); ++i)
288 tickmarks_converted[i] = tickmarks[i];
289
290 LayoutBox* box;
291 if (target.IsNull())
292 box = frame_->GetFrame()->ContentLayoutObject();
293 else
294 box = target.ConstUnwrap<Element>()->GetLayoutBoxForScrolling();
295 if (box)
296 box->OverrideTickmarks(std::move(tickmarks_converted));
297 }
298
GetTextFinder() const299 TextFinder* WebLocalFrameImpl::GetTextFinder() const {
300 return find_in_page_->GetTextFinder();
301 }
302
GetTextFinder() const303 TextFinder* FindInPage::GetTextFinder() const {
304 return text_finder_;
305 }
306
EnsureTextFinder()307 TextFinder& WebLocalFrameImpl::EnsureTextFinder() {
308 return find_in_page_->EnsureTextFinder();
309 }
310
EnsureTextFinder()311 TextFinder& FindInPage::EnsureTextFinder() {
312 if (!text_finder_)
313 text_finder_ = MakeGarbageCollected<TextFinder>(*frame_);
314
315 return *text_finder_;
316 }
317
SetPluginFindHandler(WebPluginContainer * plugin)318 void FindInPage::SetPluginFindHandler(WebPluginContainer* plugin) {
319 plugin_find_handler_ = plugin;
320 }
321
PluginFindHandler() const322 WebPluginContainer* FindInPage::PluginFindHandler() const {
323 return plugin_find_handler_;
324 }
325
GetWebPluginForFind()326 WebPlugin* FindInPage::GetWebPluginForFind() {
327 if (frame_->GetDocument().IsPluginDocument())
328 return frame_->GetDocument().To<WebPluginDocument>().Plugin();
329 if (plugin_find_handler_)
330 return plugin_find_handler_->Plugin();
331 return nullptr;
332 }
333
BindToReceiver(mojo::PendingAssociatedReceiver<mojom::blink::FindInPage> receiver)334 void FindInPage::BindToReceiver(
335 mojo::PendingAssociatedReceiver<mojom::blink::FindInPage> receiver) {
336 receiver_.Bind(std::move(receiver),
337 frame_->GetTaskRunner(blink::TaskType::kInternalDefault));
338 }
339
Dispose()340 void FindInPage::Dispose() {
341 receiver_.reset();
342 }
343
ReportFindInPageMatchCount(int request_id,int count,bool final_update)344 void FindInPage::ReportFindInPageMatchCount(int request_id,
345 int count,
346 bool final_update) {
347 // In tests, |client_| might not be set.
348 if (!client_)
349 return;
350 client_->SetNumberOfMatches(
351 request_id, count,
352 final_update ? mojom::blink::FindMatchUpdateType::kFinalUpdate
353 : mojom::blink::FindMatchUpdateType::kMoreUpdatesComing);
354 }
355
ReportFindInPageSelection(int request_id,int active_match_ordinal,const gfx::Rect & selection_rect,bool final_update)356 void FindInPage::ReportFindInPageSelection(int request_id,
357 int active_match_ordinal,
358 const gfx::Rect& selection_rect,
359 bool final_update) {
360 // In tests, |client_| might not be set.
361 if (!client_)
362 return;
363 client_->SetActiveMatch(
364 request_id, selection_rect, active_match_ordinal,
365 final_update ? mojom::blink::FindMatchUpdateType::kFinalUpdate
366 : mojom::blink::FindMatchUpdateType::kMoreUpdatesComing);
367 }
368
369 } // namespace blink
370