| // Copyright 2019 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/browser/ui/webui/new_tab_page/new_tab_page_handler.h" |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/containers/flat_map.h" |
| #include "base/files/file_path.h" |
| #include "base/i18n/rtl.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_string_value_serializer.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h" |
| #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h" |
| #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service_factory.h" |
| #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/browser/favicon/favicon_service_factory.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/predictors/autocomplete_action_predictor.h" |
| #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_metrics.h" |
| #include "chrome/browser/search/background/ntp_background_service.h" |
| #include "chrome/browser/search/background/ntp_background_service_factory.h" |
| #include "chrome/browser/search/instant_service.h" |
| #include "chrome/browser/search/one_google_bar/one_google_bar_service_factory.h" |
| #include "chrome/browser/search/promos/promo_service_factory.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/browser/search_provider_logos/logo_service_factory.h" |
| #include "chrome/browser/ui/bookmarks/bookmark_stats.h" |
| #include "chrome/browser/ui/chrome_select_file_policy.h" |
| #include "chrome/browser/ui/search/omnibox_mojo_utils.h" |
| #include "chrome/browser/ui/search/omnibox_utils.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/search/instant_types.h" |
| #include "chrome/common/url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/keyed_service/core/service_access_type.h" |
| #include "components/navigation_metrics/navigation_metrics.h" |
| #include "components/omnibox/browser/autocomplete_classifier.h" |
| #include "components/omnibox/browser/autocomplete_input.h" |
| #include "components/omnibox/browser/autocomplete_match.h" |
| #include "components/omnibox/browser/autocomplete_match_type.h" |
| #include "components/omnibox/browser/omnibox_controller_emitter.h" |
| #include "components/omnibox/browser/omnibox_event_global_tracker.h" |
| #include "components/omnibox/browser/omnibox_log.h" |
| #include "components/omnibox/browser/omnibox_prefs.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/search/ntp_features.h" |
| #include "components/search_engines/omnibox_focus_type.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/search_provider_logos/logo_service.h" |
| #include "components/search_provider_logos/switches.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "net/cookies/cookie_util.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/webui/web_ui_util.h" |
| #include "ui/gfx/color_utils.h" |
| |
| namespace { |
| |
| const int64_t kMaxDownloadBytes = 1024 * 1024; |
| |
| new_tab_page::mojom::ThemePtr MakeTheme(const NtpTheme& ntp_theme) { |
| auto theme = new_tab_page::mojom::Theme::New(); |
| theme->is_default = ntp_theme.using_default_theme; |
| theme->background_color = ntp_theme.background_color; |
| theme->shortcut_background_color = ntp_theme.shortcut_color; |
| theme->shortcut_text_color = ntp_theme.text_color; |
| theme->shortcut_use_white_add_icon = |
| color_utils::IsDark(ntp_theme.shortcut_color); |
| theme->shortcut_use_title_pill = false; |
| theme->is_dark = !color_utils::IsDark(ntp_theme.text_color); |
| if (ntp_theme.logo_alternate) { |
| theme->logo_color = ntp_theme.logo_color; |
| } |
| auto background_image = new_tab_page::mojom::BackgroundImage::New(); |
| if (!ntp_theme.custom_background_url.is_empty()) { |
| base::StringPiece url = ntp_theme.custom_background_url.spec(); |
| // TODO(crbug.com/1041125): Clean up when chrome-search://local-ntp removed. |
| if (base::StartsWith(url, "chrome-search://local-ntp/")) { |
| background_image->url = |
| GURL("chrome-untrusted://new-tab-page/" + |
| url.substr(strlen("chrome-search://local-ntp/")).as_string()); |
| } else { |
| background_image->url = ntp_theme.custom_background_url; |
| } |
| } else if (ntp_theme.has_theme_image) { |
| theme->shortcut_use_title_pill = true; |
| background_image->url = |
| GURL(base::StrCat({"chrome-untrusted://theme/IDR_THEME_NTP_BACKGROUND?", |
| ntp_theme.theme_id})); |
| background_image->url_2x = GURL( |
| base::StrCat({"chrome-untrusted://theme/IDR_THEME_NTP_BACKGROUND@2x?", |
| ntp_theme.theme_id})); |
| if (ntp_theme.has_attribution) { |
| background_image->attribution_url = GURL(base::StrCat( |
| {"chrome://theme/IDR_THEME_NTP_ATTRIBUTION?", ntp_theme.theme_id})); |
| } |
| background_image->size = "initial"; |
| switch (ntp_theme.image_tiling) { |
| case THEME_BKGRND_IMAGE_NO_REPEAT: |
| background_image->repeat_x = "no-repeat"; |
| background_image->repeat_y = "no-repeat"; |
| break; |
| case THEME_BKGRND_IMAGE_REPEAT_X: |
| background_image->repeat_x = "repeat"; |
| background_image->repeat_y = "no-repeat"; |
| break; |
| case THEME_BKGRND_IMAGE_REPEAT_Y: |
| background_image->repeat_x = "no-repeat"; |
| background_image->repeat_y = "repeat"; |
| break; |
| case THEME_BKGRND_IMAGE_REPEAT: |
| background_image->repeat_x = "repeat"; |
| background_image->repeat_y = "repeat"; |
| break; |
| } |
| switch (ntp_theme.image_horizontal_alignment) { |
| case THEME_BKGRND_IMAGE_ALIGN_CENTER: |
| background_image->position_x = "center"; |
| break; |
| case THEME_BKGRND_IMAGE_ALIGN_LEFT: |
| background_image->position_x = "left"; |
| break; |
| case THEME_BKGRND_IMAGE_ALIGN_RIGHT: |
| background_image->position_x = "right"; |
| break; |
| case THEME_BKGRND_IMAGE_ALIGN_TOP: |
| case THEME_BKGRND_IMAGE_ALIGN_BOTTOM: |
| // Inconsistent. Ignore. |
| break; |
| } |
| switch (ntp_theme.image_vertical_alignment) { |
| case THEME_BKGRND_IMAGE_ALIGN_CENTER: |
| background_image->position_y = "center"; |
| break; |
| case THEME_BKGRND_IMAGE_ALIGN_TOP: |
| background_image->position_y = "top"; |
| break; |
| case THEME_BKGRND_IMAGE_ALIGN_BOTTOM: |
| background_image->position_y = "bottom"; |
| break; |
| case THEME_BKGRND_IMAGE_ALIGN_LEFT: |
| case THEME_BKGRND_IMAGE_ALIGN_RIGHT: |
| // Inconsistent. Ignore. |
| break; |
| } |
| } else { |
| background_image = nullptr; |
| } |
| theme->background_image = std::move(background_image); |
| if (!ntp_theme.custom_background_attribution_line_1.empty()) { |
| theme->background_image_attribution_1 = |
| ntp_theme.custom_background_attribution_line_1; |
| } |
| if (!ntp_theme.custom_background_attribution_line_2.empty()) { |
| theme->background_image_attribution_2 = |
| ntp_theme.custom_background_attribution_line_2; |
| } |
| if (!ntp_theme.custom_background_attribution_action_url.is_empty()) { |
| theme->background_image_attribution_url = |
| ntp_theme.custom_background_attribution_action_url; |
| } |
| if (!ntp_theme.collection_id.empty()) { |
| theme->daily_refresh_collection_id = ntp_theme.collection_id; |
| } |
| |
| auto search_box = new_tab_page::mojom::SearchBoxTheme::New(); |
| search_box->bg = ntp_theme.search_box.bg; |
| search_box->icon = ntp_theme.search_box.icon; |
| search_box->icon_selected = ntp_theme.search_box.icon_selected; |
| search_box->placeholder = ntp_theme.search_box.placeholder; |
| search_box->results_bg = ntp_theme.search_box.results_bg; |
| search_box->results_bg_hovered = ntp_theme.search_box.results_bg_hovered; |
| search_box->results_bg_selected = ntp_theme.search_box.results_bg_selected; |
| search_box->results_dim = ntp_theme.search_box.results_dim; |
| search_box->results_dim_selected = ntp_theme.search_box.results_dim_selected; |
| search_box->results_text = ntp_theme.search_box.results_text; |
| search_box->results_text_selected = |
| ntp_theme.search_box.results_text_selected; |
| search_box->results_url = ntp_theme.search_box.results_url; |
| search_box->results_url_selected = ntp_theme.search_box.results_url_selected; |
| search_box->text = ntp_theme.search_box.text; |
| theme->search_box = std::move(search_box); |
| |
| return theme; |
| } |
| |
| ntp_tiles::NTPTileImpression MakeNTPTileImpression( |
| const new_tab_page::mojom::MostVisitedTile& tile, |
| uint32_t index) { |
| return ntp_tiles::NTPTileImpression( |
| /*index=*/index, |
| /*source=*/static_cast<ntp_tiles::TileSource>(tile.source), |
| /*title_source=*/ |
| static_cast<ntp_tiles::TileTitleSource>(tile.title_source), |
| /*visual_type=*/ |
| ntp_tiles::TileVisualType::ICON_REAL /* unused on desktop */, |
| /*icon_type=*/favicon_base::IconType::kInvalid /* unused on desktop */, |
| /*data_generation_time=*/tile.data_generation_time, |
| /*url_for_rappor=*/GURL() /* unused */); |
| } |
| |
| SkColor ParseHexColor(const std::string& color) { |
| SkColor result; |
| if (color.size() == 7 && color[0] == '#' && |
| base::HexStringToUInt(color.substr(1), &result)) { |
| return SkColorSetA(result, 255); |
| } |
| return SK_ColorTRANSPARENT; |
| } |
| |
| new_tab_page::mojom::ImageDoodlePtr MakeImageDoodle( |
| search_provider_logos::LogoType type, |
| const std::string& data, |
| const std::string& mime_type, |
| const GURL& animated_url, |
| int width_px, |
| int height_px, |
| const std::string& background_color, |
| int share_button_x, |
| int share_button_y, |
| const std::string& share_button_icon, |
| const std::string& share_button_bg, |
| GURL log_url, |
| GURL cta_log_url) { |
| auto doodle = new_tab_page::mojom::ImageDoodle::New(); |
| std::string base64; |
| base::Base64Encode(data, &base64); |
| doodle->image_url = GURL(base::StringPrintf( |
| "data:%s;base64,%s", mime_type.c_str(), base64.c_str())); |
| if (type == search_provider_logos::LogoType::ANIMATED) { |
| doodle->animation_url = animated_url; |
| } |
| doodle->width = width_px; |
| doodle->height = height_px; |
| doodle->background_color = ParseHexColor(background_color); |
| if (!share_button_icon.empty()) { |
| doodle->share_button = new_tab_page::mojom::DoodleShareButton::New(); |
| doodle->share_button->x = share_button_x; |
| doodle->share_button->y = share_button_y; |
| doodle->share_button->icon_url = GURL(base::StringPrintf( |
| "data:image/png;base64,%s", share_button_icon.c_str())); |
| doodle->share_button->background_color = ParseHexColor(share_button_bg); |
| } |
| if (type == search_provider_logos::LogoType::ANIMATED) { |
| doodle->image_impression_log_url = cta_log_url; |
| doodle->animation_impression_log_url = log_url; |
| } else { |
| doodle->image_impression_log_url = log_url; |
| } |
| return doodle; |
| } |
| |
| new_tab_page::mojom::PromoPtr MakePromo(const PromoData& data) { |
| // |data.middle_slot_json| is safe to be decoded here. The JSON string is part |
| // of a larger JSON initially decoded using the data decoder utility in the |
| // PromoService to base::Value. The middle-slot promo part is then reencoded |
| // from base::Value to a JSON string stored in |data.middle_slot_json|. |
| auto middle_slot = base::JSONReader::Read(data.middle_slot_json); |
| if (!middle_slot.has_value() || |
| middle_slot.value().FindBoolPath("hidden").value_or(false)) { |
| return nullptr; |
| } |
| auto promo = new_tab_page::mojom::Promo::New(); |
| promo->id = data.promo_id; |
| if (middle_slot.has_value()) { |
| auto* parts = middle_slot.value().FindListPath("part"); |
| if (parts) { |
| std::vector<new_tab_page::mojom::PromoPartPtr> mojom_parts; |
| for (const base::Value& part : parts->GetList()) { |
| if (part.FindKey("image")) { |
| auto mojom_image = new_tab_page::mojom::PromoImagePart::New(); |
| auto* image_url = part.FindStringPath("image.image_url"); |
| if (!image_url || image_url->empty()) { |
| continue; |
| } |
| mojom_image->image_url = GURL(*image_url); |
| auto* target = part.FindStringPath("image.target"); |
| if (target && !target->empty()) { |
| mojom_image->target = GURL(*target); |
| } |
| mojom_parts.push_back( |
| new_tab_page::mojom::PromoPart::NewImage(std::move(mojom_image))); |
| } else if (part.FindKey("link")) { |
| auto mojom_link = new_tab_page::mojom::PromoLinkPart::New(); |
| auto* url = part.FindStringPath("link.url"); |
| if (!url || url->empty()) { |
| continue; |
| } |
| mojom_link->url = GURL(*url); |
| auto* text = part.FindStringPath("link.text"); |
| if (!text || text->empty()) { |
| continue; |
| } |
| mojom_link->text = *text; |
| auto* color = part.FindStringPath("link.color"); |
| if (color && !color->empty()) { |
| mojom_link->color = *color; |
| } |
| mojom_parts.push_back( |
| new_tab_page::mojom::PromoPart::NewLink(std::move(mojom_link))); |
| } else if (part.FindKey("text")) { |
| auto mojom_text = new_tab_page::mojom::PromoTextPart::New(); |
| auto* text = part.FindStringPath("text.text"); |
| if (!text || text->empty()) { |
| continue; |
| } |
| mojom_text->text = *text; |
| auto* color = part.FindStringPath("text.color"); |
| if (color && !color->empty()) { |
| mojom_text->color = *color; |
| } |
| mojom_parts.push_back( |
| new_tab_page::mojom::PromoPart::NewText(std::move(mojom_text))); |
| } |
| } |
| promo->middle_slot_parts = std::move(mojom_parts); |
| } |
| } |
| promo->log_url = data.promo_log_url; |
| return promo; |
| } |
| |
| } // namespace |
| |
| // static |
| const char NewTabPageHandler::kModuleDismissedHistogram[] = |
| "NewTabPage.Modules.Dismissed"; |
| const char NewTabPageHandler::kModuleRestoredHistogram[] = |
| "NewTabPage.Modules.Restored"; |
| |
| NewTabPageHandler::NewTabPageHandler( |
| mojo::PendingReceiver<new_tab_page::mojom::PageHandler> |
| pending_page_handler, |
| mojo::PendingRemote<new_tab_page::mojom::Page> pending_page, |
| Profile* profile, |
| InstantService* instant_service, |
| content::WebContents* web_contents, |
| const base::Time& ntp_navigation_start_time) |
| : instant_service_(instant_service), |
| ntp_background_service_( |
| NtpBackgroundServiceFactory::GetForProfile(profile)), |
| logo_service_(LogoServiceFactory::GetForProfile(profile)), |
| one_google_bar_service_( |
| OneGoogleBarServiceFactory::GetForProfile(profile)), |
| profile_(profile), |
| favicon_cache_(FaviconServiceFactory::GetForProfile( |
| profile, |
| ServiceAccessType::EXPLICIT_ACCESS), |
| HistoryServiceFactory::GetForProfile( |
| profile, |
| ServiceAccessType::EXPLICIT_ACCESS)), |
| bitmap_fetcher_service_( |
| BitmapFetcherServiceFactory::GetForBrowserContext(profile)), |
| web_contents_(web_contents), |
| ntp_navigation_start_time_(ntp_navigation_start_time), |
| logger_(profile, GURL(chrome::kChromeUINewTabPageURL)), |
| promo_service_(PromoServiceFactory::GetForProfile(profile)), |
| page_{std::move(pending_page)}, |
| receiver_{this, std::move(pending_page_handler)} { |
| CHECK(instant_service_); |
| CHECK(ntp_background_service_); |
| CHECK(logo_service_); |
| CHECK(one_google_bar_service_); |
| CHECK(promo_service_); |
| CHECK(web_contents_); |
| instant_service_->AddObserver(this); |
| ntp_background_service_->AddObserver(this); |
| instant_service_->UpdateNtpTheme(); |
| OmniboxTabHelper::CreateForWebContents(web_contents); |
| OmniboxTabHelper::FromWebContents(web_contents_)->AddObserver(this); |
| promo_service_observer_.Add(promo_service_); |
| one_google_bar_service_observer_.Add(one_google_bar_service_); |
| logger_.SetModulesVisible( |
| profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesVisible)); |
| } |
| |
| NewTabPageHandler::~NewTabPageHandler() { |
| instant_service_->RemoveObserver(this); |
| ntp_background_service_->RemoveObserver(this); |
| if (auto* helper = OmniboxTabHelper::FromWebContents(web_contents_)) { |
| helper->RemoveObserver(this); |
| } |
| // Clear pending bitmap requests. |
| for (auto bitmap_request_id : bitmap_request_ids_) { |
| bitmap_fetcher_service_->CancelRequest(bitmap_request_id); |
| } |
| if (select_file_dialog_) { |
| select_file_dialog_->ListenerDestroyed(); |
| } |
| } |
| |
| // static |
| void NewTabPageHandler::RegisterProfilePrefs(PrefRegistrySimple* registry) { |
| registry->RegisterBooleanPref(prefs::kNtpModulesVisible, true); |
| } |
| |
| void NewTabPageHandler::AddMostVisitedTile( |
| const GURL& url, |
| const std::string& title, |
| AddMostVisitedTileCallback callback) { |
| bool success = instant_service_->AddCustomLink(url, title); |
| std::move(callback).Run(success); |
| logger_.LogEvent(NTP_CUSTOMIZE_SHORTCUT_ADD, base::TimeDelta() /* unused */); |
| } |
| |
| void NewTabPageHandler::DeleteMostVisitedTile(const GURL& url) { |
| if (instant_service_->IsCustomLinksEnabled()) { |
| instant_service_->DeleteCustomLink(url); |
| logger_.LogEvent(NTP_CUSTOMIZE_SHORTCUT_REMOVE, |
| base::TimeDelta() /* unused */); |
| } else { |
| instant_service_->DeleteMostVisitedItem(url); |
| last_blocklisted_ = url; |
| } |
| } |
| |
| void NewTabPageHandler::RestoreMostVisitedDefaults() { |
| if (instant_service_->IsCustomLinksEnabled()) { |
| instant_service_->ResetCustomLinks(); |
| logger_.LogEvent(NTP_CUSTOMIZE_SHORTCUT_RESTORE_ALL, |
| base::TimeDelta() /* unused */); |
| } else { |
| instant_service_->UndoAllMostVisitedDeletions(); |
| } |
| } |
| |
| void NewTabPageHandler::ReorderMostVisitedTile(const GURL& url, |
| uint8_t new_pos) { |
| instant_service_->ReorderCustomLink(url, new_pos); |
| } |
| |
| void NewTabPageHandler::SetMostVisitedSettings(bool custom_links_enabled, |
| bool visible) { |
| auto pair = instant_service_->GetCurrentShortcutSettings(); |
| // The first of the pair is true if most-visited tiles are being used. |
| bool old_custom_links_enabled = !pair.first; |
| bool old_visible = pair.second; |
| // |ToggleMostVisitedOrCustomLinks()| always notifies observers. Since we only |
| // want to notify once, we need to call |ToggleShortcutsVisibility()| with |
| // false if we are also going to call |ToggleMostVisitedOrCustomLinks()|. |
| bool toggleCustomLinksEnabled = |
| old_custom_links_enabled != custom_links_enabled; |
| if (old_visible != visible) { |
| instant_service_->ToggleShortcutsVisibility( |
| /* do_notify= */ !toggleCustomLinksEnabled); |
| logger_.LogEvent(NTP_CUSTOMIZE_SHORTCUT_TOGGLE_VISIBILITY, |
| base::TimeDelta() /* unused */); |
| } |
| if (toggleCustomLinksEnabled) { |
| instant_service_->ToggleMostVisitedOrCustomLinks(); |
| logger_.LogEvent(NTP_CUSTOMIZE_SHORTCUT_TOGGLE_TYPE, |
| base::TimeDelta() /* unused */); |
| } |
| } |
| |
| void NewTabPageHandler::UndoMostVisitedTileAction() { |
| if (instant_service_->IsCustomLinksEnabled()) { |
| instant_service_->UndoCustomLinkAction(); |
| logger_.LogEvent(NTP_CUSTOMIZE_SHORTCUT_UNDO, |
| base::TimeDelta() /* unused */); |
| } else if (last_blocklisted_.is_valid()) { |
| instant_service_->UndoMostVisitedDeletion(last_blocklisted_); |
| last_blocklisted_ = GURL(); |
| } |
| } |
| |
| void NewTabPageHandler::SetBackgroundImage(const std::string& attribution_1, |
| const std::string& attribution_2, |
| const GURL& attribution_url, |
| const GURL& image_url) { |
| // Populating the |collection_id| turns on refresh daily which overrides the |
| // the selected image. |
| instant_service_->SetCustomBackgroundInfo(image_url, attribution_1, |
| attribution_2, attribution_url, |
| /* collection_id= */ ""); |
| LogEvent(NTP_BACKGROUND_IMAGE_SET); |
| } |
| |
| void NewTabPageHandler::SetDailyRefreshCollectionId( |
| const std::string& collection_id) { |
| // Populating the |collection_id| turns on refresh daily which overrides the |
| // the selected image. |
| instant_service_->SetCustomBackgroundInfo( |
| /* image_url */ GURL(), /* attribution_1= */ "", /* attribution_2= */ "", |
| /* attribution_url= */ GURL(), collection_id); |
| LogEvent(NTP_BACKGROUND_DAILY_REFRESH_ENABLED); |
| } |
| |
| void NewTabPageHandler::SetNoBackgroundImage() { |
| instant_service_->SetCustomBackgroundInfo( |
| /* image_url */ GURL(), /* attribution_1= */ "", /* attribution_2= */ "", |
| /* attribution_url= */ GURL(), /* collection_id= */ ""); |
| LogEvent(NTP_BACKGROUND_IMAGE_RESET); |
| } |
| |
| void NewTabPageHandler::UpdateMostVisitedInfo() { |
| // OnNewTabPageOpened refreshes the most visited entries while |
| // UpdateMostVisitedInfo triggers a call to MostVisitedInfoChanged. |
| instant_service_->OnNewTabPageOpened(); |
| instant_service_->UpdateMostVisitedInfo(); |
| } |
| |
| void NewTabPageHandler::UpdateMostVisitedTile( |
| const GURL& url, |
| const GURL& new_url, |
| const std::string& new_title, |
| UpdateMostVisitedTileCallback callback) { |
| bool success = instant_service_->UpdateCustomLink( |
| url, new_url != url ? new_url : GURL(), new_title); |
| std::move(callback).Run(success); |
| logger_.LogEvent(NTP_CUSTOMIZE_SHORTCUT_UPDATE, |
| base::TimeDelta() /* unused */); |
| } |
| |
| void NewTabPageHandler::GetBackgroundCollections( |
| GetBackgroundCollectionsCallback callback) { |
| if (!ntp_background_service_ || background_collections_callback_) { |
| std::move(callback).Run( |
| std::vector<new_tab_page::mojom::BackgroundCollectionPtr>()); |
| return; |
| } |
| background_collections_request_start_time_ = base::TimeTicks::Now(); |
| background_collections_callback_ = std::move(callback); |
| ntp_background_service_->FetchCollectionInfo(); |
| } |
| |
| void NewTabPageHandler::GetBackgroundImages( |
| const std::string& collection_id, |
| GetBackgroundImagesCallback callback) { |
| if (background_images_callback_) { |
| std::move(background_images_callback_) |
| .Run(std::vector<new_tab_page::mojom::CollectionImagePtr>()); |
| } |
| if (!ntp_background_service_) { |
| std::move(callback).Run( |
| std::vector<new_tab_page::mojom::CollectionImagePtr>()); |
| return; |
| } |
| images_request_collection_id_ = collection_id; |
| background_images_request_start_time_ = base::TimeTicks::Now(); |
| background_images_callback_ = std::move(callback); |
| ntp_background_service_->FetchCollectionImageInfo(collection_id); |
| } |
| |
| void NewTabPageHandler::FocusOmnibox() { |
| search::FocusOmnibox(true, web_contents_); |
| } |
| |
| void NewTabPageHandler::PasteIntoOmnibox(const std::string& text) { |
| search::PasteIntoOmnibox(base::UTF8ToUTF16(text), web_contents_); |
| } |
| |
| void NewTabPageHandler::GetDoodle(GetDoodleCallback callback) { |
| search_provider_logos::LogoCallbacks callbacks; |
| std::string fresh_doodle_param; |
| if (net::GetValueForKeyInQuery(web_contents_->GetLastCommittedURL(), |
| "fresh-doodle", &fresh_doodle_param) && |
| fresh_doodle_param == "1") { |
| // In fresh-doodle mode, wait for the desired doodle to be downloaded. |
| callbacks.on_fresh_encoded_logo_available = |
| base::BindOnce(&NewTabPageHandler::OnLogoAvailable, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)); |
| } else { |
| // In regular mode, return cached doodle as it is available faster. |
| callbacks.on_cached_encoded_logo_available = |
| base::BindOnce(&NewTabPageHandler::OnLogoAvailable, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)); |
| } |
| // This will trigger re-downloading the doodle and caching it. This means that |
| // in regular mode a new doodle will be returned on subsequent NTP loads. |
| logo_service_->GetLogo(std::move(callbacks), /*for_webui_ntp=*/true); |
| } |
| |
| void NewTabPageHandler::ChooseLocalCustomBackground( |
| ChooseLocalCustomBackgroundCallback callback) { |
| select_file_dialog_ = ui::SelectFileDialog::Create( |
| this, std::make_unique<ChromeSelectFilePolicy>(web_contents_)); |
| ui::SelectFileDialog::FileTypeInfo file_types; |
| file_types.allowed_paths = ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH; |
| file_types.extensions.resize(1); |
| file_types.extensions[0].push_back(FILE_PATH_LITERAL("jpg")); |
| file_types.extensions[0].push_back(FILE_PATH_LITERAL("jpeg")); |
| file_types.extensions[0].push_back(FILE_PATH_LITERAL("png")); |
| file_types.extension_description_overrides.push_back( |
| l10n_util::GetStringUTF16(IDS_UPLOAD_IMAGE_FORMAT)); |
| select_file_dialog_->SelectFile( |
| ui::SelectFileDialog::SELECT_OPEN_FILE, base::string16(), |
| profile_->last_selected_directory(), &file_types, 0, |
| base::FilePath::StringType(), web_contents_->GetTopLevelNativeWindow(), |
| nullptr); |
| choose_local_custom_background_callback_ = std::move(callback); |
| } |
| |
| void NewTabPageHandler::GetOneGoogleBarParts( |
| const std::string& query_params, |
| GetOneGoogleBarPartsCallback callback) { |
| if (!one_google_bar_service_) { |
| return; |
| } |
| one_google_bar_parts_callbacks_.push_back(std::move(callback)); |
| bool wait_for_refresh = |
| one_google_bar_service_->SetAdditionalQueryParams(query_params); |
| if (one_google_bar_service_->one_google_bar_data().has_value() && |
| !wait_for_refresh) { |
| OnOneGoogleBarDataUpdated(); |
| } |
| one_google_bar_load_start_time_ = base::TimeTicks::Now(); |
| one_google_bar_service_->Refresh(); |
| } |
| |
| void NewTabPageHandler::GetPromo(GetPromoCallback callback) { |
| // Replace the promo URL with "command:<id>" if such a command ID is set |
| // via the feature params. |
| const std::string command_id = base::GetFieldTrialParamValueByFeature( |
| features::kPromoBrowserCommands, features::kPromoBrowserCommandIdParam); |
| if (!command_id.empty()) { |
| auto promo = new_tab_page::mojom::Promo::New(); |
| std::vector<new_tab_page::mojom::PromoPartPtr> parts; |
| auto image = new_tab_page::mojom::PromoImagePart::New(); |
| // Warning symbol used as the test image. |
| image->image_url = GURL( |
| "data:image/" |
| "svg+xml;base64," |
| "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC" |
| "01IDU4IDU4IiBmaWxsPSIjZmRkNjMzIj48cGF0aCBkPSJNMiA0Mmg0NEwyNCA0IDIgNDJ6" |
| "bTI0LTZoLTR2LTRoNHY0em0wLThoLTR2LThoNHY4eiIvPjwvc3ZnPg=="); |
| image->target = GURL("command:" + command_id); |
| parts.push_back(new_tab_page::mojom::PromoPart::NewImage(std::move(image))); |
| auto link = new_tab_page::mojom::PromoLinkPart::New(); |
| link->url = GURL("command:" + command_id); |
| link->text = "Test command: " + command_id; |
| parts.push_back(new_tab_page::mojom::PromoPart::NewLink(std::move(link))); |
| promo->middle_slot_parts = std::move(parts); |
| std::move(callback).Run(std::move(promo)); |
| return; |
| } |
| |
| promo_callbacks_.push_back(std::move(callback)); |
| if (promo_service_->promo_data().has_value()) { |
| OnPromoDataUpdated(); |
| } |
| promo_load_start_time_ = base::TimeTicks::Now(); |
| promo_service_->Refresh(); |
| } |
| |
| void NewTabPageHandler::OnDismissModule(const std::string& module_id) { |
| const std::string histogram_prefix(kModuleDismissedHistogram); |
| base::UmaHistogramExactLinear(histogram_prefix, 1, 1); |
| base::UmaHistogramExactLinear(histogram_prefix + "." + module_id, 1, 1); |
| } |
| |
| void NewTabPageHandler::OnRestoreModule(const std::string& module_id) { |
| const std::string histogram_prefix(kModuleRestoredHistogram); |
| base::UmaHistogramExactLinear(histogram_prefix, 1, 1); |
| base::UmaHistogramExactLinear(histogram_prefix + "." + module_id, 1, 1); |
| } |
| |
| void NewTabPageHandler::SetModulesVisible(bool visible) { |
| profile_->GetPrefs()->SetBoolean(prefs::kNtpModulesVisible, visible); |
| UpdateModulesVisible(); |
| } |
| |
| void NewTabPageHandler::UpdateModulesVisible() { |
| page_->SetModulesVisible( |
| profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesVisible)); |
| } |
| |
| void NewTabPageHandler::OnPromoDataUpdated() { |
| if (promo_load_start_time_.has_value()) { |
| base::TimeDelta duration = base::TimeTicks::Now() - *promo_load_start_time_; |
| UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency2", duration); |
| if (promo_service_->promo_status() == PromoService::Status::OK_WITH_PROMO) { |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.Promos.RequestLatency2.SuccessWithPromo", duration); |
| } else if (promo_service_->promo_status() == |
| PromoService::Status::OK_BUT_BLOCKED) { |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.Promos.RequestLatency2.SuccessButBlocked", duration); |
| } else if (promo_service_->promo_status() == |
| PromoService::Status::OK_WITHOUT_PROMO) { |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.Promos.RequestLatency2.SuccessWithoutPromo", duration); |
| } else { |
| UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency2.Failure", |
| duration); |
| } |
| promo_load_start_time_ = base::nullopt; |
| } |
| |
| const auto& data = promo_service_->promo_data(); |
| for (auto& callback : promo_callbacks_) { |
| if (data.has_value() && !data->promo_html.empty()) { |
| std::move(callback).Run(MakePromo(data.value())); |
| } else { |
| std::move(callback).Run(nullptr); |
| } |
| } |
| promo_callbacks_.clear(); |
| } |
| |
| void NewTabPageHandler::OnPromoServiceShuttingDown() { |
| promo_service_observer_.RemoveAll(); |
| promo_service_ = nullptr; |
| } |
| |
| void NewTabPageHandler::OnAppRendered(double time) { |
| logger_.LogEvent(NTP_APP_RENDERED, |
| base::Time::FromJsTime(time) - ntp_navigation_start_time_); |
| } |
| |
| void NewTabPageHandler::OnMostVisitedTilesRendered( |
| std::vector<new_tab_page::mojom::MostVisitedTilePtr> tiles, |
| double time) { |
| for (size_t i = 0; i < tiles.size(); i++) { |
| logger_.LogMostVisitedImpression(MakeNTPTileImpression(*tiles[i], i)); |
| } |
| // This call flushes all most visited impression logs to UMA histograms. |
| // Therefore, it must come last. |
| logger_.LogEvent(NTP_ALL_TILES_LOADED, |
| base::Time::FromJsTime(time) - ntp_navigation_start_time_); |
| } |
| |
| void NewTabPageHandler::OnOneGoogleBarRendered(double time) { |
| logger_.LogEvent(NTP_ONE_GOOGLE_BAR_SHOWN, |
| base::Time::FromJsTime(time) - ntp_navigation_start_time_); |
| } |
| |
| void NewTabPageHandler::OnPromoRendered(double time, |
| const base::Optional<GURL>& log_url) { |
| logger_.LogEvent(NTP_MIDDLE_SLOT_PROMO_SHOWN, |
| base::Time::FromJsTime(time) - ntp_navigation_start_time_); |
| if (log_url.has_value() && log_url->is_valid()) { |
| Fetch(*log_url, base::BindOnce([](bool, std::unique_ptr<std::string>) {})); |
| } |
| } |
| |
| void NewTabPageHandler::OnMostVisitedTileNavigation( |
| new_tab_page::mojom::MostVisitedTilePtr tile, |
| uint32_t index, |
| uint8_t mouse_button, |
| bool alt_key, |
| bool ctrl_key, |
| bool meta_key, |
| bool shift_key) { |
| logger_.LogMostVisitedNavigation(MakeNTPTileImpression(*tile, index)); |
| |
| if (!base::FeatureList::IsEnabled( |
| ntp_features::kNtpHandleMostVisitedNavigationExplicitly)) |
| return; |
| |
| WindowOpenDisposition disposition = ui::DispositionFromClick( |
| /*middle_button=*/mouse_button == 1, alt_key, ctrl_key, meta_key, |
| shift_key); |
| // Clicks on the MV tiles should be treated as if the user clicked on a |
| // bookmark. This is consistent with Android's native implementation and |
| // ensures the visit count for the MV entry is updated. |
| // Use a link transition for query tiles, e.g., repeatable queries, so that |
| // their visit count is not updated by this navigation. Otherwise duplicate |
| // query tiles could also be offered as most visited. |
| // |is_query_tile| can be true only when ntp_features::kNtpRepeatableQueries |
| // is enabled. |
| web_contents_->OpenURL(content::OpenURLParams( |
| tile->url, content::Referrer(), disposition, |
| tile->is_query_tile ? ui::PAGE_TRANSITION_LINK |
| : ui::PAGE_TRANSITION_AUTO_BOOKMARK, |
| false)); |
| } |
| |
| void NewTabPageHandler::OnCustomizeDialogAction( |
| new_tab_page::mojom::CustomizeDialogAction action) { |
| NTPLoggingEventType event; |
| switch (action) { |
| case new_tab_page::mojom::CustomizeDialogAction::kCancelClicked: |
| event = NTP_CUSTOMIZATION_MENU_CANCEL; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction::kDoneClicked: |
| event = NTP_CUSTOMIZATION_MENU_DONE; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction::kOpenClicked: |
| event = NTP_CUSTOMIZATION_MENU_OPENED; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction::kBackgroundsBackClicked: |
| event = NTP_BACKGROUND_BACK_CLICK; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction:: |
| kBackgroundsNoBackgroundSelected: |
| event = NTP_BACKGROUND_DEFAULT_SELECTED; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction:: |
| kBackgroundsCollectionOpened: |
| event = NTP_BACKGROUND_OPEN_COLLECTION; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction:: |
| kBackgroundsRefreshToggleClicked: |
| event = NTP_BACKGROUND_REFRESH_TOGGLE_CLICKED; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction::kBackgroundsImageSelected: |
| event = NTP_BACKGROUND_SELECT_IMAGE; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction:: |
| kBackgroundsUploadFromDeviceClicked: |
| event = NTP_BACKGROUND_UPLOAD_FROM_DEVICE; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction:: |
| kShortcutsCustomLinksClicked: |
| event = NTP_CUSTOMIZE_SHORTCUT_CUSTOM_LINKS_CLICKED; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction:: |
| kShortcutsMostVisitedClicked: |
| event = NTP_CUSTOMIZE_SHORTCUT_MOST_VISITED_CLICKED; |
| break; |
| case new_tab_page::mojom::CustomizeDialogAction:: |
| kShortcutsVisibilityToggleClicked: |
| event = NTP_CUSTOMIZE_SHORTCUT_VISIBILITY_TOGGLE_CLICKED; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| LogEvent(event); |
| } |
| |
| void NewTabPageHandler::OnDoodleImageClicked( |
| new_tab_page::mojom::DoodleImageType type, |
| const base::Optional<::GURL>& log_url) { |
| NTPLoggingEventType event; |
| switch (type) { |
| case new_tab_page::mojom::DoodleImageType::kAnimation: |
| event = NTP_ANIMATED_LOGO_CLICKED; |
| break; |
| case new_tab_page::mojom::DoodleImageType::kCta: |
| event = NTP_CTA_LOGO_CLICKED; |
| break; |
| case new_tab_page::mojom::DoodleImageType::kStatic: |
| event = NTP_STATIC_LOGO_CLICKED; |
| break; |
| default: |
| NOTREACHED(); |
| return; |
| } |
| LogEvent(event); |
| |
| if (type == new_tab_page::mojom::DoodleImageType::kCta && |
| log_url.has_value()) { |
| // We just ping the server to indicate a CTA image has been clicked. |
| Fetch(*log_url, base::BindOnce([](bool, std::unique_ptr<std::string>) {})); |
| } |
| } |
| |
| void NewTabPageHandler::OnDoodleImageRendered( |
| new_tab_page::mojom::DoodleImageType type, |
| double time, |
| const GURL& log_url, |
| OnDoodleImageRenderedCallback callback) { |
| if (type == new_tab_page::mojom::DoodleImageType::kCta || |
| type == new_tab_page::mojom::DoodleImageType::kStatic) { |
| logger_.LogEvent(type == new_tab_page::mojom::DoodleImageType::kCta |
| ? NTP_CTA_LOGO_SHOWN_FROM_CACHE |
| : NTP_STATIC_LOGO_SHOWN_FROM_CACHE, |
| base::Time::FromJsTime(time) - ntp_navigation_start_time_); |
| } |
| Fetch(log_url, |
| base::BindOnce(&NewTabPageHandler::OnLogFetchResult, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void NewTabPageHandler::OnDoodleShared( |
| new_tab_page::mojom::DoodleShareChannel channel, |
| const std::string& doodle_id, |
| const base::Optional<std::string>& share_id) { |
| int channel_id; |
| switch (channel) { |
| case new_tab_page::mojom::DoodleShareChannel::kFacebook: |
| channel_id = 2; |
| break; |
| case new_tab_page::mojom::DoodleShareChannel::kTwitter: |
| channel_id = 3; |
| break; |
| case new_tab_page::mojom::DoodleShareChannel::kEmail: |
| channel_id = 5; |
| break; |
| case new_tab_page::mojom::DoodleShareChannel::kLinkCopy: |
| channel_id = 6; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| std::string query = |
| base::StringPrintf("gen_204?atype=i&ct=doodle&ntp=2&cad=sh,%d,ct:%s", |
| channel_id, doodle_id.c_str()); |
| if (share_id.has_value()) { |
| query += "&ei=" + *share_id; |
| } |
| auto url = GURL(TemplateURLServiceFactory::GetForProfile(profile_) |
| ->search_terms_data() |
| .GoogleBaseURLValue()) |
| .Resolve(query); |
| // We just ping the server to indicate a doodle has been shared. |
| Fetch(url, base::BindOnce([](bool s, std::unique_ptr<std::string>) {})); |
| } |
| |
| void NewTabPageHandler::OnPromoLinkClicked() { |
| LogEvent(NTP_MIDDLE_SLOT_PROMO_LINK_CLICKED); |
| } |
| |
| void NewTabPageHandler::OnVoiceSearchAction( |
| new_tab_page::mojom::VoiceSearchAction action) { |
| NTPLoggingEventType event; |
| switch (action) { |
| case new_tab_page::mojom::VoiceSearchAction::kActivateSearchBox: |
| event = NTP_VOICE_ACTION_ACTIVATE_SEARCH_BOX; |
| break; |
| case new_tab_page::mojom::VoiceSearchAction::kActivateKeyboard: |
| event = NTP_VOICE_ACTION_ACTIVATE_KEYBOARD; |
| break; |
| case new_tab_page::mojom::VoiceSearchAction::kCloseOverlay: |
| event = NTP_VOICE_ACTION_CLOSE_OVERLAY; |
| break; |
| case new_tab_page::mojom::VoiceSearchAction::kQuerySubmitted: |
| event = NTP_VOICE_ACTION_QUERY_SUBMITTED; |
| break; |
| case new_tab_page::mojom::VoiceSearchAction::kSupportLinkClicked: |
| event = NTP_VOICE_ACTION_SUPPORT_LINK_CLICKED; |
| break; |
| case new_tab_page::mojom::VoiceSearchAction::kTryAgainLink: |
| event = NTP_VOICE_ACTION_TRY_AGAIN_LINK; |
| break; |
| case new_tab_page::mojom::VoiceSearchAction::kTryAgainMicButton: |
| event = NTP_VOICE_ACTION_TRY_AGAIN_MIC_BUTTON; |
| break; |
| } |
| LogEvent(event); |
| } |
| |
| void NewTabPageHandler::OnVoiceSearchError( |
| new_tab_page::mojom::VoiceSearchError error) { |
| NTPLoggingEventType event; |
| switch (error) { |
| case new_tab_page::mojom::VoiceSearchError::kAborted: |
| event = NTP_VOICE_ERROR_ABORTED; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kNoSpeech: |
| event = NTP_VOICE_ERROR_NO_SPEECH; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kAudioCapture: |
| event = NTP_VOICE_ERROR_AUDIO_CAPTURE; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kNetwork: |
| event = NTP_VOICE_ERROR_NETWORK; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kNotAllowed: |
| event = NTP_VOICE_ERROR_NOT_ALLOWED; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kLanguageNotSupported: |
| event = NTP_VOICE_ERROR_LANGUAGE_NOT_SUPPORTED; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kNoMatch: |
| event = NTP_VOICE_ERROR_NO_MATCH; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kServiceNotAllowed: |
| event = NTP_VOICE_ERROR_SERVICE_NOT_ALLOWED; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kBadGrammar: |
| event = NTP_VOICE_ERROR_BAD_GRAMMAR; |
| break; |
| case new_tab_page::mojom::VoiceSearchError::kOther: |
| event = NTP_VOICE_ERROR_OTHER; |
| break; |
| } |
| LogEvent(event); |
| } |
| |
| void NewTabPageHandler::OnModuleImpression(const std::string& module_id, |
| double time) { |
| logger_.LogModuleImpression( |
| module_id, base::Time::FromJsTime(time) - ntp_navigation_start_time_); |
| } |
| |
| void NewTabPageHandler::OnModuleLoaded(const std::string& module_id, |
| double time) { |
| logger_.LogModuleLoaded( |
| module_id, base::Time::FromJsTime(time) - ntp_navigation_start_time_); |
| } |
| |
| void NewTabPageHandler::OnModuleUsage(const std::string& module_id) { |
| logger_.LogModuleUsage(module_id); |
| } |
| |
| void NewTabPageHandler::OnModulesRendered(double time) { |
| logger_.LogEvent(NTP_MODULES_SHOWN, |
| base::Time::FromJsTime(time) - ntp_navigation_start_time_); |
| } |
| |
| void NewTabPageHandler::QueryAutocomplete(const base::string16& input, |
| bool prevent_inline_autocomplete) { |
| if (!autocomplete_controller_) { |
| autocomplete_controller_ = std::make_unique<AutocompleteController>( |
| std::make_unique<ChromeAutocompleteProviderClient>(profile_), |
| AutocompleteClassifier::DefaultOmniboxProviders()); |
| autocomplete_controller_->AddObserver(this); |
| |
| OmniboxControllerEmitter* emitter = |
| OmniboxControllerEmitter::GetForBrowserContext(profile_); |
| if (emitter) |
| autocomplete_controller_->AddObserver(emitter); |
| } |
| |
| if (time_of_first_autocomplete_query_.is_null() && !input.empty()) |
| time_of_first_autocomplete_query_ = base::TimeTicks::Now(); |
| |
| AutocompleteInput autocomplete_input( |
| input, metrics::OmniboxEventProto::NTP_REALBOX, |
| ChromeAutocompleteSchemeClassifier(profile_)); |
| // TODO(tommycli): We use the input being empty as a signal we are requesting |
| // on-focus suggestions. It would be nice if we had a more explicit signal. |
| autocomplete_input.set_focus_type(input.empty() ? OmniboxFocusType::ON_FOCUS |
| : OmniboxFocusType::DEFAULT); |
| autocomplete_input.set_prevent_inline_autocomplete( |
| prevent_inline_autocomplete); |
| |
| // We do not want keyword matches for the NTP realbox, which has no UI |
| // facilities to support them. |
| autocomplete_input.set_prefer_keyword(false); |
| autocomplete_input.set_allow_exact_keyword_match(false); |
| |
| autocomplete_controller_->Start(autocomplete_input); |
| } |
| |
| void NewTabPageHandler::StopAutocomplete(bool clear_result) { |
| if (!autocomplete_controller_) |
| return; |
| |
| autocomplete_controller_->Stop(clear_result); |
| |
| if (clear_result) |
| time_of_first_autocomplete_query_ = base::TimeTicks(); |
| } |
| |
| void NewTabPageHandler::OpenAutocompleteMatch( |
| uint8_t line, |
| const GURL& url, |
| bool are_matches_showing, |
| base::TimeDelta time_elapsed_since_last_focus, |
| uint8_t mouse_button, |
| bool alt_key, |
| bool ctrl_key, |
| bool meta_key, |
| bool shift_key) { |
| if (autocomplete_controller_->result().size() <= line) { |
| return; |
| } |
| |
| AutocompleteMatch match(autocomplete_controller_->result().match_at(line)); |
| if (match.destination_url != url) { |
| // TODO(https://crbug.com/1020025): this could be malice or staleness. |
| // Either way: don't navigate. |
| return; |
| } |
| |
| // TODO(crbug.com/1041129): The following logic for recording Omnibox metrics |
| // is largely copied from SearchTabHelper::OpenAutocompleteMatch(). Make sure |
| // any changes here is reflected there until one code path is obsolete. |
| |
| const auto now = base::TimeTicks::Now(); |
| base::TimeDelta elapsed_time_since_first_autocomplete_query = |
| now - time_of_first_autocomplete_query_; |
| autocomplete_controller_->UpdateMatchDestinationURLWithQueryFormulationTime( |
| elapsed_time_since_first_autocomplete_query, &match); |
| |
| LOCAL_HISTOGRAM_BOOLEAN("Omnibox.EventCount", true); |
| |
| UMA_HISTOGRAM_MEDIUM_TIMES("Omnibox.FocusToOpenTimeAnyPopupState3", |
| time_elapsed_since_last_focus); |
| |
| if (ui::PageTransitionTypeIncludingQualifiersIs(match.transition, |
| ui::PAGE_TRANSITION_TYPED)) { |
| navigation_metrics::RecordOmniboxURLNavigation(match.destination_url); |
| } |
| // The following histogram should be recorded for both TYPED and pasted |
| // URLs, but should still exclude reloads. |
| if (ui::PageTransitionTypeIncludingQualifiersIs(match.transition, |
| ui::PAGE_TRANSITION_TYPED) || |
| ui::PageTransitionTypeIncludingQualifiersIs(match.transition, |
| ui::PAGE_TRANSITION_LINK)) { |
| net::cookie_util::RecordCookiePortOmniboxHistograms(match.destination_url); |
| } |
| |
| SuggestionAnswer::LogAnswerUsed(match.answer); |
| |
| TemplateURLService* template_url_service = |
| TemplateURLServiceFactory::GetForProfile(profile_); |
| if (template_url_service && |
| template_url_service->IsSearchResultsPageFromDefaultSearchProvider( |
| match.destination_url)) { |
| // Note: will always be false for the realbox. |
| UMA_HISTOGRAM_BOOLEAN("Omnibox.Search.OffTheRecord", |
| profile_->IsOffTheRecord()); |
| base::RecordAction( |
| base::UserMetricsAction("OmniboxDestinationURLIsSearchOnDSP")); |
| } |
| |
| AutocompleteMatch::LogSearchEngineUsed(match, template_url_service); |
| |
| auto* bookmark_model = BookmarkModelFactory::GetForBrowserContext(profile_); |
| if (bookmark_model->IsBookmarked(match.destination_url)) { |
| RecordBookmarkLaunch(BOOKMARK_LAUNCH_LOCATION_OMNIBOX, |
| ProfileMetrics::GetBrowserProfileType(profile_)); |
| } |
| |
| const AutocompleteInput& input = autocomplete_controller_->input(); |
| WindowOpenDisposition disposition = ui::DispositionFromClick( |
| /*middle_button=*/mouse_button == 1, alt_key, ctrl_key, meta_key, |
| shift_key); |
| |
| base::TimeDelta default_time_delta = base::TimeDelta::FromMilliseconds(-1); |
| |
| if (time_of_first_autocomplete_query_.is_null()) |
| elapsed_time_since_first_autocomplete_query = default_time_delta; |
| |
| base::TimeDelta elapsed_time_since_last_change_to_default_match = |
| !autocomplete_controller_->last_time_default_match_changed().is_null() |
| ? now - autocomplete_controller_->last_time_default_match_changed() |
| : default_time_delta; |
| |
| OmniboxLog log( |
| /*text=*/input.focus_type() != OmniboxFocusType::DEFAULT |
| ? base::string16() |
| : input.text(), |
| /*just_deleted_text=*/input.prevent_inline_autocomplete(), |
| /*input_type=*/input.type(), |
| /*in_keyword_mode=*/false, |
| /*entry_method=*/metrics::OmniboxEventProto::INVALID, |
| /*is_popup_open=*/are_matches_showing, |
| /*selected_index=*/line, |
| /*disposition=*/disposition, |
| /*is_paste_and_go=*/false, |
| /*tab_id=*/sessions::SessionTabHelper::IdForTab(web_contents_), |
| /*current_page_classification=*/metrics::OmniboxEventProto::NTP_REALBOX, |
| /*elapsed_time_since_user_first_modified_omnibox=*/ |
| elapsed_time_since_first_autocomplete_query, |
| /*completed_length=*/match.allowed_to_be_default_match |
| ? match.inline_autocompletion.length() |
| : base::string16::npos, |
| /*elapsed_time_since_last_change_to_default_match=*/ |
| elapsed_time_since_last_change_to_default_match, |
| /*result=*/autocomplete_controller_->result()); |
| autocomplete_controller_->AddProviderAndTriggeringLogs(&log); |
| |
| OmniboxEventGlobalTracker::GetInstance()->OnURLOpened(&log); |
| |
| predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_) |
| ->OnOmniboxOpenedUrl(log); |
| |
| web_contents_->OpenURL( |
| content::OpenURLParams(match.destination_url, content::Referrer(), |
| disposition, match.transition, false)); |
| } |
| |
| void NewTabPageHandler::DeleteAutocompleteMatch(uint8_t line) { |
| if (autocomplete_controller_->result().size() <= line || |
| !autocomplete_controller_->result().match_at(line).SupportsDeletion()) { |
| return; |
| } |
| |
| const auto& match = autocomplete_controller_->result().match_at(line); |
| if (match.SupportsDeletion()) { |
| autocomplete_controller_->Stop(false); |
| autocomplete_controller_->DeleteMatch(match); |
| } |
| } |
| |
| void NewTabPageHandler::ToggleSuggestionGroupIdVisibility( |
| int32_t suggestion_group_id) { |
| if (!autocomplete_controller_) |
| return; |
| |
| omnibox::SuggestionGroupVisibility new_value = |
| autocomplete_controller_->result().IsSuggestionGroupIdHidden( |
| profile_->GetPrefs(), suggestion_group_id) |
| ? omnibox::SuggestionGroupVisibility::SHOWN |
| : omnibox::SuggestionGroupVisibility::HIDDEN; |
| omnibox::SetSuggestionGroupVisibility(profile_->GetPrefs(), |
| suggestion_group_id, new_value); |
| } |
| |
| void NewTabPageHandler::LogCharTypedToRepaintLatency(base::TimeDelta latency) { |
| UMA_HISTOGRAM_TIMES("NewTabPage.Realbox.CharTypedToRepaintLatency.ToPaint", |
| latency); |
| } |
| |
| void NewTabPageHandler::NtpThemeChanged(const NtpTheme& ntp_theme) { |
| page_->SetTheme(MakeTheme(ntp_theme)); |
| } |
| |
| void NewTabPageHandler::MostVisitedInfoChanged( |
| const InstantMostVisitedInfo& info) { |
| auto* template_url_service = |
| TemplateURLServiceFactory::GetForProfile(profile_); |
| std::vector<new_tab_page::mojom::MostVisitedTilePtr> list; |
| auto result = new_tab_page::mojom::MostVisitedInfo::New(); |
| for (auto& tile : info.items) { |
| auto value = new_tab_page::mojom::MostVisitedTile::New(); |
| if (tile.title.empty()) { |
| value->title = tile.url.spec(); |
| value->title_direction = base::i18n::LEFT_TO_RIGHT; |
| } else { |
| value->title = base::UTF16ToUTF8(tile.title); |
| value->title_direction = |
| base::i18n::GetFirstStrongCharacterDirection(tile.title); |
| } |
| value->url = tile.url; |
| value->source = static_cast<int32_t>(tile.source); |
| value->title_source = static_cast<int32_t>(tile.title_source); |
| value->data_generation_time = tile.data_generation_time; |
| value->is_query_tile = |
| base::FeatureList::IsEnabled(ntp_features::kNtpRepeatableQueries) && |
| template_url_service && |
| template_url_service->IsSearchResultsPageFromDefaultSearchProvider( |
| tile.url); |
| list.push_back(std::move(value)); |
| } |
| result->custom_links_enabled = !info.use_most_visited; |
| result->tiles = std::move(list); |
| result->visible = info.is_visible; |
| page_->SetMostVisitedInfo(std::move(result)); |
| } |
| |
| void NewTabPageHandler::OnCollectionInfoAvailable() { |
| if (!background_collections_callback_) { |
| return; |
| } |
| |
| base::TimeDelta duration = |
| base::TimeTicks::Now() - background_collections_request_start_time_; |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.BackgroundService.Collections.RequestLatency", duration); |
| // Any response where no collections are returned is considered a failure. |
| if (ntp_background_service_->collection_info().empty()) { |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.BackgroundService.Collections.RequestLatency.Failure", |
| duration); |
| } else { |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.BackgroundService.Collections.RequestLatency.Success", |
| duration); |
| } |
| |
| std::vector<new_tab_page::mojom::BackgroundCollectionPtr> collections; |
| for (const auto& info : ntp_background_service_->collection_info()) { |
| auto collection = new_tab_page::mojom::BackgroundCollection::New(); |
| collection->id = info.collection_id; |
| collection->label = info.collection_name; |
| collection->preview_image_url = GURL(info.preview_image_url); |
| collections.push_back(std::move(collection)); |
| } |
| std::move(background_collections_callback_).Run(std::move(collections)); |
| } |
| |
| void NewTabPageHandler::OnCollectionImagesAvailable() { |
| if (!background_images_callback_) { |
| return; |
| } |
| |
| base::TimeDelta duration = |
| base::TimeTicks::Now() - background_images_request_start_time_; |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.BackgroundService.Images.RequestLatency", duration); |
| // Any response where no images are returned is considered a failure. |
| if (ntp_background_service_->collection_images().empty()) { |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.BackgroundService.Images.RequestLatency.Failure", duration); |
| } else { |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NewTabPage.BackgroundService.Images.RequestLatency.Success", duration); |
| } |
| |
| std::vector<new_tab_page::mojom::CollectionImagePtr> images; |
| if (ntp_background_service_->collection_images().empty()) { |
| std::move(background_images_callback_).Run(std::move(images)); |
| return; |
| } |
| auto collection_id = |
| ntp_background_service_->collection_images()[0].collection_id; |
| for (const auto& info : ntp_background_service_->collection_images()) { |
| DCHECK(info.collection_id == collection_id); |
| auto image = new_tab_page::mojom::CollectionImage::New(); |
| image->attribution_1 = !info.attribution.empty() ? info.attribution[0] : ""; |
| image->attribution_2 = |
| info.attribution.size() > 1 ? info.attribution[1] : ""; |
| image->attribution_url = info.attribution_action_url; |
| image->image_url = info.image_url; |
| image->preview_image_url = info.thumbnail_image_url; |
| images.push_back(std::move(image)); |
| } |
| std::move(background_images_callback_).Run(std::move(images)); |
| } |
| |
| void NewTabPageHandler::OnNextCollectionImageAvailable() {} |
| |
| void NewTabPageHandler::OnNtpBackgroundServiceShuttingDown() { |
| ntp_background_service_->RemoveObserver(this); |
| ntp_background_service_ = nullptr; |
| } |
| |
| void NewTabPageHandler::OnOmniboxInputStateChanged() { |
| // This handler was added for the local NTP to show the fakebox when pressing |
| // escape while the omnibox has focus. The WebUI NTP only shows the fakebox |
| // when blurring the omnibox. Thus, we do nothing here. |
| } |
| |
| void NewTabPageHandler::OnOmniboxFocusChanged(OmniboxFocusState state, |
| OmniboxFocusChangeReason reason) { |
| page_->SetFakeboxFocused(state == OMNIBOX_FOCUS_INVISIBLE); |
| // Don't make fakebox re-appear for a short moment before navigating away. |
| if (web_contents_->GetController().GetPendingEntry() == nullptr) { |
| page_->SetFakeboxVisible(reason != OMNIBOX_FOCUS_CHANGE_TYPING); |
| } |
| } |
| |
| void NewTabPageHandler::OnOneGoogleBarDataUpdated() { |
| base::Optional<OneGoogleBarData> data = |
| one_google_bar_service_->one_google_bar_data(); |
| |
| if (one_google_bar_load_start_time_.has_value()) { |
| NTPUserDataLogger::LogOneGoogleBarFetchDuration( |
| /*success=*/data.has_value(), |
| /*duration=*/base::TimeTicks::Now() - *one_google_bar_load_start_time_); |
| one_google_bar_load_start_time_ = base::nullopt; |
| } |
| |
| for (auto& callback : one_google_bar_parts_callbacks_) { |
| if (data.has_value()) { |
| auto parts = new_tab_page::mojom::OneGoogleBarParts::New(); |
| parts->bar_html = data->bar_html; |
| parts->in_head_script = data->in_head_script; |
| parts->in_head_style = data->in_head_style; |
| parts->after_bar_script = data->after_bar_script; |
| parts->end_of_body_html = data->end_of_body_html; |
| parts->end_of_body_script = data->end_of_body_script; |
| std::move(callback).Run(std::move(parts)); |
| } else { |
| std::move(callback).Run(nullptr); |
| } |
| } |
| one_google_bar_parts_callbacks_.clear(); |
| } |
| |
| void NewTabPageHandler::OnOneGoogleBarServiceShuttingDown() { |
| one_google_bar_service_observer_.RemoveAll(); |
| one_google_bar_service_ = nullptr; |
| } |
| |
| void NewTabPageHandler::FileSelected(const base::FilePath& path, |
| int index, |
| void* params) { |
| if (instant_service_) { |
| profile_->set_last_selected_directory(path.DirName()); |
| instant_service_->SelectLocalBackgroundImage(path); |
| } |
| |
| select_file_dialog_ = nullptr; |
| // File selection can happen at any time after NTP load, and is not logged |
| // with the event. |
| LogEvent(NTP_CUSTOMIZE_LOCAL_IMAGE_DONE); |
| LogEvent(NTP_BACKGROUND_UPLOAD_DONE); |
| |
| std::move(choose_local_custom_background_callback_).Run(true); |
| } |
| |
| void NewTabPageHandler::FileSelectionCanceled(void* params) { |
| select_file_dialog_ = nullptr; |
| // File selection can happen at any time after NTP load, and is not logged |
| // with the event. |
| LogEvent(NTP_CUSTOMIZE_LOCAL_IMAGE_CANCEL); |
| LogEvent(NTP_BACKGROUND_UPLOAD_CANCEL); |
| std::move(choose_local_custom_background_callback_).Run(false); |
| } |
| |
| void NewTabPageHandler::OnResultChanged(AutocompleteController* controller, |
| bool default_match_changed) { |
| DCHECK(controller == autocomplete_controller_.get()); |
| |
| page_->AutocompleteResultChanged(omnibox::CreateAutocompleteResult( |
| autocomplete_controller_->input().text(), |
| autocomplete_controller_->result(), |
| BookmarkModelFactory::GetForBrowserContext(profile_), |
| profile_->GetPrefs())); |
| |
| // Clear pending bitmap requests before requesting new ones. |
| for (auto bitmap_request_id : bitmap_request_ids_) { |
| bitmap_fetcher_service_->CancelRequest(bitmap_request_id); |
| } |
| bitmap_request_ids_.clear(); |
| |
| int match_index = -1; |
| for (const auto& match : autocomplete_controller_->result()) { |
| match_index++; |
| |
| // Request bitmaps for matche images. |
| if (!match.image_url.is_empty()) { |
| bitmap_request_ids_.push_back(bitmap_fetcher_service_->RequestImage( |
| match.image_url, |
| base::BindOnce(&NewTabPageHandler::OnRealboxBitmapFetched, |
| weak_ptr_factory_.GetWeakPtr(), match_index, |
| match.image_url))); |
| } |
| |
| // Request favicons for navigational matches. |
| // TODO(crbug.com/1075848): Investigate using chrome://favicon2. |
| if (!AutocompleteMatch::IsSearchType(match.type) && |
| match.type != AutocompleteMatchType::DOCUMENT_SUGGESTION) { |
| gfx::Image favicon = favicon_cache_.GetLargestFaviconForPageUrl( |
| match.destination_url, |
| base::BindOnce(&NewTabPageHandler::OnRealboxFaviconFetched, |
| weak_ptr_factory_.GetWeakPtr(), match_index, |
| match.destination_url)); |
| if (!favicon.IsEmpty()) { |
| OnRealboxFaviconFetched(match_index, match.destination_url, favicon); |
| } |
| } |
| } |
| } |
| |
| void NewTabPageHandler::OnLogoAvailable( |
| GetDoodleCallback callback, |
| search_provider_logos::LogoCallbackReason type, |
| const base::Optional<search_provider_logos::EncodedLogo>& logo) { |
| if (!logo) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| auto doodle = new_tab_page::mojom::Doodle::New(); |
| if (logo->metadata.type == search_provider_logos::LogoType::SIMPLE || |
| logo->metadata.type == search_provider_logos::LogoType::ANIMATED) { |
| if (!logo->encoded_image) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| auto image_doodle = new_tab_page::mojom::AllModeImageDoodle::New(); |
| image_doodle->light = MakeImageDoodle( |
| logo->metadata.type, logo->encoded_image->data(), |
| logo->metadata.mime_type, logo->metadata.animated_url, |
| logo->metadata.width_px, logo->metadata.height_px, "#ffffff", |
| logo->metadata.share_button_x, logo->metadata.share_button_y, |
| logo->metadata.share_button_icon, logo->metadata.share_button_bg, |
| logo->metadata.log_url, logo->metadata.cta_log_url); |
| if (logo->dark_encoded_image) { |
| image_doodle->dark = MakeImageDoodle( |
| logo->metadata.type, logo->dark_encoded_image->data(), |
| logo->metadata.dark_mime_type, logo->metadata.dark_animated_url, |
| logo->metadata.dark_width_px, logo->metadata.dark_height_px, |
| logo->metadata.dark_background_color, |
| logo->metadata.dark_share_button_x, |
| logo->metadata.dark_share_button_y, |
| logo->metadata.dark_share_button_icon, |
| logo->metadata.dark_share_button_bg, logo->metadata.dark_log_url, |
| logo->metadata.dark_cta_log_url); |
| } |
| image_doodle->on_click_url = logo->metadata.on_click_url; |
| image_doodle->share_url = logo->metadata.short_link; |
| doodle->image = std::move(image_doodle); |
| } else if (logo->metadata.type == |
| search_provider_logos::LogoType::INTERACTIVE) { |
| auto interactive_doodle = new_tab_page::mojom::InteractiveDoodle::New(); |
| interactive_doodle->url = logo->metadata.full_page_url; |
| interactive_doodle->width = logo->metadata.iframe_width_px; |
| interactive_doodle->height = logo->metadata.iframe_height_px; |
| doodle->interactive = std::move(interactive_doodle); |
| } else { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| doodle->description = logo->metadata.alt_text; |
| std::move(callback).Run(std::move(doodle)); |
| } |
| |
| void NewTabPageHandler::OnRealboxBitmapFetched(int match_index, |
| const GURL& image_url, |
| const SkBitmap& bitmap) { |
| auto data = gfx::Image::CreateFrom1xBitmap(bitmap).As1xPNGBytes(); |
| std::string data_url = |
| webui::GetPngDataUrl(data->front_as<unsigned char>(), data->size()); |
| |
| page_->AutocompleteMatchImageAvailable(match_index, image_url, data_url); |
| } |
| |
| void NewTabPageHandler::OnRealboxFaviconFetched(int match_index, |
| const GURL& page_url, |
| const gfx::Image& favicon) { |
| DCHECK(!favicon.IsEmpty()); |
| auto data = favicon.As1xPNGBytes(); |
| std::string data_url = |
| webui::GetPngDataUrl(data->front_as<unsigned char>(), data->size()); |
| |
| page_->AutocompleteMatchImageAvailable(match_index, page_url, data_url); |
| } |
| |
| void NewTabPageHandler::LogEvent(NTPLoggingEventType event) { |
| logger_.LogEvent(event, base::TimeDelta() /* unused */); |
| } |
| |
| void NewTabPageHandler::Fetch(const GURL& url, |
| OnFetchResultCallback on_result) { |
| auto traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("new_tab_page_handler", R"( |
| semantics { |
| sender: "New Tab Page" |
| description: "Logs impression and interaction with doodle or promo." |
| trigger: |
| "Showing or clicking on the doodle or promo on the New Tab Page. " |
| "Desktop only." |
| data: |
| "String identifiying todays doodle or promo and token identifying " |
| "a single interaction session. Data does not contain PII." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "Users can control this feature via selecting a non-Google default " |
| "search engine in Chrome settings under 'Search Engine'." |
| chrome_policy { |
| DefaultSearchProviderEnabled { |
| policy_options {mode: MANDATORY} |
| DefaultSearchProviderEnabled: false |
| } |
| } |
| })"); |
| auto url_loader_factory = |
| content::BrowserContext::GetDefaultStoragePartition(profile_) |
| ->GetURLLoaderFactoryForBrowserProcess(); |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->url = url; |
| auto loader = |
| network::SimpleURLLoader::Create(std::move(request), traffic_annotation); |
| loader->DownloadToString(url_loader_factory.get(), |
| base::BindOnce(&NewTabPageHandler::OnFetchResult, |
| weak_ptr_factory_.GetWeakPtr(), |
| loader.get(), std::move(on_result)), |
| kMaxDownloadBytes); |
| loader_map_.insert({loader.get(), std::move(loader)}); |
| } |
| |
| void NewTabPageHandler::OnFetchResult(const network::SimpleURLLoader* loader, |
| OnFetchResultCallback on_result, |
| std::unique_ptr<std::string> body) { |
| bool success = loader->NetError() == net::OK && loader->ResponseInfo() && |
| loader->ResponseInfo()->headers && |
| loader->ResponseInfo()->headers->response_code() >= 200 && |
| loader->ResponseInfo()->headers->response_code() <= 299 && |
| body; |
| std::move(on_result).Run(success, std::move(body)); |
| loader_map_.erase(loader); |
| } |
| |
| void NewTabPageHandler::OnLogFetchResult(OnDoodleImageRenderedCallback callback, |
| bool success, |
| std::unique_ptr<std::string> body) { |
| if (!success || body->size() < 4 || body->substr(0, 4) != ")]}'") { |
| std::move(callback).Run("", base::nullopt, ""); |
| return; |
| } |
| auto value = base::JSONReader::Read(body->substr(4)); |
| if (!value.has_value()) { |
| std::move(callback).Run("", base::nullopt, ""); |
| return; |
| } |
| |
| auto* target_url_params_value = value->FindPath("ddllog.target_url_params"); |
| auto target_url_params = |
| target_url_params_value && target_url_params_value->is_string() |
| ? target_url_params_value->GetString() |
| : ""; |
| auto* interaction_log_url_value = |
| value->FindPath("ddllog.interaction_log_url"); |
| auto interaction_log_url = |
| interaction_log_url_value && interaction_log_url_value->is_string() |
| ? base::Optional<GURL>( |
| GURL(TemplateURLServiceFactory::GetForProfile(profile_) |
| ->search_terms_data() |
| .GoogleBaseURLValue()) |
| .Resolve(interaction_log_url_value->GetString())) |
| : base::nullopt; |
| auto* encoded_ei_value = value->FindPath("ddllog.encoded_ei"); |
| auto encoded_ei = encoded_ei_value && encoded_ei_value->is_string() |
| ? encoded_ei_value->GetString() |
| : ""; |
| std::move(callback).Run(target_url_params, interaction_log_url, encoded_ei); |
| } |