// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/find_request_manager.h" #include #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/frame_messages.h" #include "content/common/input_messages.h" #include "content/public/browser/guest_mode.h" namespace content { namespace { // The following functions allow traversal over all frames, including those // across WebContentses. // // Note that there are currently two different ways in which an inner // WebContents may be embedded in an outer WebContents: // // 1) As a guest of the outer WebContents's BrowserPluginEmbedder. // 2) Within an inner WebContentsTreeNode of the outer WebContents's // WebContentsTreeNode. // Returns all child frames of |node|. std::vector GetChildren(FrameTreeNode* node) { std::vector children(node->child_count()); for (size_t i = 0; i != node->child_count(); ++i) children[i] = node->child_at(i); if (auto* inner_contents = WebContentsImpl::FromOuterFrameTreeNode(node)) { children.push_back(inner_contents->GetMainFrame()->frame_tree_node()); } else { auto* contents = WebContentsImpl::FromFrameTreeNode(node); if (node->IsMainFrame() && contents->GetBrowserPluginEmbedder()) { for (auto* inner_contents : contents->GetInnerWebContents()) { children.push_back(inner_contents->GetMainFrame()->frame_tree_node()); } } } return children; } // Returns the first child FrameTreeNode under |node|, if |node| has a child, or // nullptr otherwise. FrameTreeNode* GetFirstChild(FrameTreeNode* node) { auto children = GetChildren(node); if (!children.empty()) return children.front(); return nullptr; } // Returns the last child FrameTreeNode under |node|, if |node| has a child, or // nullptr otherwise. FrameTreeNode* GetLastChild(FrameTreeNode* node) { auto children = GetChildren(node); if (!children.empty()) return children.back(); return nullptr; } // Returns the deepest last child frame under |node|/|rfh| in the frame tree. FrameTreeNode* GetDeepestLastChild(FrameTreeNode* node) { while (FrameTreeNode* last_child = GetLastChild(node)) node = last_child; return node; } RenderFrameHost* GetDeepestLastChild(RenderFrameHost* rfh) { FrameTreeNode* node = static_cast(rfh)->frame_tree_node(); return GetDeepestLastChild(node)->current_frame_host(); } // Returns the parent FrameTreeNode of |node|, if |node| has a parent, or // nullptr otherwise. FrameTreeNode* GetParent(FrameTreeNode* node) { if (node->parent()) return node->parent(); // The parent frame may be in another WebContents. if (node->IsMainFrame()) { auto* contents = WebContentsImpl::FromFrameTreeNode(node); if (GuestMode::IsCrossProcessFrameGuest(contents)) { int node_id = contents->GetOuterDelegateFrameTreeNodeId(); if (node_id != FrameTreeNode::kFrameTreeNodeInvalidId) return FrameTreeNode::GloballyFindByID(node_id); } else if (auto* outer_contents = contents->GetOuterWebContents()) { return outer_contents->GetMainFrame()->frame_tree_node(); } } return nullptr; } // Returns the previous sibling FrameTreeNode of |node|, if one exists, or // nullptr otherwise. FrameTreeNode* GetPreviousSibling(FrameTreeNode* node) { if (FrameTreeNode* previous_sibling = node->PreviousSibling()) return previous_sibling; // The previous sibling may be in another WebContents. if (FrameTreeNode* parent = GetParent(node)) { auto children = GetChildren(parent); auto it = std::find(children.begin(), children.end(), node); // It is odd that this node may not be a child of its parent, but this is // actually possible during teardown, hence the need for the check for // "it != children.end()". if (it != children.end() && it != children.begin()) return *--it; } return nullptr; } // Returns the next sibling FrameTreeNode of |node|, if one exists, or nullptr // otherwise. FrameTreeNode* GetNextSibling(FrameTreeNode* node) { if (FrameTreeNode* next_sibling = node->NextSibling()) return next_sibling; // The next sibling may be in another WebContents. if (FrameTreeNode* parent = GetParent(node)) { auto children = GetChildren(parent); auto it = std::find(children.begin(), children.end(), node); // It is odd that this node may not be a child of its parent, but this is // actually possible during teardown, hence the need for the check for // "it != children.end()". if (it != children.end() && ++it != children.end()) return *it; } return nullptr; } // Returns the FrameTreeNode directly after |node| in the frame tree in search // order, or nullptr if one does not exist. If |wrap| is set, then wrapping // between the first and last frames is permitted. Note that this traversal // follows the same ordering as in blink::FrameTree::traverseNextWithWrap(). FrameTreeNode* TraverseNext(FrameTreeNode* node, bool wrap) { if (FrameTreeNode* first_child = GetFirstChild(node)) return first_child; FrameTreeNode* sibling = GetNextSibling(node); while (!sibling) { FrameTreeNode* parent = GetParent(node); if (!parent) return wrap ? node : nullptr; node = parent; sibling = GetNextSibling(node); } return sibling; } // Returns the FrameTreeNode directly before |node| in the frame tree in search // order, or nullptr if one does not exist. If |wrap| is set, then wrapping // between the first and last frames is permitted. Note that this traversal // follows the same ordering as in blink::FrameTree::traversePreviousWithWrap(). FrameTreeNode* TraversePrevious(FrameTreeNode* node, bool wrap) { if (FrameTreeNode* previous_sibling = GetPreviousSibling(node)) return GetDeepestLastChild(previous_sibling); if (FrameTreeNode* parent = GetParent(node)) return parent; return wrap ? GetDeepestLastChild(node) : nullptr; } // The same as either TraverseNext() or TraversePrevious(), depending on // |forward|. FrameTreeNode* TraverseNode(FrameTreeNode* node, bool forward, bool wrap) { return forward ? TraverseNext(node, wrap) : TraversePrevious(node, wrap); } } // namespace // Observes searched WebContentses for frame changes, including deletion, // creation, and navigation. class FindRequestManager::FrameObserver : public WebContentsObserver { public: FrameObserver(WebContentsImpl* web_contents, FindRequestManager* manager) : WebContentsObserver(web_contents), manager_(manager) {} ~FrameObserver() override {} void DidFinishLoad(RenderFrameHost* rfh, const GURL& validated_url) override { if (manager_->current_session_id_ == kInvalidId) return; manager_->RemoveFrame(rfh); manager_->AddFrame(rfh, true /* force */); } void RenderFrameDeleted(RenderFrameHost* rfh) override { manager_->RemoveFrame(rfh); } void RenderFrameHostChanged(RenderFrameHost* old_host, RenderFrameHost* new_host) override { manager_->RemoveFrame(old_host); } void FrameDeleted(RenderFrameHost* rfh) override { manager_->RemoveFrame(rfh); } private: // The FindRequestManager that owns this FrameObserver. FindRequestManager* const manager_; DISALLOW_COPY_AND_ASSIGN(FrameObserver); }; #if defined(OS_ANDROID) FindRequestManager::ActivateNearestFindResultState:: ActivateNearestFindResultState() = default; FindRequestManager::ActivateNearestFindResultState:: ActivateNearestFindResultState(float x, float y) : current_request_id(GetNextID()), x(x), y(y) {} FindRequestManager::ActivateNearestFindResultState:: ~ActivateNearestFindResultState() {} FindRequestManager::FrameRects::FrameRects() = default; FindRequestManager::FrameRects::FrameRects(const std::vector& rects, int version) : rects(rects), version(version) {} FindRequestManager::FrameRects::~FrameRects() {} FindRequestManager::FindMatchRectsState::FindMatchRectsState() = default; FindRequestManager::FindMatchRectsState::~FindMatchRectsState() {} #endif // static const int FindRequestManager::kInvalidId = -1; FindRequestManager::FindRequestManager(WebContentsImpl* web_contents) : contents_(web_contents), current_session_id_(kInvalidId), pending_find_next_reply_(nullptr), pending_active_match_ordinal_(false), number_of_matches_(0), active_frame_(nullptr), relative_active_match_ordinal_(0), active_match_ordinal_(0), last_reported_id_(kInvalidId) {} FindRequestManager::~FindRequestManager() {} void FindRequestManager::Find(int request_id, const base::string16& search_text, const blink::WebFindOptions& options) { // Every find request must have a unique ID, and these IDs must strictly // increase so that newer requests always have greater IDs than older // requests. DCHECK_GT(request_id, current_request_.id); DCHECK_GT(request_id, current_session_id_); // If this is a new find session, clear any queued requests from last session. if (!options.find_next) find_request_queue_ = std::queue(); find_request_queue_.emplace(request_id, search_text, options); if (find_request_queue_.size() == 1) FindInternal(find_request_queue_.front()); } void FindRequestManager::StopFinding(StopFindAction action) { for (WebContentsImpl* contents : contents_->GetWebContentsAndAllInner()) { contents->SendToAllFrames( new FrameMsg_StopFinding(MSG_ROUTING_NONE, action)); } current_session_id_ = kInvalidId; #if defined(OS_ANDROID) // It is important that these pending replies are cleared whenever a find // session ends, so that subsequent replies for the old session are ignored. activate_.pending_replies.clear(); match_rects_.pending_replies.clear(); #endif } void FindRequestManager::OnFindReply(RenderFrameHost* rfh, int request_id, int number_of_matches, const gfx::Rect& selection_rect, int active_match_ordinal, bool final_update) { // Ignore stale replies from abandoned find sessions or dead frames. if (current_session_id_ == kInvalidId || request_id < current_session_id_ || !CheckFrame(rfh)) { return; } // Update the stored find results. DCHECK_GE(number_of_matches, -1); DCHECK_GE(active_match_ordinal, -1); // Check for an update to the number of matches. if (number_of_matches != -1) { DCHECK_GE(number_of_matches, 0); auto matches_per_frame_it = matches_per_frame_.find(rfh); if (int matches_delta = number_of_matches - matches_per_frame_it->second) { // Increment the global number of matches by the number of additional // matches found for this frame. number_of_matches_ += matches_delta; matches_per_frame_it->second = number_of_matches; // All matches may have been removed since the last find reply. if (rfh == active_frame_ && !number_of_matches) relative_active_match_ordinal_ = 0; // The active match ordinal may need updating since the number of matches // before the active match may have changed. if (rfh != active_frame_ || !number_of_matches) UpdateActiveMatchOrdinal(); } } // Check for an update to the selection rect. if (!selection_rect.IsEmpty()) selection_rect_ = selection_rect; // Check for an update to the active match ordinal. if (active_match_ordinal > 0) { if (rfh == active_frame_) { active_match_ordinal_ += active_match_ordinal - relative_active_match_ordinal_; relative_active_match_ordinal_ = active_match_ordinal; } else { if (active_frame_) { // The new active match is in a different frame than the previous, so // the previous active frame needs to be informed (to clear its active // match highlighting). active_frame_->Send(new FrameMsg_ClearActiveFindMatch( active_frame_->GetRoutingID())); } active_frame_ = rfh; relative_active_match_ordinal_ = active_match_ordinal; UpdateActiveMatchOrdinal(); } if (pending_active_match_ordinal_ && request_id == current_request_.id) pending_active_match_ordinal_ = false; AdvanceQueue(request_id); } if (!final_update) { NotifyFindReply(request_id, false /* final_update */); return; } // This is the final update for this frame for the current find operation. pending_initial_replies_.erase(rfh); if (request_id == current_session_id_ && !pending_initial_replies_.empty()) { NotifyFindReply(request_id, false /* final_update */); return; } // This is the final update for the current find operation. if (request_id == current_request_.id && request_id != current_session_id_) { DCHECK(current_request_.options.find_next); DCHECK_EQ(pending_find_next_reply_, rfh); pending_find_next_reply_ = nullptr; } FinalUpdateReceived(request_id, rfh); } void FindRequestManager::RemoveFrame(RenderFrameHost* rfh) { if (current_session_id_ == kInvalidId || !CheckFrame(rfh)) return; // If matches are counted for the frame that is being removed, decrement the // match total before erasing that entry. auto it = matches_per_frame_.find(rfh); if (it != matches_per_frame_.end()) { number_of_matches_ -= it->second; matches_per_frame_.erase(it); } // Update the active match ordinal, since it may have changed. if (active_frame_ == rfh) { active_frame_ = nullptr; relative_active_match_ordinal_ = 0; selection_rect_ = gfx::Rect(); } UpdateActiveMatchOrdinal(); #if defined(OS_ANDROID) // The removed frame may contain the nearest find result known so far. Note // that once all queried frames have responded, if this result was the overall // nearest, then no activation will occur. if (rfh == activate_.nearest_frame) activate_.nearest_frame = nullptr; // Match rects in the removed frame are no longer relevant. if (match_rects_.frame_rects.count(rfh)) { match_rects_.frame_rects.erase(rfh); ++match_rects_.known_version; } // A reply should not be expected from the removed frame. RemoveNearestFindResultPendingReply(rfh); RemoveFindMatchRectsPendingReply(rfh); #endif // If no pending find replies are expected for the removed frame, then just // report the updated results. if (!pending_initial_replies_.count(rfh) && pending_find_next_reply_ != rfh) { bool final_update = pending_initial_replies_.empty() && !pending_find_next_reply_; NotifyFindReply(current_session_id_, final_update); return; } if (pending_initial_replies_.count(rfh)) { // A reply should not be expected from the removed frame. pending_initial_replies_.erase(rfh); if (pending_initial_replies_.empty()) { FinalUpdateReceived(current_session_id_, rfh); } } if (pending_find_next_reply_ == rfh) { // A reply should not be expected from the removed frame. pending_find_next_reply_ = nullptr; FinalUpdateReceived(current_request_.id, rfh); } } #if defined(OS_ANDROID) void FindRequestManager::ActivateNearestFindResult(float x, float y) { if (current_session_id_ == kInvalidId) return; activate_ = ActivateNearestFindResultState(x, y); // Request from each frame the distance to the nearest find result (in that // frame) from the point (x, y), defined in find-in-page coordinates. for (WebContentsImpl* contents : contents_->GetWebContentsAndAllInner()) { for (FrameTreeNode* node : contents->GetFrameTree()->Nodes()) { RenderFrameHost* rfh = node->current_frame_host(); if (!CheckFrame(rfh) || !rfh->IsRenderFrameLive()) continue; activate_.pending_replies.insert(rfh); rfh->Send(new FrameMsg_GetNearestFindResult(rfh->GetRoutingID(), activate_.current_request_id, activate_.x, activate_.y)); } } } void FindRequestManager::OnGetNearestFindResultReply(RenderFrameHost* rfh, int request_id, float distance) { if (request_id != activate_.current_request_id || !activate_.pending_replies.count(rfh)) { return; } // Check if this frame has a nearer find result than the current nearest. if (distance < activate_.nearest_distance) { activate_.nearest_frame = rfh; activate_.nearest_distance = distance; } RemoveNearestFindResultPendingReply(rfh); } void FindRequestManager::RequestFindMatchRects(int current_version) { match_rects_.pending_replies.clear(); match_rects_.request_version = current_version; // Request the latest find match rects from each frame. for (WebContentsImpl* contents : contents_->GetWebContentsAndAllInner()) { for (FrameTreeNode* node : contents->GetFrameTree()->Nodes()) { RenderFrameHost* rfh = node->current_frame_host(); if (!CheckFrame(rfh) || !rfh->IsRenderFrameLive()) continue; match_rects_.pending_replies.insert(rfh); auto it = match_rects_.frame_rects.find(rfh); int version = (it != match_rects_.frame_rects.end()) ? it->second.version : kInvalidId; rfh->Send(new FrameMsg_FindMatchRects(rfh->GetRoutingID(), version)); } } } void FindRequestManager::OnFindMatchRectsReply( RenderFrameHost* rfh, int version, const std::vector& rects, const gfx::RectF& active_rect) { auto it = match_rects_.frame_rects.find(rfh); if (it == match_rects_.frame_rects.end() || it->second.version != version) { // New version of rects has been received, so update the data. match_rects_.frame_rects[rfh] = FrameRects(rects, version); ++match_rects_.known_version; } if (!active_rect.IsEmpty()) match_rects_.active_rect = active_rect; RemoveFindMatchRectsPendingReply(rfh); } #endif void FindRequestManager::Reset(const FindRequest& initial_request) { current_session_id_ = initial_request.id; current_request_ = initial_request; pending_initial_replies_.clear(); pending_find_next_reply_ = nullptr; pending_active_match_ordinal_ = true; matches_per_frame_.clear(); number_of_matches_ = 0; active_frame_ = nullptr; relative_active_match_ordinal_ = 0; active_match_ordinal_ = 0; selection_rect_ = gfx::Rect(); last_reported_id_ = kInvalidId; frame_observers_.clear(); #if defined(OS_ANDROID) activate_ = ActivateNearestFindResultState(); match_rects_.pending_replies.clear(); #endif } void FindRequestManager::FindInternal(const FindRequest& request) { DCHECK_GT(request.id, current_request_.id); DCHECK_GT(request.id, current_session_id_); if (request.options.find_next) { // This is a find next operation. // This implies that there is an ongoing find session with the same search // text. DCHECK_GE(current_session_id_, 0); DCHECK_EQ(request.search_text, current_request_.search_text); // The find next request will be directed at the focused frame if there is // one, or the first frame with matches otherwise. RenderFrameHost* target_rfh = contents_->GetFocusedWebContents()->GetFocusedFrame(); if (!target_rfh || !CheckFrame(target_rfh)) target_rfh = GetInitialFrame(request.options.forward); SendFindIPC(request, target_rfh); current_request_ = request; pending_active_match_ordinal_ = true; return; } // This is an initial find operation. Reset(request); for (WebContentsImpl* contents : contents_->GetWebContentsAndAllInner()) { frame_observers_.push_back(base::MakeUnique(contents, this)); for (FrameTreeNode* node : contents->GetFrameTree()->Nodes()) { AddFrame(node->current_frame_host(), false /* force */); } } } void FindRequestManager::AdvanceQueue(int request_id) { if (find_request_queue_.empty() || request_id != find_request_queue_.front().id) { return; } find_request_queue_.pop(); if (!find_request_queue_.empty()) FindInternal(find_request_queue_.front()); } void FindRequestManager::SendFindIPC(const FindRequest& request, RenderFrameHost* rfh) { DCHECK(CheckFrame(rfh)); DCHECK(rfh->IsRenderFrameLive()); if (request.options.find_next) pending_find_next_reply_ = rfh; else pending_initial_replies_.insert(rfh); rfh->Send(new FrameMsg_Find(rfh->GetRoutingID(), request.id, request.search_text, request.options)); } void FindRequestManager::NotifyFindReply(int request_id, bool final_update) { if (request_id == kInvalidId) { NOTREACHED(); return; } // Ensure that replies are not reported with IDs lower than the ID of the // latest request we have results from. if (request_id < last_reported_id_) request_id = last_reported_id_; else last_reported_id_ = request_id; contents_->NotifyFindReply(request_id, number_of_matches_, selection_rect_, active_match_ordinal_, final_update); } RenderFrameHost* FindRequestManager::GetInitialFrame(bool forward) const { RenderFrameHost* rfh = contents_->GetMainFrame(); if (!forward) rfh = GetDeepestLastChild(rfh); return rfh; } RenderFrameHost* FindRequestManager::Traverse(RenderFrameHost* from_rfh, bool forward, bool matches_only, bool wrap) const { DCHECK(from_rfh); FrameTreeNode* node = static_cast(from_rfh)->frame_tree_node(); while ((node = TraverseNode(node, forward, wrap)) != nullptr) { if (!CheckFrame(node->current_frame_host())) continue; RenderFrameHost* current_rfh = node->current_frame_host(); if (!matches_only || matches_per_frame_.find(current_rfh)->second || pending_initial_replies_.count(current_rfh)) { // Note that if there is still a pending reply expected for this frame, // then it may have unaccounted matches and will not be skipped via // |matches_only|. return node->current_frame_host(); } if (wrap && node->current_frame_host() == from_rfh) return nullptr; } return nullptr; } void FindRequestManager::AddFrame(RenderFrameHost* rfh, bool force) { if (!rfh || !rfh->IsRenderFrameLive()) return; // A frame that is already being searched should not normally be added again. DCHECK(force || !CheckFrame(rfh)); matches_per_frame_[rfh] = 0; FindRequest request = current_request_; request.id = current_session_id_; request.options.find_next = false; request.options.force = force; SendFindIPC(request, rfh); } bool FindRequestManager::CheckFrame(RenderFrameHost* rfh) const { return rfh && matches_per_frame_.count(rfh); } void FindRequestManager::UpdateActiveMatchOrdinal() { active_match_ordinal_ = 0; if (!active_frame_ || !relative_active_match_ordinal_) { DCHECK(!active_frame_ && !relative_active_match_ordinal_); return; } // Traverse the frame tree backwards (in search order) and count all of the // matches in frames before the frame with the active match, in order to // determine the overall active match ordinal. RenderFrameHost* frame = active_frame_; while ((frame = Traverse(frame, false /* forward */, true /* matches_only */, false /* wrap */)) != nullptr) { active_match_ordinal_ += matches_per_frame_[frame]; } active_match_ordinal_ += relative_active_match_ordinal_; } void FindRequestManager::FinalUpdateReceived(int request_id, RenderFrameHost* rfh) { if (!number_of_matches_ || (active_match_ordinal_ && !pending_active_match_ordinal_) || pending_find_next_reply_) { // All the find results for |request_id| are in and ready to report. Note // that |final_update| will be set to false if there are still pending // replies expected from the initial find request. NotifyFindReply(request_id, pending_initial_replies_.empty() /* final_update */); AdvanceQueue(request_id); return; } // There are matches, but no active match was returned, so another find next // request must be sent. RenderFrameHost* target_rfh; if (request_id == current_request_.id && current_request_.options.find_next) { // If this was a find next operation, then the active match will be in the // next frame with matches after this one. target_rfh = Traverse(rfh, current_request_.options.forward, true /* matches_only */, true /* wrap */); } else if ((target_rfh = contents_->GetFocusedWebContents()->GetFocusedFrame()) != nullptr) { // Otherwise, if there is a focused frame, then the active match will be in // the next frame with matches after that one. target_rfh = Traverse(target_rfh, current_request_.options.forward, true /* matches_only */, true /* wrap */); } else { // Otherwise, the first frame with matches will have the active match. target_rfh = GetInitialFrame(current_request_.options.forward); if (!CheckFrame(target_rfh) || !matches_per_frame_[target_rfh]) { target_rfh = Traverse(target_rfh, current_request_.options.forward, true /* matches_only */, false /* wrap */); } } DCHECK(target_rfh); // Forward the find reply without |final_update| set because the active match // has not yet been found. NotifyFindReply(request_id, false /* final_update */); current_request_.options.find_next = true; SendFindIPC(current_request_, target_rfh); } #if defined(OS_ANDROID) void FindRequestManager::RemoveNearestFindResultPendingReply( RenderFrameHost* rfh) { auto it = activate_.pending_replies.find(rfh); if (it == activate_.pending_replies.end()) return; activate_.pending_replies.erase(it); if (activate_.pending_replies.empty() && CheckFrame(activate_.nearest_frame)) { activate_.nearest_frame->Send(new FrameMsg_ActivateNearestFindResult( activate_.nearest_frame->GetRoutingID(), current_session_id_, activate_.x, activate_.y)); } } void FindRequestManager::RemoveFindMatchRectsPendingReply( RenderFrameHost* rfh) { auto it = match_rects_.pending_replies.find(rfh); if (it == match_rects_.pending_replies.end()) return; match_rects_.pending_replies.erase(it); if (match_rects_.pending_replies.empty()) { // All replies are in. std::vector aggregate_rects; if (match_rects_.request_version != match_rects_.known_version) { // Request version is stale, so aggregate and report the newer find // match rects. The rects should be aggregated in search order. for (RenderFrameHost* frame = GetInitialFrame(true /* forward */); frame; frame = Traverse(frame, true /* forward */, true /* matches_only */, false /* wrap */)) { auto it = match_rects_.frame_rects.find(frame); if (it == match_rects_.frame_rects.end()) continue; std::vector& frame_rects = it->second.rects; aggregate_rects.insert( aggregate_rects.end(), frame_rects.begin(), frame_rects.end()); } } contents_->NotifyFindMatchRectsReply( match_rects_.known_version, aggregate_rects, match_rects_.active_rect); } } #endif } // namespace content