// Copyright 2012 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 "chrome/renderer/searchbox/searchbox.h" #include #include #include #include "base/bind.h" #include "base/logging.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "chrome/common/search/omnibox.mojom.h" #include "chrome/common/search/search.mojom.h" #include "chrome/common/webui_url_constants.h" #include "chrome/renderer/searchbox/searchbox_extension.h" #include "components/favicon_base/favicon_types.h" #include "components/favicon_base/favicon_url_parser.h" #include "components/url_formatter/url_fixer.h" #include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_view.h" #include "mojo/public/cpp/bindings/pending_associated_remote.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h" #include "third_party/blink/public/web/web_frame.h" #include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_performance.h" namespace { // The size of the InstantMostVisitedItem cache. const size_t kMaxInstantMostVisitedItemCacheSize = 100; // Returns true if items stored in |old_item_id_pairs| and |new_items| are // equal. bool AreMostVisitedItemsEqual( const std::vector& old_item_id_pairs, const std::vector& new_items) { if (old_item_id_pairs.size() != new_items.size()) return false; for (size_t i = 0; i < new_items.size(); ++i) { if (new_items[i].url != old_item_id_pairs[i].second.url || new_items[i].title != old_item_id_pairs[i].second.title || new_items[i].source != old_item_id_pairs[i].second.source) { return false; } } return true; } // Helper for SearchBox::GenerateImageURLFromTransientURL(). class SearchBoxIconURLHelper: public SearchBox::IconURLHelper { public: explicit SearchBoxIconURLHelper(const SearchBox* search_box); ~SearchBoxIconURLHelper() override; int GetViewID() const override; std::string GetURLStringFromRestrictedID(InstantRestrictedID rid) const override; private: const SearchBox* search_box_; }; SearchBoxIconURLHelper::SearchBoxIconURLHelper(const SearchBox* search_box) : search_box_(search_box) { } SearchBoxIconURLHelper::~SearchBoxIconURLHelper() { } int SearchBoxIconURLHelper::GetViewID() const { return search_box_->render_frame()->GetRenderView()->GetRoutingID(); } std::string SearchBoxIconURLHelper::GetURLStringFromRestrictedID( InstantRestrictedID rid) const { InstantMostVisitedItem item; if (!search_box_->GetMostVisitedItemWithID(rid, &item)) return std::string(); return item.url.spec(); } } // namespace namespace internal { // for testing // Parses "/". If successful, assigns // |*view_id| := "", |*rid| := "", and returns true. bool ParseViewIdAndRestrictedId(const std::string& id_part, int* view_id_out, InstantRestrictedID* rid_out) { DCHECK(view_id_out); DCHECK(rid_out); // Check that the path is of Most visited item ID form. std::vector tokens = base::SplitStringPiece( id_part, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); if (tokens.size() != 2) return false; int view_id; InstantRestrictedID rid; if (!base::StringToInt(tokens[0], &view_id) || view_id < 0 || !base::StringToInt(tokens[1], &rid) || rid < 0) return false; *view_id_out = view_id; *rid_out = rid; return true; } // Takes a favicon |url| that looks like: // // chrome-search://favicon// // chrome-search://favicon/// // // If successful, assigns |*param_part| := "" or "/" (note trailing // slash), |*view_id| := "", |*rid| := "rid", and returns true. bool ParseIconRestrictedUrl(const GURL& url, std::string* param_part, int* view_id, InstantRestrictedID* rid) { DCHECK(param_part); DCHECK(view_id); DCHECK(rid); // Strip leading slash. std::string raw_path = url.path(); DCHECK_GT(raw_path.length(), (size_t) 0); DCHECK_EQ(raw_path[0], '/'); raw_path = raw_path.substr(1); // Get the starting index of the page URL. chrome::ParsedFaviconPath parsed; if (!chrome::ParseFaviconPath( raw_path, chrome::FaviconUrlFormat::kFaviconLegacy, &parsed)) { return false; } int path_index = parsed.path_index; std::string id_part = raw_path.substr(path_index); if (!ParseViewIdAndRestrictedId(id_part, view_id, rid)) return false; *param_part = raw_path.substr(0, path_index); return true; } void TranslateIconRestrictedUrl(const GURL& transient_url, const SearchBox::IconURLHelper& helper, GURL* url) { std::string params; int view_id = -1; InstantRestrictedID rid = -1; if (!internal::ParseIconRestrictedUrl(transient_url, ¶ms, &view_id, &rid) || view_id != helper.GetViewID()) { *url = GURL(base::StringPrintf("chrome-search://%s/", chrome::kChromeUIFaviconHost)); } else { std::string item_url = helper.GetURLStringFromRestrictedID(rid); *url = GURL(base::StringPrintf("chrome-search://%s/%s%s", chrome::kChromeUIFaviconHost, params.c_str(), item_url.c_str())); } } std::string FixupAndValidateUrl(const std::string& url) { GURL gurl = url_formatter::FixupURL(url, /*desired_tld=*/std::string()); if (!gurl.is_valid()) return std::string(); // Unless "http" was specified, replaces FixupURL's default "http" with // "https". if (url.find(std::string("http://")) == std::string::npos && gurl.SchemeIs(url::kHttpScheme)) { GURL::Replacements replacements; replacements.SetSchemeStr(url::kHttpsScheme); gurl = gurl.ReplaceComponents(replacements); } return gurl.spec(); } } // namespace internal SearchBox::IconURLHelper::IconURLHelper() = default; SearchBox::IconURLHelper::~IconURLHelper() = default; SearchBox::SearchBox(content::RenderFrame* render_frame) : content::RenderFrameObserver(render_frame), content::RenderFrameObserverTracker(render_frame), can_run_js_in_renderframe_(false), page_seq_no_(0), is_focused_(false), is_input_in_progress_(false), is_key_capture_enabled_(false), most_visited_items_cache_(kMaxInstantMostVisitedItemCacheSize), has_received_most_visited_(false) { // Connect to the embedded search interface in the browser. mojo::AssociatedRemote connector; render_frame->GetRemoteAssociatedInterfaces()->GetInterface(&connector); mojo::PendingAssociatedRemote embedded_search_client; receiver_.Bind(embedded_search_client.InitWithNewEndpointAndPassReceiver(), render_frame->GetTaskRunner( blink::TaskType::kInternalNavigationAssociated)); connector->Connect(embedded_search_service_.BindNewEndpointAndPassReceiver( render_frame->GetTaskRunner( blink::TaskType::kInternalNavigationAssociated)), std::move(embedded_search_client)); } SearchBox::~SearchBox() = default; void SearchBox::LogEvent(NTPLoggingEventType event) { base::Time navigation_start = base::Time::FromDoubleT( render_frame()->GetWebFrame()->Performance().NavigationStart()); base::Time now = base::Time::Now(); base::TimeDelta delta = now - navigation_start; embedded_search_service_->LogEvent(page_seq_no_, event, delta); } void SearchBox::LogSuggestionEventWithValue( NTPSuggestionsLoggingEventType event, int data) { base::Time navigation_start = base::Time::FromDoubleT( render_frame()->GetWebFrame()->Performance().NavigationStart()); base::Time now = base::Time::Now(); base::TimeDelta delta = now - navigation_start; embedded_search_service_->LogSuggestionEventWithValue(page_seq_no_, event, data, delta); } void SearchBox::LogMostVisitedImpression( const ntp_tiles::NTPTileImpression& impression) { embedded_search_service_->LogMostVisitedImpression(page_seq_no_, impression); } void SearchBox::LogMostVisitedNavigation( const ntp_tiles::NTPTileImpression& impression) { embedded_search_service_->LogMostVisitedNavigation(page_seq_no_, impression); } void SearchBox::DeleteMostVisitedItem( InstantRestrictedID most_visited_item_id) { GURL url = GetURLForMostVisitedItem(most_visited_item_id); if (!url.is_valid()) return; embedded_search_service_->DeleteMostVisitedItem(page_seq_no_, url); } void SearchBox::GenerateImageURLFromTransientURL(const GURL& transient_url, GURL* url) const { SearchBoxIconURLHelper helper(this); internal::TranslateIconRestrictedUrl(transient_url, helper, url); } void SearchBox::GetMostVisitedItems( std::vector* items) const { most_visited_items_cache_.GetCurrentItems(items); } bool SearchBox::AreMostVisitedItemsAvailable() const { return has_received_most_visited_; } bool SearchBox::GetMostVisitedItemWithID( InstantRestrictedID most_visited_item_id, InstantMostVisitedItem* item) const { return most_visited_items_cache_.GetItemWithRestrictedID(most_visited_item_id, item); } const NtpTheme* SearchBox::GetNtpTheme() const { return base::OptionalOrNullptr(theme_); } void SearchBox::Paste(const base::string16& text) { embedded_search_service_->PasteAndOpenDropdown(page_seq_no_, text); } void SearchBox::StartCapturingKeyStrokes() { embedded_search_service_->FocusOmnibox(page_seq_no_, OMNIBOX_FOCUS_INVISIBLE); } void SearchBox::StopCapturingKeyStrokes() { embedded_search_service_->FocusOmnibox(page_seq_no_, OMNIBOX_FOCUS_NONE); } void SearchBox::UndoAllMostVisitedDeletions() { embedded_search_service_->UndoAllMostVisitedDeletions(page_seq_no_); } void SearchBox::UndoMostVisitedDeletion( InstantRestrictedID most_visited_item_id) { GURL url = GetURLForMostVisitedItem(most_visited_item_id); if (!url.is_valid()) return; embedded_search_service_->UndoMostVisitedDeletion(page_seq_no_, url); } bool SearchBox::IsCustomLinks() const { return most_visited_info_.items_are_custom_links; } bool SearchBox::IsUsingMostVisited() const { return most_visited_info_.use_most_visited; } bool SearchBox::AreShortcutsVisible() const { return most_visited_info_.is_visible; } void SearchBox::AddCustomLink(const GURL& url, const std::string& title) { if (!url.is_valid()) { AddCustomLinkResult(false); return; } embedded_search_service_->AddCustomLink( page_seq_no_, url, title, base::BindOnce(&SearchBox::AddCustomLinkResult, weak_ptr_factory_.GetWeakPtr())); } void SearchBox::UpdateCustomLink(InstantRestrictedID link_id, const GURL& new_url, const std::string& new_title) { GURL url = GetURLForMostVisitedItem(link_id); if (!url.is_valid()) { UpdateCustomLinkResult(false); return; } embedded_search_service_->UpdateCustomLink( page_seq_no_, url, new_url, new_title, base::BindOnce(&SearchBox::UpdateCustomLinkResult, weak_ptr_factory_.GetWeakPtr())); } void SearchBox::ReorderCustomLink(InstantRestrictedID link_id, int new_pos) { GURL url = GetURLForMostVisitedItem(link_id); if (!url.is_valid()) return; embedded_search_service_->ReorderCustomLink(page_seq_no_, url, new_pos); } void SearchBox::DeleteCustomLink(InstantRestrictedID most_visited_item_id) { GURL url = GetURLForMostVisitedItem(most_visited_item_id); if (!url.is_valid()) { DeleteCustomLinkResult(false); return; } embedded_search_service_->DeleteCustomLink( page_seq_no_, url, base::BindOnce(&SearchBox::DeleteCustomLinkResult, weak_ptr_factory_.GetWeakPtr())); } void SearchBox::UndoCustomLinkAction() { embedded_search_service_->UndoCustomLinkAction(page_seq_no_); } void SearchBox::ResetCustomLinks() { embedded_search_service_->ResetCustomLinks(page_seq_no_); } void SearchBox::ToggleMostVisitedOrCustomLinks() { embedded_search_service_->ToggleMostVisitedOrCustomLinks(page_seq_no_); } void SearchBox::ToggleShortcutsVisibility(bool do_notify) { embedded_search_service_->ToggleShortcutsVisibility(page_seq_no_, do_notify); } std::string SearchBox::FixupAndValidateUrl(const std::string& url) const { return internal::FixupAndValidateUrl(url); } void SearchBox::SetCustomBackgroundInfo(const GURL& background_url, const std::string& attribution_line_1, const std::string& attribution_line_2, const GURL& action_url, const std::string& collection_id) { embedded_search_service_->SetCustomBackgroundInfo( background_url, attribution_line_1, attribution_line_2, action_url, collection_id); } void SearchBox::SelectLocalBackgroundImage() { embedded_search_service_->SelectLocalBackgroundImage(); } void SearchBox::BlocklistSearchSuggestion(int task_version, long task_id) { embedded_search_service_->BlocklistSearchSuggestion(task_version, task_id); } void SearchBox::BlocklistSearchSuggestionWithHash( int task_version, long task_id, const std::vector& hash) { embedded_search_service_->BlocklistSearchSuggestionWithHash(task_version, task_id, hash); } void SearchBox::SearchSuggestionSelected(int task_version, long task_id, const std::vector& hash) { embedded_search_service_->SearchSuggestionSelected(task_version, task_id, hash); } void SearchBox::OptOutOfSearchSuggestions() { embedded_search_service_->OptOutOfSearchSuggestions(); } void SearchBox::ApplyDefaultTheme() { embedded_search_service_->ApplyDefaultTheme(); } void SearchBox::ApplyAutogeneratedTheme(SkColor color) { embedded_search_service_->ApplyAutogeneratedTheme(color); } void SearchBox::RevertThemeChanges() { embedded_search_service_->RevertThemeChanges(); } void SearchBox::ConfirmThemeChanges() { embedded_search_service_->ConfirmThemeChanges(); } void SearchBox::QueryAutocomplete(const base::string16& input, bool prevent_inline_autocomplete) { embedded_search_service_->QueryAutocomplete(input, prevent_inline_autocomplete); } void SearchBox::DeleteAutocompleteMatch(uint8_t line) { embedded_search_service_->DeleteAutocompleteMatch(line); } void SearchBox::StopAutocomplete(bool clear_result) { embedded_search_service_->StopAutocomplete(clear_result); } void SearchBox::LogCharTypedToRepaintLatency(uint32_t latency_ms) { embedded_search_service_->LogCharTypedToRepaintLatency(latency_ms); } void SearchBox::BlocklistPromo(const std::string& promo_id) { embedded_search_service_->BlocklistPromo(promo_id); } void SearchBox::OpenExtensionsPage(double button, bool alt_key, bool ctrl_key, bool meta_key, bool shift_key) { embedded_search_service_->OpenExtensionsPage(button, alt_key, ctrl_key, meta_key, shift_key); } void SearchBox::OpenAutocompleteMatch(uint8_t line, const GURL& url, bool are_matches_showing, double time_elapsed_since_last_focus, double button, bool alt_key, bool ctrl_key, bool meta_key, bool shift_key) { embedded_search_service_->OpenAutocompleteMatch( line, url, are_matches_showing, time_elapsed_since_last_focus, button, alt_key, ctrl_key, meta_key, shift_key); } void SearchBox::ToggleSuggestionGroupIdVisibility(int32_t suggestion_group_id) { embedded_search_service_->ToggleSuggestionGroupIdVisibility( suggestion_group_id); } void SearchBox::SetPageSequenceNumber(int page_seq_no) { page_seq_no_ = page_seq_no; } void SearchBox::FocusChanged(OmniboxFocusState new_focus_state, OmniboxFocusChangeReason reason) { bool key_capture_enabled = new_focus_state == OMNIBOX_FOCUS_INVISIBLE; if (key_capture_enabled != is_key_capture_enabled_) { // Tell the page if the key capture mode changed unless the focus state // changed because of TYPING. This is because in that case, the browser // hasn't really stopped capturing key strokes. // // (More practically, if we don't do this check, the page would receive // onkeycapturechange before the corresponding onchange, and the page would // have no way of telling whether the keycapturechange happened because of // some actual user action or just because they started typing.) if (reason != OMNIBOX_FOCUS_CHANGE_TYPING) { is_key_capture_enabled_ = key_capture_enabled; DVLOG(1) << render_frame() << " KeyCaptureChange"; if (can_run_js_in_renderframe_) { SearchBoxExtension::DispatchKeyCaptureChange( render_frame()->GetWebFrame()); } } } bool is_focused = new_focus_state == OMNIBOX_FOCUS_VISIBLE; if (is_focused != is_focused_) { is_focused_ = is_focused; DVLOG(1) << render_frame() << " FocusChange"; if (can_run_js_in_renderframe_) SearchBoxExtension::DispatchFocusChange(render_frame()->GetWebFrame()); } } void SearchBox::AddCustomLinkResult(bool success) { if (can_run_js_in_renderframe_) { SearchBoxExtension::DispatchAddCustomLinkResult( render_frame()->GetWebFrame(), success); } } void SearchBox::UpdateCustomLinkResult(bool success) { if (can_run_js_in_renderframe_) { SearchBoxExtension::DispatchUpdateCustomLinkResult( render_frame()->GetWebFrame(), success); } } void SearchBox::DeleteCustomLinkResult(bool success) { if (can_run_js_in_renderframe_) { SearchBoxExtension::DispatchDeleteCustomLinkResult( render_frame()->GetWebFrame(), success); } } void SearchBox::AutocompleteResultChanged( search::mojom::AutocompleteResultPtr result) { if (can_run_js_in_renderframe_) { SearchBoxExtension::DispatchAutocompleteResultChanged( render_frame()->GetWebFrame(), std::move(result)); } } void SearchBox::AutocompleteMatchImageAvailable(uint32_t match_index, const std::string& image_url, const std::string& data_url) { if (can_run_js_in_renderframe_) { SearchBoxExtension::DispatchAutocompleteMatchImageAvailable( render_frame()->GetWebFrame(), match_index, image_url, data_url); } } void SearchBox::MostVisitedInfoChanged( const InstantMostVisitedInfo& most_visited_info) { has_received_most_visited_ = true; most_visited_info_.items_are_custom_links = most_visited_info.items_are_custom_links; std::vector last_known_items; GetMostVisitedItems(&last_known_items); if (AreMostVisitedItemsEqual(last_known_items, most_visited_info.items) && most_visited_info_.use_most_visited == most_visited_info.use_most_visited && most_visited_info_.is_visible == most_visited_info.is_visible) { return; // Do not send duplicate onmostvisitedchange events. } most_visited_info_.use_most_visited = most_visited_info.use_most_visited; most_visited_info_.is_visible = most_visited_info.is_visible; most_visited_items_cache_.AddItems(most_visited_info.items); if (can_run_js_in_renderframe_) { SearchBoxExtension::DispatchMostVisitedChanged( render_frame()->GetWebFrame()); } } void SearchBox::SetInputInProgress(bool is_input_in_progress) { if (is_input_in_progress_ == is_input_in_progress) return; is_input_in_progress_ = is_input_in_progress; DVLOG(1) << render_frame() << " SetInputInProgress"; if (can_run_js_in_renderframe_) { if (is_input_in_progress_) SearchBoxExtension::DispatchInputStart(render_frame()->GetWebFrame()); else SearchBoxExtension::DispatchInputCancel(render_frame()->GetWebFrame()); } } void SearchBox::ThemeChanged(const NtpTheme& theme) { // Do not send duplicate notifications. if (theme_ == theme) return; theme_ = theme; if (can_run_js_in_renderframe_) SearchBoxExtension::DispatchThemeChange(render_frame()->GetWebFrame()); } void SearchBox::LocalBackgroundSelected() { if (can_run_js_in_renderframe_) { SearchBoxExtension::DispatchLocalBackgroundSelected( render_frame()->GetWebFrame()); } } GURL SearchBox::GetURLForMostVisitedItem(InstantRestrictedID item_id) const { InstantMostVisitedItem item; return GetMostVisitedItemWithID(item_id, &item) ? item.url : GURL(); } void SearchBox::DidCommitProvisionalLoad(ui::PageTransition transition) { can_run_js_in_renderframe_ = true; } void SearchBox::OnDestruct() { delete this; }