blob: 7ef7463cb61f8e2ff2c079c146ccb77f03e85cd4 [file] [log] [blame]
/*
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/frame/find_in_page.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_find_options.h"
#include "third_party/blink/public/web/web_local_frame_client.h"
#include "third_party/blink/public/web/web_plugin.h"
#include "third_party/blink/public/web/web_plugin_document.h"
#include "third_party/blink/public/web/web_widget_client.h"
#include "third_party/blink/renderer/core/editing/finder/text_finder.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/focus_controller.h"
#include "third_party/blink/renderer/core/page/page.h"
namespace blink {
FindInPage::FindInPage(WebLocalFrameImpl& frame,
InterfaceRegistry* interface_registry)
: ContextLifecycleObserver(
frame.GetFrame() ? frame.GetFrame()->GetDocument() : nullptr),
frame_(&frame),
binding_(this) {
// TODO(rakina): Use InterfaceRegistry of |frame| directly rather than passing
// both of them.
if (!interface_registry)
return;
// TODO(crbug.com/800641): Use InterfaceValidator when it works for associated
// interfaces.
interface_registry->AddAssociatedInterface(
WTF::BindRepeating(&FindInPage::BindToRequest, WrapWeakPersistent(this)));
}
void FindInPage::Find(int request_id,
const String& search_text,
mojom::blink::FindOptionsPtr options) {
DCHECK(!search_text.IsEmpty());
blink::WebPlugin* plugin = GetWebPluginForFind();
// Check if the plugin still exists in the document.
if (plugin) {
if (options->find_next) {
// Just navigate back/forward.
plugin->SelectFindResult(options->forward, request_id);
LocalFrame* core_frame = frame_->GetFrame();
core_frame->GetPage()->GetFocusController().SetFocusedFrame(core_frame);
} else if (!plugin->StartFind(search_text, options->match_case,
request_id)) {
// Send "no results"
ReportFindInPageMatchCount(request_id, 0 /* count */,
true /* final_update */);
}
return;
}
// Send "no results" if this frame has no visible content.
if (!frame_->HasVisibleContent() && !options->force) {
ReportFindInPageMatchCount(request_id, 0 /* count */,
true /* final_update */);
return;
}
WebRange current_selection = frame_->SelectionRange();
bool result = false;
bool active_now = false;
WebFindOptions web_options;
web_options.forward = options->forward;
web_options.match_case = options->match_case;
web_options.find_next = options->find_next;
web_options.force = options->force;
web_options.run_synchronously_for_testing =
options->run_synchronously_for_testing;
// Search for an active match only if this frame is focused or if this is a
// find next request.
if (frame_->IsFocused() || options->find_next) {
result = FindInternal(request_id, search_text, web_options,
false /* wrap_within_frame */, &active_now);
}
if (result && !options->find_next) {
// Indicate that at least one match has been found. 1 here means
// possibly more matches could be coming.
ReportFindInPageMatchCount(request_id, 1 /* count */,
false /* final_update */);
}
// There are three cases in which scoping is needed:
//
// (1) This is an initial find request (|options.findNext| is false). This
// will be the first scoping effort for this find session.
//
// (2) Something has been selected since the last search. This means that we
// cannot just increment the current match ordinal; we need to re-generate
// it.
//
// (3) TextFinder::Find() found what should be the next match (|result| is
// true), but was unable to activate it (|activeNow| is false). This means
// that the text containing this match was dynamically added since the last
// scope of the frame. The frame needs to be re-scoped so that any matches
// in the new text can be highlighted and included in the reported number of
// matches.
//
// If none of these cases are true, then we just report the current match
// count without scoping.
if (/* (1) */ options->find_next && /* (2) */ current_selection.IsNull() &&
/* (3) */ !(result && !active_now)) {
// Force report of the actual count.
EnsureTextFinder().IncreaseMatchCount(request_id, 0);
return;
}
// Start a new scoping request. If the scoping function determines that it
// needs to scope, it will defer until later.
EnsureTextFinder().StartScopingStringMatches(request_id, search_text,
web_options);
}
bool WebLocalFrameImpl::Find(int identifier,
const WebString& search_text,
const WebFindOptions& options,
bool wrap_within_frame,
bool* active_now) {
return find_in_page_->FindInternal(identifier, search_text, options,
wrap_within_frame, active_now);
}
bool FindInPage::FindInternal(int identifier,
const WebString& search_text,
const WebFindOptions& options,
bool wrap_within_frame,
bool* active_now) {
if (!frame_->GetFrame())
return false;
// Unlikely, but just in case we try to find-in-page on a detached frame.
DCHECK(frame_->GetFrame()->GetPage());
// Up-to-date, clean tree is required for finding text in page, since it
// relies on TextIterator to look over the text.
frame_->GetFrame()
->GetDocument()
->UpdateStyleAndLayoutIgnorePendingStylesheets();
return EnsureTextFinder().Find(identifier, search_text, options,
wrap_within_frame, active_now);
}
void WebLocalFrameImpl::StopFindingForTesting(mojom::StopFindAction action) {
find_in_page_->StopFinding(action);
}
void FindInPage::StopFinding(mojom::StopFindAction action) {
WebPlugin* const plugin = GetWebPluginForFind();
if (plugin) {
plugin->StopFind();
return;
}
const bool clear_selection =
action == mojom::StopFindAction::kStopFindActionClearSelection;
if (clear_selection)
frame_->ExecuteCommand(WebString::FromUTF8("Unselect"));
if (GetTextFinder()) {
if (!clear_selection)
GetTextFinder()->SetFindEndstateFocusAndSelection();
GetTextFinder()->StopFindingAndClearSelection();
}
if (action == mojom::StopFindAction::kStopFindActionActivateSelection &&
frame_->IsFocused()) {
WebDocument doc = frame_->GetDocument();
if (!doc.IsNull()) {
WebElement element = doc.FocusedElement();
if (!element.IsNull())
element.SimulateClick();
}
}
}
int FindInPage::FindMatchMarkersVersion() const {
if (GetTextFinder())
return GetTextFinder()->FindMatchMarkersVersion();
return 0;
}
WebFloatRect FindInPage::ActiveFindMatchRect() {
if (GetTextFinder())
return GetTextFinder()->ActiveFindMatchRect();
return WebFloatRect();
}
void FindInPage::ActivateNearestFindResult(int request_id,
const WebFloatPoint& point) {
WebRect active_match_rect;
const int ordinal =
EnsureTextFinder().SelectNearestFindMatch(point, &active_match_rect);
if (ordinal == -1) {
// Something went wrong, so send a no-op reply (force the frame to report
// the current match count) in case the host is waiting for a response due
// to rate-limiting.
EnsureTextFinder().IncreaseMatchCount(request_id, 0);
return;
}
ReportFindInPageSelection(request_id, ordinal, active_match_rect,
true /* final_update */);
}
void FindInPage::SetClient(mojom::blink::FindInPageClientPtr client) {
client_ = std::move(client);
}
void FindInPage::GetNearestFindResult(const WebFloatPoint& point,
GetNearestFindResultCallback callback) {
float distance;
EnsureTextFinder().NearestFindMatch(point, &distance);
std::move(callback).Run(distance);
}
void FindInPage::FindMatchRects(int current_version,
FindMatchRectsCallback callback) {
int rects_version = FindMatchMarkersVersion();
Vector<WebFloatRect> rects;
if (current_version != rects_version)
rects = EnsureTextFinder().FindMatchRects();
std::move(callback).Run(rects_version, rects, ActiveFindMatchRect());
}
void FindInPage::ClearActiveFindMatch() {
// TODO(rakina): Do collapse selection as this currently does nothing.
frame_->ExecuteCommand(WebString::FromUTF8("CollapseSelection"));
EnsureTextFinder().ClearActiveFindMatch();
}
void WebLocalFrameImpl::SetTickmarks(const WebVector<WebRect>& tickmarks) {
find_in_page_->SetTickmarks(tickmarks);
}
void FindInPage::SetTickmarks(const WebVector<WebRect>& tickmarks) {
if (LayoutView* layout_view = frame_->GetFrame()->ContentLayoutObject()) {
Vector<IntRect> tickmarks_converted(tickmarks.size());
for (size_t i = 0; i < tickmarks.size(); ++i)
tickmarks_converted[i] = tickmarks[i];
layout_view->OverrideTickmarks(tickmarks_converted);
}
}
TextFinder* WebLocalFrameImpl::GetTextFinder() const {
return find_in_page_->GetTextFinder();
}
TextFinder* FindInPage::GetTextFinder() const {
return text_finder_;
}
TextFinder& WebLocalFrameImpl::EnsureTextFinder() {
return find_in_page_->EnsureTextFinder();
}
TextFinder& FindInPage::EnsureTextFinder() {
if (!text_finder_)
text_finder_ = TextFinder::Create(*frame_);
return *text_finder_;
}
void FindInPage::SetPluginFindHandler(WebPluginContainer* plugin) {
plugin_find_handler_ = plugin;
}
WebPluginContainer* FindInPage::PluginFindHandler() const {
return plugin_find_handler_;
}
WebPlugin* FindInPage::GetWebPluginForFind() {
if (frame_->GetDocument().IsPluginDocument())
return frame_->GetDocument().To<WebPluginDocument>().Plugin();
if (plugin_find_handler_)
return plugin_find_handler_->Plugin();
return nullptr;
}
void FindInPage::BindToRequest(
mojom::blink::FindInPageAssociatedRequest request) {
binding_.Bind(std::move(request));
}
void FindInPage::Dispose() {
binding_.Close();
}
void FindInPage::ContextDestroyed(ExecutionContext* context) {
binding_.Close();
}
void FindInPage::ReportFindInPageMatchCount(int request_id,
int count,
bool final_update) {
// In tests, |client_| might not be set.
if (!client_)
return;
client_->SetNumberOfMatches(
request_id, count,
final_update ? mojom::blink::FindMatchUpdateType::kFinalUpdate
: mojom::blink::FindMatchUpdateType::kMoreUpdatesComing);
}
void FindInPage::ReportFindInPageSelection(int request_id,
int active_match_ordinal,
const blink::WebRect& selection_rect,
bool final_update) {
// In tests, |client_| might not be set.
if (!client_)
return;
client_->SetActiveMatch(
request_id, selection_rect, active_match_ordinal,
final_update ? mojom::blink::FindMatchUpdateType::kFinalUpdate
: mojom::blink::FindMatchUpdateType::kMoreUpdatesComing);
}
} // namespace blink