diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-02-13 10:55:42 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-03-05 13:33:38 +0000 |
commit | 248b70b82a40964d5594eb04feca0fa36716185d (patch) | |
tree | 44e31d9dd0ac2cb79f48633eefbc5496e013c347 /chromium/chrome/renderer/searchbox | |
parent | cabfcdd1db482729ded525feae56911a99792773 (diff) | |
download | qtwebengine-chromium-248b70b82a40964d5594eb04feca0fa36716185d.tar.gz |
BASELINE: Update Chromium to 79.0.3945.147
And new simplified snapshot filter
Change-Id: I7c692bedd5b3833f05565bd6f6939115350b233a
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/chrome/renderer/searchbox')
-rw-r--r-- | chromium/chrome/renderer/searchbox/DEPS | 4 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/OWNERS | 4 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/search_bouncer.cc | 56 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/search_bouncer.h | 48 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/search_bouncer_unittest.cc | 29 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/searchbox.cc | 596 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/searchbox.h | 274 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/searchbox_extension.cc | 1469 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/searchbox_extension.h | 57 | ||||
-rw-r--r-- | chromium/chrome/renderer/searchbox/searchbox_unittest.cc | 271 |
10 files changed, 2808 insertions, 0 deletions
diff --git a/chromium/chrome/renderer/searchbox/DEPS b/chromium/chrome/renderer/searchbox/DEPS new file mode 100644 index 00000000000..03093492dc7 --- /dev/null +++ b/chromium/chrome/renderer/searchbox/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+components/favicon_base", + "+components/ntp_tiles", +] diff --git a/chromium/chrome/renderer/searchbox/OWNERS b/chromium/chrome/renderer/searchbox/OWNERS new file mode 100644 index 00000000000..fa6ab8f1fb5 --- /dev/null +++ b/chromium/chrome/renderer/searchbox/OWNERS @@ -0,0 +1,4 @@ +file://chrome/browser/search/OWNERS + +dcblack@chromium.org +# COMPONENT: UI>Browser>NewTabPage diff --git a/chromium/chrome/renderer/searchbox/search_bouncer.cc b/chromium/chrome/renderer/searchbox/search_bouncer.cc new file mode 100644 index 00000000000..126a99f82f8 --- /dev/null +++ b/chromium/chrome/renderer/searchbox/search_bouncer.cc @@ -0,0 +1,56 @@ +// Copyright 2013 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/search_bouncer.h" + +#include <utility> + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "mojo/public/cpp/bindings/pending_associated_receiver.h" + +namespace { +base::LazyInstance<SearchBouncer>::Leaky g_search_bouncer = + LAZY_INSTANCE_INITIALIZER; + +GURL RemoveQueryAndRef(const GURL& url) { + url::Replacements<char> replacements; + replacements.ClearQuery(); + replacements.ClearRef(); + return url.ReplaceComponents(replacements); +} + +} // namespace + +SearchBouncer::SearchBouncer() = default; + +SearchBouncer::~SearchBouncer() = default; + +// static +SearchBouncer* SearchBouncer::GetInstance() { + return g_search_bouncer.Pointer(); +} + +void SearchBouncer::RegisterMojoInterfaces( + blink::AssociatedInterfaceRegistry* associated_interfaces) { + // Note: Unretained is safe here because this class is a leaky LazyInstance. + // For the same reason, UnregisterMojoInterfaces isn't required. + associated_interfaces->AddInterface(base::Bind( + &SearchBouncer::BindSearchBouncerReceiver, base::Unretained(this))); +} + +bool SearchBouncer::IsNewTabPage(const GURL& url) const { + GURL url_no_query_or_ref = RemoveQueryAndRef(url); + return url_no_query_or_ref.is_valid() && + url_no_query_or_ref == new_tab_page_url_; +} + +void SearchBouncer::SetNewTabPageURL(const GURL& new_tab_page_url) { + new_tab_page_url_ = new_tab_page_url; +} + +void SearchBouncer::BindSearchBouncerReceiver( + mojo::PendingAssociatedReceiver<chrome::mojom::SearchBouncer> receiver) { + search_bouncer_receiver_.Bind(std::move(receiver)); +} diff --git a/chromium/chrome/renderer/searchbox/search_bouncer.h b/chromium/chrome/renderer/searchbox/search_bouncer.h new file mode 100644 index 00000000000..929a85fb38f --- /dev/null +++ b/chromium/chrome/renderer/searchbox/search_bouncer.h @@ -0,0 +1,48 @@ +// Copyright 2013 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. + +#ifndef CHROME_RENDERER_SEARCHBOX_SEARCH_BOUNCER_H_ +#define CHROME_RENDERER_SEARCHBOX_SEARCH_BOUNCER_H_ + +#include "base/macros.h" +#include "chrome/common/search.mojom.h" +#include "content/public/renderer/render_thread_observer.h" +#include "mojo/public/cpp/bindings/associated_receiver.h" +#include "mojo/public/cpp/bindings/pending_associated_receiver.h" +#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h" +#include "url/gurl.h" + +// SearchBouncer tracks a set of URLs which should be transferred back to the +// browser process for potential reassignment to an Instant renderer process. +class SearchBouncer : public content::RenderThreadObserver, + public chrome::mojom::SearchBouncer { + public: + SearchBouncer(); + ~SearchBouncer() override; + + static SearchBouncer* GetInstance(); + + // RenderThreadObserver: + void RegisterMojoInterfaces( + blink::AssociatedInterfaceRegistry* associated_interfaces) override; + + // Returns whether |url| is a valid Instant new tab page URL. + bool IsNewTabPage(const GURL& url) const; + + // chrome::mojom::SearchBouncer: + void SetNewTabPageURL(const GURL& new_tab_page_url) override; + + private: + void BindSearchBouncerReceiver( + mojo::PendingAssociatedReceiver<chrome::mojom::SearchBouncer> receiver); + + GURL new_tab_page_url_; + + mojo::AssociatedReceiver<chrome::mojom::SearchBouncer> + search_bouncer_receiver_{this}; + + DISALLOW_COPY_AND_ASSIGN(SearchBouncer); +}; + +#endif // CHROME_RENDERER_SEARCHBOX_SEARCH_BOUNCER_H_ diff --git a/chromium/chrome/renderer/searchbox/search_bouncer_unittest.cc b/chromium/chrome/renderer/searchbox/search_bouncer_unittest.cc new file mode 100644 index 00000000000..0d18a93e2a7 --- /dev/null +++ b/chromium/chrome/renderer/searchbox/search_bouncer_unittest.cc @@ -0,0 +1,29 @@ +// Copyright 2013 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/search_bouncer.h" + +#include <vector> + +#include "chrome/common/url_constants.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +class SearchBouncerTest : public testing::Test { + public: + void SetUp() override { + bouncer_.SetNewTabPageURL(GURL("http://example.com/newtab")); + } + + SearchBouncer bouncer_; +}; + +TEST_F(SearchBouncerTest, IsNewTabPage) { + EXPECT_FALSE(bouncer_.IsNewTabPage(GURL("http://example.com/foo"))); + EXPECT_TRUE(bouncer_.IsNewTabPage(GURL("http://example.com/newtab"))); + EXPECT_TRUE(bouncer_.IsNewTabPage(GURL("http://example.com/newtab?q=foo"))); + EXPECT_TRUE(bouncer_.IsNewTabPage(GURL("http://example.com/newtab#q=foo"))); + EXPECT_TRUE( + bouncer_.IsNewTabPage(GURL("http://example.com/newtab#q=foo?q=foo"))); +} diff --git a/chromium/chrome/renderer/searchbox/searchbox.cc b/chromium/chrome/renderer/searchbox/searchbox.cc new file mode 100644 index 00000000000..ebb238442cf --- /dev/null +++ b/chromium/chrome/renderer/searchbox/searchbox.cc @@ -0,0 +1,596 @@ +// 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 <stddef.h> +#include <stdint.h> + +#include <utility> + +#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.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/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<InstantMostVisitedItemIDPair>& old_item_id_pairs, + const std::vector<InstantMostVisitedItem>& 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 "<view_id>/<restricted_id>". If successful, assigns +// |*view_id| := "<view_id>", |*rid| := "<restricted_id>", 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<base::StringPiece> 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/<view_id>/<restricted_id> +// chrome-search://favicon/<parameters>/<view_id>/<restricted_id> +// +// If successful, assigns |*param_part| := "" or "<parameters>/" (note trailing +// slash), |*view_id| := "<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<SearchBox>(render_frame), + binding_(this), + 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<chrome::mojom::EmbeddedSearchConnector> connector; + render_frame->GetRemoteAssociatedInterfaces()->GetInterface(&connector); + chrome::mojom::EmbeddedSearchClientAssociatedPtrInfo embedded_search_client; + binding_.Bind(mojo::MakeRequest(&embedded_search_client)); + connector->Connect(mojo::MakeRequest(&embedded_search_service_), + 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<InstantMostVisitedItemIDPair>* 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 ThemeBackgroundInfo* SearchBox::GetThemeBackgroundInfo() const { + return base::OptionalOrNullptr(theme_info_); +} + +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<uint8_t>& hash) { + embedded_search_service_->BlocklistSearchSuggestionWithHash(task_version, + task_id, hash); +} + +void SearchBox::SearchSuggestionSelected(int task_version, + long task_id, + const std::vector<uint8_t>& 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) { + embedded_search_service_->QueryAutocomplete( + input, base::BindOnce(&SearchBox::QueryAutocompleteResult, + weak_ptr_factory_.GetWeakPtr())); +} + +void SearchBox::DeleteAutocompleteMatch(uint8_t line) { + embedded_search_service_->DeleteAutocompleteMatch( + line, base::BindOnce(&SearchBox::OnDeleteAutocompleteMatch, + base::Unretained(this))); +} + +void SearchBox::StopAutocomplete(bool clear_result) { + embedded_search_service_->StopAutocomplete(clear_result); +} + +void SearchBox::BlocklistPromo(const std::string& promo_id) { + embedded_search_service_->BlocklistPromo(promo_id); +} + +void SearchBox::QueryAutocompleteResult( + chrome::mojom::AutocompleteResultPtr result) { + if (can_run_js_in_renderframe_) { + SearchBoxExtension::DispatchQueryAutocompleteResult( + render_frame()->GetWebFrame(), std::move(result)); + } +} + +void SearchBox::OnDeleteAutocompleteMatch( + chrome::mojom::DeleteAutocompleteMatchResultPtr result) { + if (can_run_js_in_renderframe_) { + SearchBoxExtension::DispatchDeleteAutocompleteMatchResult( + render_frame()->GetWebFrame(), std::move(result)); + } +} + +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::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<InstantMostVisitedItemIDPair> 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 ThemeBackgroundInfo& theme_info) { + // Do not send duplicate notifications. + if (theme_info_ == theme_info) + return; + + theme_info_ = theme_info; + 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(bool is_same_document_navigation, + ui::PageTransition transition) { + can_run_js_in_renderframe_ = true; +} + +void SearchBox::OnDestruct() { + delete this; +} diff --git a/chromium/chrome/renderer/searchbox/searchbox.h b/chromium/chrome/renderer/searchbox/searchbox.h new file mode 100644 index 00000000000..c7755aab6aa --- /dev/null +++ b/chromium/chrome/renderer/searchbox/searchbox.h @@ -0,0 +1,274 @@ +// 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. + +#ifndef CHROME_RENDERER_SEARCHBOX_SEARCHBOX_H_ +#define CHROME_RENDERER_SEARCHBOX_SEARCHBOX_H_ + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/strings/string16.h" +#include "chrome/common/search.mojom.h" +#include "chrome/common/search/instant_types.h" +#include "chrome/common/search/ntp_logging_events.h" +#include "chrome/renderer/instant_restricted_id_cache.h" +#include "components/ntp_tiles/ntp_tile_impression.h" +#include "components/omnibox/common/omnibox_focus_state.h" +#include "content/public/renderer/render_frame_observer.h" +#include "content/public/renderer/render_frame_observer_tracker.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "url/gurl.h" + +// The renderer-side implementation of the embeddedSearch API (see +// https://www.chromium.org/embeddedsearch). +class SearchBox : public content::RenderFrameObserver, + public content::RenderFrameObserverTracker<SearchBox>, + public chrome::mojom::EmbeddedSearchClient { + public: + // Helper class for GenerateImageURLFromTransientURL() to adapt SearchBox's + // instance, thereby allow mocking for unit tests. + class IconURLHelper { + public: + IconURLHelper(); + virtual ~IconURLHelper(); + // Retruns view id for validating icon URL. + virtual int GetViewID() const = 0; + // Returns the page URL string for |rid|, or empty string for invalid |rid|. + virtual std::string GetURLStringFromRestrictedID(InstantRestrictedID rid) + const = 0; + }; + + explicit SearchBox(content::RenderFrame* render_frame); + ~SearchBox() override; + + // Sends LogEvent to the browser. + void LogEvent(NTPLoggingEventType event); + + // Sends LogSuggestionEventWithValue to the browser. + void LogSuggestionEventWithValue(NTPSuggestionsLoggingEventType event, + int data); + + // Sends LogMostVisitedImpression to the browser. + void LogMostVisitedImpression(const ntp_tiles::NTPTileImpression& impression); + + // Sends LogMostVisitedNavigation to the browser. + void LogMostVisitedNavigation(const ntp_tiles::NTPTileImpression& impression); + + // Sends DeleteMostVisitedItem to the browser. + void DeleteMostVisitedItem(InstantRestrictedID most_visited_item_id); + + // Generates the image URL of the most visited item favicon specified by + // |transient_url|. If |transient_url| is valid, |url| is set with a + // translated URL. Otherwise, |url| is set the the default favicon + // ("chrome-search://favicon/"). + // + // Valid forms of |transient_url|: + // chrome-search://favicon/<view_id>/<restricted_id> + // chrome-search://favicon/<favicon_parameters>/<view_id>/<restricted_id> + // + // We do this to prevent search providers from abusing image URLs and deduce + // whether the user has visited a particular page. For example, if + // "chrome-search://favicon/http://www.secretsite.com" is accessible, then + // the search provider can use its return code to determine whether the user + // has visited "http://www.secretsite.com". Therefore we require search + // providers to specify URL by "<view_id>/<restricted_id>". We then translate + // this to the original |url|, and pass the request to the proper endpoint. + void GenerateImageURLFromTransientURL(const GURL& transient_url, + GURL* url) const; + + // Returns the latest most visited items sent by the browser. + void GetMostVisitedItems( + std::vector<InstantMostVisitedItemIDPair>* items) const; + + bool AreMostVisitedItemsAvailable() const; + + // If the |most_visited_item_id| is found in the cache, sets |item| to it + // and returns true. + bool GetMostVisitedItemWithID(InstantRestrictedID most_visited_item_id, + InstantMostVisitedItem* item) const; + + // Sends PasteAndOpenDropdown to the browser. + void Paste(const base::string16& text); + + // Will return null if the theme info hasn't been set yet. + const ThemeBackgroundInfo* GetThemeBackgroundInfo() const; + + // Sends FocusOmnibox(OMNIBOX_FOCUS_INVISIBLE) to the browser. + void StartCapturingKeyStrokes(); + + // Sends FocusOmnibox(OMNIBOX_FOCUS_NONE) to the browser. + void StopCapturingKeyStrokes(); + + // Sends UndoAllMostVisitedDeletions to the browser. + void UndoAllMostVisitedDeletions(); + + // Sends UndoMostVisitedDeletion to the browser. + void UndoMostVisitedDeletion(InstantRestrictedID most_visited_item_id); + + // Returns true if the most visited items are custom links. + bool IsCustomLinks() const; + + // Returns true if most visited is enabled instead of custom links. + bool IsUsingMostVisited() const; + + // Returns true if the shortcuts are visible and not hidden by the user. + bool AreShortcutsVisible() const; + + // Sends AddCustomLink to the browser. + void AddCustomLink(const GURL& url, const std::string& title); + + // Sends UpdateCustomLink to the browser. + void UpdateCustomLink(InstantRestrictedID link_id, + const GURL& new_url, + const std::string& new_title); + + // Sends ReorderCustomLink to the browser. + void ReorderCustomLink(InstantRestrictedID link_id, int new_pos); + + // Sends DeleteCustomLink to the browser. + void DeleteCustomLink(InstantRestrictedID most_visited_item_id); + + // Sends UndoCustomLinkAction to the browser. + void UndoCustomLinkAction(); + + // Sends ResetCustomLinks to the browser. + void ResetCustomLinks(); + + // Sends ToggleMostVisitedOrCustomLinks to the browser. + void ToggleMostVisitedOrCustomLinks(); + + // Sends ToggleShortcutsVisibility to the browser. + void ToggleShortcutsVisibility(bool do_notify); + + // Attempts to fix obviously invalid URLs. Uses the "https" scheme unless + // otherwise specified. Returns the fixed URL if valid, otherwise returns an + // empty string. + std::string FixupAndValidateUrl(const std::string& url) const; + + // Updates the NTP custom background preferences, sometimes this includes + // image attributions. + void 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); + + // Let the user select a local file for the NTP background. + void SelectLocalBackgroundImage(); + + // Add a search suggestion task id to the blocklist. + void BlocklistSearchSuggestion(int task_version, long task_id); + + // Add a search suggestion task id and hash to the blocklist. + void BlocklistSearchSuggestionWithHash(int task_version, + long task_id, + const std::vector<uint8_t>& hash); + + // A suggestion collected, issue a new request with the suggestion + // temporarily added to the blocklist. + void SearchSuggestionSelected(int task_version, + long task_id, + const std::vector<uint8_t>& hash); + + // Opts the user out of receiving search suggestions. + void OptOutOfSearchSuggestions(); + + // Applies the default theme. + void ApplyDefaultTheme(); + + // Applies autogenerated theme for the given color. + void ApplyAutogeneratedTheme(SkColor color); + + // Reverts applied theme changes. + void RevertThemeChanges(); + + // Confirms applied theme changes. + void ConfirmThemeChanges(); + + // Queries the autocomplete backend for realbox results for |input| as a + // search term. Handled by |QueryAutocompleteResult|. + void QueryAutocomplete(const base::string16& input); + + // Deletes |AutocompleteMatch| by index of the result. + void DeleteAutocompleteMatch(uint8_t line); + + // Cancels the current autocomplete query. Clears the result set if + // |clear_result| is true. + void StopAutocomplete(bool clear_result); + + // Called when a user dismisses a promo. + void BlocklistPromo(const std::string& promo_id); + + bool is_focused() const { return is_focused_; } + bool is_input_in_progress() const { return is_input_in_progress_; } + bool is_key_capture_enabled() const { return is_key_capture_enabled_; } + + private: + // Overridden from content::RenderFrameObserver: + void DidCommitProvisionalLoad(bool is_same_document_navigation, + ui::PageTransition transition) override; + void OnDestruct() override; + + // Overridden from chrome::mojom::EmbeddedSearchClient: + void SetPageSequenceNumber(int page_seq_no) override; + void FocusChanged(OmniboxFocusState new_focus_state, + OmniboxFocusChangeReason reason) override; + void MostVisitedInfoChanged( + const InstantMostVisitedInfo& most_visited_info) override; + void SetInputInProgress(bool input_in_progress) override; + void ThemeChanged(const ThemeBackgroundInfo& theme_info) override; + void LocalBackgroundSelected() override; + + void AddCustomLinkResult(bool success); + void UpdateCustomLinkResult(bool success); + void DeleteCustomLinkResult(bool success); + + // Returns the URL of the Most Visited item specified by the |item_id|. + GURL GetURLForMostVisitedItem(InstantRestrictedID item_id) const; + + // Asynchronous callback for autocomplete query results. Sends to renderer. + void QueryAutocompleteResult(chrome::mojom::AutocompleteResultPtr result); + + // Asynchronous callback for results of attempting to delete an autocomplete + // result. + void OnDeleteAutocompleteMatch( + chrome::mojom::DeleteAutocompleteMatchResultPtr result); + + // The connection to the EmbeddedSearch service in the browser process. + chrome::mojom::EmbeddedSearchAssociatedPtr embedded_search_service_; + mojo::AssociatedBinding<chrome::mojom::EmbeddedSearchClient> binding_; + + // Whether it's legal to execute JavaScript in |render_frame()|. + // This class may want to execute JS in response to IPCs (via the + // SearchBoxExtension::Dispatch* methods). However, for cross-process + // navigations, a "provisional frame" is created at first, and it's illegal + // to execute any JS in it before it is actually swapped in, i.e. before the + // navigation has committed. So this only gets set to true in + // RenderFrameObserver::DidCommitProvisionalLoad. See crbug.com/765101. + // Note: If crbug.com/794942 ever gets resolved, then it might be possible to + // move the mojo connection code from the ctor to DidCommitProvisionalLoad and + // avoid this bool. + bool can_run_js_in_renderframe_; + + // The Instant state. + int page_seq_no_; + bool is_focused_; + bool is_input_in_progress_; + bool is_key_capture_enabled_; + InstantRestrictedIDCache<InstantMostVisitedItem> most_visited_items_cache_; + // Use |most_visited_items_cache_| instead of |most_visited_info_.items| when + // comparing most visited items. + InstantMostVisitedInfo most_visited_info_; + bool has_received_most_visited_; + base::Optional<ThemeBackgroundInfo> theme_info_; + + base::WeakPtrFactory<SearchBox> weak_ptr_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(SearchBox); +}; + +#endif // CHROME_RENDERER_SEARCHBOX_SEARCHBOX_H_ diff --git a/chromium/chrome/renderer/searchbox/searchbox_extension.cc b/chromium/chrome/renderer/searchbox/searchbox_extension.cc new file mode 100644 index 00000000000..5412c5566fd --- /dev/null +++ b/chromium/chrome/renderer/searchbox/searchbox_extension.cc @@ -0,0 +1,1469 @@ +// 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_extension.h" + +#include <stddef.h> +#include <stdint.h> +#include <string> +#include <vector> + +#include "base/i18n/rtl.h" +#include "base/json/json_writer.h" +#include "base/json/string_escape.h" +#include "base/macros.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/user_metrics.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "chrome/common/search/generated_colors_info.h" +#include "chrome/common/search/instant_types.h" +#include "chrome/common/search/ntp_logging_events.h" +#include "chrome/common/search/selected_colors_info.h" +#include "chrome/common/url_constants.h" +#include "chrome/grit/renderer_resources.h" +#include "chrome/renderer/searchbox/searchbox.h" +#include "components/crx_file/id_util.h" +#include "components/ntp_tiles/features.h" +#include "components/ntp_tiles/ntp_tile_impression.h" +#include "components/ntp_tiles/tile_source.h" +#include "components/ntp_tiles/tile_visual_type.h" +#include "content/public/renderer/chrome_object_extensions_utils.h" +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/render_thread.h" +#include "content/public/renderer/render_view.h" +#include "gin/data_object_builder.h" +#include "gin/handle.h" +#include "gin/object_template_builder.h" +#include "gin/wrappable.h" +#include "third_party/blink/public/common/page/page_zoom.h" +#include "third_party/blink/public/platform/web_string.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/public/web/blink.h" +#include "third_party/blink/public/web/web_document.h" +#include "third_party/blink/public/web/web_local_frame.h" +#include "third_party/blink/public/web/web_script_source.h" +#include "third_party/blink/public/web/web_view.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/window_open_disposition.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/text_constants.h" +#include "ui/gfx/text_elider.h" +#include "url/gurl.h" +#include "url/url_constants.h" +#include "v8/include/v8.h" + +namespace { + +const char kCSSBackgroundImageFormat[] = "-webkit-image-set(" + "url(chrome-search://theme/IDR_THEME_NTP_BACKGROUND?%s) 1x, " + "url(chrome-search://theme/IDR_THEME_NTP_BACKGROUND@2x?%s) 2x)"; + +const char kCSSBackgroundPositionCenter[] = "center"; +const char kCSSBackgroundPositionLeft[] = "left"; +const char kCSSBackgroundPositionTop[] = "top"; +const char kCSSBackgroundPositionRight[] = "right"; +const char kCSSBackgroundPositionBottom[] = "bottom"; + +const char kCSSBackgroundRepeatNo[] = "no-repeat"; +const char kCSSBackgroundRepeatX[] = "repeat-x"; +const char kCSSBackgroundRepeatY[] = "repeat-y"; +const char kCSSBackgroundRepeat[] = "repeat"; + +const char kThemeAttributionFormat[] = "-webkit-image-set(" + "url(chrome-search://theme/IDR_THEME_NTP_ATTRIBUTION?%s) 1x, " + "url(chrome-search://theme/IDR_THEME_NTP_ATTRIBUTION@2x?%s) 2x)"; + +const char kLTRHtmlTextDirection[] = "ltr"; +const char kRTLHtmlTextDirection[] = "rtl"; + +// Max character limit for custom link titles. +const size_t kMaxCustomLinkTitleLength = 150; + +void Dispatch(blink::WebLocalFrame* frame, const blink::WebString& script) { + if (!frame) + return; + frame->ExecuteScript(blink::WebScriptSource(script)); +} + +// Populates a Javascript MostVisitedItem object for returning from +// newTabPage.mostVisited. This does not include private data such as "url" or +// "title". +v8::Local<v8::Object> GenerateMostVisitedItem( + v8::Isolate* isolate, + float device_pixel_ratio, + int render_view_id, + InstantRestrictedID restricted_id) { + return gin::DataObjectBuilder(isolate) + .Set("rid", restricted_id) + .Set("faviconUrl", base::StringPrintf( + "chrome-search://favicon/size/16@%fx/%d/%d", + device_pixel_ratio, render_view_id, restricted_id)) + .Build(); +} + +// Populates a Javascript MostVisitedItem object appropriate for returning from +// newTabPage.getMostVisitedItemData. +// NOTE: Includes private data such as "url", "title", and "domain", so this +// should not be returned to the host page (via newTabPage.mostVisited). It is +// only accessible to most-visited iframes via getMostVisitedItemData. +v8::Local<v8::Object> GenerateMostVisitedItemData( + v8::Isolate* isolate, + int render_view_id, + InstantRestrictedID restricted_id, + const InstantMostVisitedItem& mv_item) { + // We set the "dir" attribute of the title, so that in RTL locales, a LTR + // title is rendered left-to-right and truncated from the right. For + // example, the title of http://msdn.microsoft.com/en-us/default.aspx is + // "MSDN: Microsoft developer network". In RTL locales, in the New Tab + // page, if the "dir" of this title is not specified, it takes Chrome UI's + // directionality. So the title will be truncated as "soft developer + // network". Setting the "dir" attribute as "ltr" renders the truncated + // title as "MSDN: Microsoft D...". As another example, the title of + // http://yahoo.com is "Yahoo!". In RTL locales, in the New Tab page, the + // title will be rendered as "!Yahoo" if its "dir" attribute is not set to + // "ltr". + const char* direction; + if (base::i18n::GetFirstStrongCharacterDirection(mv_item.title) == + base::i18n::RIGHT_TO_LEFT) { + direction = kRTLHtmlTextDirection; + } else { + direction = kLTRHtmlTextDirection; + } + + std::string title = base::UTF16ToUTF8(mv_item.title); + if (title.empty()) + title = mv_item.url.spec(); + + gin::DataObjectBuilder builder(isolate); + builder.Set("renderViewId", render_view_id) + .Set("rid", restricted_id) + .Set("tileTitleSource", static_cast<int>(mv_item.title_source)) + .Set("tileSource", static_cast<int>(mv_item.source)) + .Set("title", title) + .Set("domain", mv_item.url.host()) + .Set("direction", base::StringPiece(direction)) + .Set("url", mv_item.url.spec()) + .Set("dataGenerationTime", + mv_item.data_generation_time.is_null() + ? v8::Local<v8::Value>(v8::Null(isolate)) + : v8::Date::New(isolate->GetCurrentContext(), + mv_item.data_generation_time.ToJsTime()) + .ToLocalChecked()); + + // If the suggestion already has a favicon, we populate the element with it. + if (!mv_item.favicon.spec().empty()) + builder.Set("faviconUrl", mv_item.favicon.spec()); + + return builder.Build(); +} + +base::Time ConvertDateValueToTime(v8::Value* value) { + DCHECK(value); + if (value->IsNull() || !value->IsDate()) + return base::Time(); + + return base::Time::FromJsTime(v8::Date::Cast(value)->ValueOf()); +} + +base::Optional<int> CoerceToInt(v8::Isolate* isolate, v8::Value* value) { + DCHECK(value); + v8::MaybeLocal<v8::Int32> maybe_int = + value->ToInt32(isolate->GetCurrentContext()); + if (maybe_int.IsEmpty()) + return base::nullopt; + return maybe_int.ToLocalChecked()->Value(); +} + +// Returns an array with the RGBA color components. +v8::Local<v8::Value> SkColorToArray(v8::Isolate* isolate, + const SkColor& color) { + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Array> color_array = v8::Array::New(isolate, 4); + color_array + ->CreateDataProperty(context, 0, + v8::Int32::New(isolate, SkColorGetR(color))) + .Check(); + color_array + ->CreateDataProperty(context, 1, + v8::Int32::New(isolate, SkColorGetG(color))) + .Check(); + color_array + ->CreateDataProperty(context, 2, + v8::Int32::New(isolate, SkColorGetB(color))) + .Check(); + color_array + ->CreateDataProperty(context, 3, + v8::Int32::New(isolate, SkColorGetA(color))) + .Check(); + return color_array; +} + +// Converts given array to SkColor and returns whether the conversion is +// successful. +bool ArrayToSkColor(v8::Isolate* isolate, + v8::Local<v8::Array> color, + SkColor* color_result) { + if (color->Length() != 4) + return false; + + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Value> r_value; + v8::Local<v8::Value> g_value; + v8::Local<v8::Value> b_value; + v8::Local<v8::Value> a_value; + + if (!color->Get(context, 0).ToLocal(&r_value) || + !color->Get(context, 1).ToLocal(&g_value) || + !color->Get(context, 2).ToLocal(&b_value) || + !color->Get(context, 3).ToLocal(&a_value)) + return false; + + base::Optional<int> r = CoerceToInt(isolate, *r_value); + base::Optional<int> g = CoerceToInt(isolate, *g_value); + base::Optional<int> b = CoerceToInt(isolate, *b_value); + base::Optional<int> a = CoerceToInt(isolate, *a_value); + + if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value()) + return false; + + if (*a > 255 || *r > 255 || *g > 255 || *b > 255) + return false; + + *color_result = SkColorSetARGB(*a, *r, *g, *b); + return true; +} + +v8::Local<v8::Object> GenerateThemeBackgroundInfo( + v8::Isolate* isolate, + const ThemeBackgroundInfo& theme_info) { + gin::DataObjectBuilder builder(isolate); + + // True if the theme is the system default and no custom theme has been + // applied. + // Value is always valid. + builder.Set("usingDefaultTheme", theme_info.using_default_theme); + + // Theme color for background as an array with the RGBA components in order. + // Value is always valid. + builder.Set("backgroundColorRgba", + SkColorToArray(isolate, theme_info.background_color)); + + // Theme color for text as an array with the RGBA components in order. + // Value is always valid. + builder.Set("textColorRgba", SkColorToArray(isolate, theme_info.text_color)); + + // Theme color for light text as an array with the RGBA components in order. + // Value is always valid. + builder.Set("textColorLightRgba", + SkColorToArray(isolate, theme_info.text_color_light)); + + // The theme alternate logo value indicates same color when TRUE and a + // colorful one when FALSE. + builder.Set("alternateLogo", theme_info.logo_alternate); + + // The theme background image url is of format kCSSBackgroundImageFormat + // where both instances of "%s" are replaced with the id that identifies the + // theme. + // This is the CSS "background-image" format. + // Value is only valid if there's a custom theme background image. + if (theme_info.has_theme_image) { + builder.Set("imageUrl", base::StringPrintf(kCSSBackgroundImageFormat, + theme_info.theme_id.c_str(), + theme_info.theme_id.c_str())); + + // The theme background image horizontal alignment is one of "left", + // "right", "center". + // This is the horizontal component of the CSS "background-position" format. + // Value is only valid if |imageUrl| is not empty. + std::string alignment = kCSSBackgroundPositionCenter; + if (theme_info.image_horizontal_alignment == + THEME_BKGRND_IMAGE_ALIGN_LEFT) { + alignment = kCSSBackgroundPositionLeft; + } else if (theme_info.image_horizontal_alignment == + THEME_BKGRND_IMAGE_ALIGN_RIGHT) { + alignment = kCSSBackgroundPositionRight; + } + builder.Set("imageHorizontalAlignment", alignment); + + // The theme background image vertical alignment is one of "top", "bottom", + // "center". + // This is the vertical component of the CSS "background-position" format. + // Value is only valid if |image_url| is not empty. + if (theme_info.image_vertical_alignment == THEME_BKGRND_IMAGE_ALIGN_TOP) { + alignment = kCSSBackgroundPositionTop; + } else if (theme_info.image_vertical_alignment == + THEME_BKGRND_IMAGE_ALIGN_BOTTOM) { + alignment = kCSSBackgroundPositionBottom; + } else { + alignment = kCSSBackgroundPositionCenter; + } + builder.Set("imageVerticalAlignment", alignment); + + // The tiling of the theme background image is one of "no-repeat", + // "repeat-x", "repeat-y", "repeat". + // This is the CSS "background-repeat" format. + // Value is only valid if |image_url| is not empty. + std::string tiling = kCSSBackgroundRepeatNo; + switch (theme_info.image_tiling) { + case THEME_BKGRND_IMAGE_NO_REPEAT: + tiling = kCSSBackgroundRepeatNo; + break; + case THEME_BKGRND_IMAGE_REPEAT_X: + tiling = kCSSBackgroundRepeatX; + break; + case THEME_BKGRND_IMAGE_REPEAT_Y: + tiling = kCSSBackgroundRepeatY; + break; + case THEME_BKGRND_IMAGE_REPEAT: + tiling = kCSSBackgroundRepeat; + break; + } + builder.Set("imageTiling", tiling); + + // The attribution URL is only valid if the theme has attribution logo. + if (theme_info.has_attribution) { + builder.Set("attributionUrl", + base::StringPrintf(kThemeAttributionFormat, + theme_info.theme_id.c_str(), + theme_info.theme_id.c_str())); + } + } + + builder.Set("themeId", theme_info.theme_id); + builder.Set("themeName", theme_info.theme_name); + + builder.Set("customBackgroundConfigured", + !theme_info.custom_background_url.is_empty()); + + // If a custom background has been set provide the relevant information to the + // page. + if (!theme_info.custom_background_url.is_empty()) { + builder.Set("imageUrl", theme_info.custom_background_url.spec()); + builder.Set("attributionActionUrl", + theme_info.custom_background_attribution_action_url.spec()); + builder.Set("attribution1", + theme_info.custom_background_attribution_line_1); + builder.Set("attribution2", + theme_info.custom_background_attribution_line_2); + builder.Set("collectionId", theme_info.collection_id); + // Clear the theme attribution url, as it shouldn't be shown when + // a custom background is set. + builder.Set("attributionUrl", std::string()); + } + + // Set fields for themeing NTP elements. + builder.Set("isNtpBackgroundDark", + !color_utils::IsDark(theme_info.text_color)); + builder.Set("useTitleContainer", theme_info.has_theme_image); + + // TODO(gayane): Rename icon color to shortcut color in JS for consitancy. + builder.Set("iconBackgroundColor", + SkColorToArray(isolate, theme_info.shortcut_color)); + builder.Set("useWhiteAddIcon", + color_utils::IsDark(theme_info.shortcut_color)); + + builder.Set("logoColor", SkColorToArray(isolate, theme_info.logo_color)); + + builder.Set("colorId", theme_info.color_id); + if (theme_info.color_id != -1) { + builder.Set("colorDark", SkColorToArray(isolate, theme_info.color_dark)); + builder.Set("colorLight", SkColorToArray(isolate, theme_info.color_light)); + builder.Set("colorPicked", + SkColorToArray(isolate, theme_info.color_picked)); + } + + return builder.Build(); +} + +content::RenderFrame* GetMainRenderFrameForCurrentContext() { + blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForCurrentContext(); + if (!frame) + return nullptr; + content::RenderFrame* main_frame = + content::RenderFrame::FromWebFrame(frame->LocalRoot()); + if (!main_frame || !main_frame->IsMainFrame()) + return nullptr; + return main_frame; +} + +SearchBox* GetSearchBoxForCurrentContext() { + content::RenderFrame* main_frame = GetMainRenderFrameForCurrentContext(); + if (!main_frame) + return nullptr; + return SearchBox::Get(main_frame); +} + +base::Value CreateAutocompleteMatches( + const std::vector<chrome::mojom::AutocompleteMatchPtr>& matches) { + base::Value list(base::Value::Type::LIST); + for (const chrome::mojom::AutocompleteMatchPtr& match : matches) { + base::Value dict(base::Value::Type::DICTIONARY); + dict.SetBoolKey("allowedToBeDefaultMatch", + match->allowed_to_be_default_match); + dict.SetStringKey("contents", match->contents); + base::Value contents_class(base::Value::Type::LIST); + for (const auto& classification : match->contents_class) { + base::Value entry(base::Value::Type::DICTIONARY); + entry.SetIntKey("offset", classification->offset); + entry.SetIntKey("style", classification->style); + contents_class.Append(std::move(entry)); + } + dict.SetKey("contentsClass", std::move(contents_class)); + dict.SetStringKey("description", match->description); + base::Value description_class(base::Value::Type::LIST); + for (const auto& classification : match->description_class) { + base::Value entry(base::Value::Type::DICTIONARY); + entry.SetIntKey("offset", classification->offset); + entry.SetIntKey("style", classification->style); + description_class.Append(std::move(entry)); + } + dict.SetKey("descriptionClass", std::move(description_class)); + dict.SetStringKey("destinationUrl", match->destination_url); + dict.SetStringKey("inlineAutocompletion", match->inline_autocompletion); + dict.SetBoolKey("isSearchType", match->is_search_type); + dict.SetStringKey("fillIntoEdit", match->fill_into_edit); + dict.SetBoolKey("swapContentsAndDescription", + match->swap_contents_and_description); + dict.SetStringKey("type", match->type); + dict.SetBoolKey("supportsDeletion", match->supports_deletion); + list.Append(std::move(dict)); + } + return list; +} + +static const char kDispatchFocusChangedScript[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.searchBox &&" + " window.chrome.embeddedSearch.searchBox.onfocuschange &&" + " typeof window.chrome.embeddedSearch.searchBox.onfocuschange ==" + " 'function') {" + " window.chrome.embeddedSearch.searchBox.onfocuschange();" + " true;" + "}"; + +static const char kDispatchAddCustomLinkResult[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.newTabPage &&" + " window.chrome.embeddedSearch.newTabPage.onaddcustomlinkdone &&" + " typeof window.chrome.embeddedSearch.newTabPage" + " .onaddcustomlinkdone === 'function') {" + " window.chrome.embeddedSearch.newTabPage.onaddcustomlinkdone(%s);" + " true;" + "}"; + +static const char kDispatchUpdateCustomLinkResult[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.newTabPage &&" + " window.chrome.embeddedSearch.newTabPage.onupdatecustomlinkdone &&" + " typeof window.chrome.embeddedSearch.newTabPage" + " .onupdatecustomlinkdone === 'function') {" + " window.chrome.embeddedSearch.newTabPage.onupdatecustomlinkdone(%s);" + " true;" + "}"; + +static const char kDispatchQueryAutocompleteResult[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.searchBox &&" + " window.chrome.embeddedSearch.searchBox.onqueryautocompletedone &&" + " typeof window.chrome.embeddedSearch.searchBox" + " .onqueryautocompletedone === 'function') {" + " window.chrome.embeddedSearch.searchBox.onqueryautocompletedone(%s);" + " true;" + "}"; + +static const char kDispatchDeleteAutocompleteMatchResult[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.searchBox &&" + " window.chrome.embeddedSearch.searchBox.ondeleteautocompletematch &&" + " typeof window.chrome.embeddedSearch.searchBox" + " .ondeleteautocompletematch === 'function') {" + " window.chrome.embeddedSearch.searchBox.ondeleteautocompletematch(%s);" + " true;" + "}"; + +static const char kDispatchDeleteCustomLinkResult[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.newTabPage &&" + " window.chrome.embeddedSearch.newTabPage.ondeletecustomlinkdone &&" + " typeof window.chrome.embeddedSearch.newTabPage" + " .ondeletecustomlinkdone === 'function') {" + " window.chrome.embeddedSearch.newTabPage.ondeletecustomlinkdone(%s);" + " true;" + "}"; + +static const char kDispatchInputCancelScript[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.newTabPage &&" + " window.chrome.embeddedSearch.newTabPage.oninputcancel &&" + " typeof window.chrome.embeddedSearch.newTabPage.oninputcancel ==" + " 'function') {" + " window.chrome.embeddedSearch.newTabPage.oninputcancel();" + " true;" + "}"; + +static const char kDispatchInputStartScript[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.newTabPage &&" + " window.chrome.embeddedSearch.newTabPage.oninputstart &&" + " typeof window.chrome.embeddedSearch.newTabPage.oninputstart ==" + " 'function') {" + " window.chrome.embeddedSearch.newTabPage.oninputstart();" + " true;" + "}"; + +static const char kDispatchKeyCaptureChangeScript[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.searchBox &&" + " window.chrome.embeddedSearch.searchBox.onkeycapturechange &&" + " typeof window.chrome.embeddedSearch.searchBox.onkeycapturechange ==" + " 'function') {" + " window.chrome.embeddedSearch.searchBox.onkeycapturechange();" + " true;" + "}"; + +static const char kDispatchMostVisitedChangedScript[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.newTabPage &&" + " window.chrome.embeddedSearch.newTabPage.onmostvisitedchange &&" + " typeof window.chrome.embeddedSearch.newTabPage.onmostvisitedchange ==" + " 'function') {" + " window.chrome.embeddedSearch.newTabPage.onmostvisitedchange();" + " true;" + "}"; + +static const char kDispatchThemeChangeEventScript[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.newTabPage &&" + " window.chrome.embeddedSearch.newTabPage.onthemechange &&" + " typeof window.chrome.embeddedSearch.newTabPage.onthemechange ==" + " 'function') {" + " window.chrome.embeddedSearch.newTabPage.onthemechange();" + " true;" + "}"; + +static const char kDispatchLocalBackgroundSelectedScript[] = + "if (window.chrome &&" + " window.chrome.embeddedSearch &&" + " window.chrome.embeddedSearch.newTabPage &&" + " window.chrome.embeddedSearch.newTabPage.onlocalbackgroundselected &&" + " typeof " + "window.chrome.embeddedSearch.newTabPage.onlocalbackgroundselected ==" + " 'function') {" + " " + "window.chrome.embeddedSearch.newTabPage." + "onlocalbackgroundselected();" + " true;" + "}"; + +// ---------------------------------------------------------------------------- + +class SearchBoxBindings : public gin::Wrappable<SearchBoxBindings> { + public: + static gin::WrapperInfo kWrapperInfo; + + SearchBoxBindings(); + ~SearchBoxBindings() override; + + private: + // gin::Wrappable. + gin::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) final; + + // Handlers for JS properties. + static bool IsFocused(); + static bool IsKeyCaptureEnabled(); + + // Handlers for JS functions. + static void DeleteAutocompleteMatch(int line); + static void Paste(const std::string& text); + static void QueryAutocomplete(const base::string16& input); + static void StopAutocomplete(bool clear_result); + static void StartCapturingKeyStrokes(); + static void StopCapturingKeyStrokes(); + + DISALLOW_COPY_AND_ASSIGN(SearchBoxBindings); +}; + +gin::WrapperInfo SearchBoxBindings::kWrapperInfo = {gin::kEmbedderNativeGin}; + +SearchBoxBindings::SearchBoxBindings() = default; + +SearchBoxBindings::~SearchBoxBindings() = default; + +gin::ObjectTemplateBuilder SearchBoxBindings::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return gin::Wrappable<SearchBoxBindings>::GetObjectTemplateBuilder(isolate) + .SetProperty("rtl", &base::i18n::IsRTL) + .SetProperty("isFocused", &SearchBoxBindings::IsFocused) + .SetProperty("isKeyCaptureEnabled", + &SearchBoxBindings::IsKeyCaptureEnabled) + .SetMethod("deleteAutocompleteMatch", + &SearchBoxBindings::DeleteAutocompleteMatch) + .SetMethod("paste", &SearchBoxBindings::Paste) + .SetMethod("queryAutocomplete", &SearchBoxBindings::QueryAutocomplete) + .SetMethod("stopAutocomplete", &SearchBoxBindings::StopAutocomplete) + .SetMethod("startCapturingKeyStrokes", + &SearchBoxBindings::StartCapturingKeyStrokes) + .SetMethod("stopCapturingKeyStrokes", + &SearchBoxBindings::StopCapturingKeyStrokes); +} + +// static +bool SearchBoxBindings::IsFocused() { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return false; + return search_box->is_focused(); +} + +// static +bool SearchBoxBindings::IsKeyCaptureEnabled() { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return false; + return search_box->is_key_capture_enabled(); +} + +// static +void SearchBoxBindings::DeleteAutocompleteMatch(int line) { + DCHECK_GE(line, 0); + DCHECK_LE(line, 255); + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->DeleteAutocompleteMatch(line); +} + +// static +void SearchBoxBindings::Paste(const std::string& text) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->Paste(base::UTF8ToUTF16(text)); +} + +// static +void SearchBoxBindings::QueryAutocomplete(const base::string16& input) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->QueryAutocomplete(input); +} + +// static +void SearchBoxBindings::StopAutocomplete(bool clear_result) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->StopAutocomplete(clear_result); +} + +// static +void SearchBoxBindings::StartCapturingKeyStrokes() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->StartCapturingKeyStrokes(); +} + +// static +void SearchBoxBindings::StopCapturingKeyStrokes() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->StopCapturingKeyStrokes(); +} + +class NewTabPageBindings : public gin::Wrappable<NewTabPageBindings> { + public: + static gin::WrapperInfo kWrapperInfo; + + NewTabPageBindings(); + ~NewTabPageBindings() override; + + private: + // gin::Wrappable. + gin::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) final; + + static bool HasOrigin(const GURL& origin); + + // Handlers for JS properties. + static bool IsInputInProgress(); + static v8::Local<v8::Value> GetMostVisited(v8::Isolate* isolate); + static bool GetMostVisitedAvailable(v8::Isolate* isolate); + static v8::Local<v8::Value> GetThemeBackgroundInfo(v8::Isolate* isolate); + static bool GetIsCustomLinks(); + static bool GetIsUsingMostVisited(); + static bool GetAreShortcutsVisible(); + + // Handlers for JS functions visible to all NTPs. + static void DeleteMostVisitedItem(v8::Isolate* isolate, + v8::Local<v8::Value> rid); + static void UndoAllMostVisitedDeletions(); + static void UndoMostVisitedDeletion(v8::Isolate* isolate, + v8::Local<v8::Value> rid); + + // Handlers for JS functions visible only to the most visited iframe, the edit + // custom links iframe, and/or the local NTP. + static v8::Local<v8::Value> GetMostVisitedItemData(v8::Isolate* isolate, + int rid); + static void UpdateCustomLink(int rid, + const std::string& url, + const std::string& title); + static void ReorderCustomLink(int rid, int new_pos); + static void UndoCustomLinkAction(); + static void ResetCustomLinks(); + static void ToggleMostVisitedOrCustomLinks(); + static void ToggleShortcutsVisibility(bool do_notify); + static std::string FixupAndValidateUrl(const std::string& url); + static void LogEvent(int event); + static void LogSuggestionEventWithValue(int event, int data); + static void LogMostVisitedImpression( + int position, + int tile_title_source, + int tile_source, + int tile_type, + v8::Local<v8::Value> data_generation_time); + static void LogMostVisitedNavigation( + int position, + int tile_title_source, + int tile_source, + int tile_type, + v8::Local<v8::Value> data_generation_time); + static void ResetCustomBackgroundInfo(); + static void SetCustomBackgroundInfo(const std::string& background_url, + const std::string& attribution_line_1, + const std::string& attribution_line_2, + const std::string& attributionActionUrl, + const std::string& collection_id); + static void SelectLocalBackgroundImage(); + static void BlocklistSearchSuggestion(int task_version, int task_id); + static void BlocklistSearchSuggestionWithHash(int task_version, + int task_id, + const std::string& hash); + static void SearchSuggestionSelected(int task_version, + int task_id, + const std::string& hash); + static void OptOutOfSearchSuggestions(); + static void UseDefaultTheme(); + static void ApplyDefaultTheme(); + static void ApplyAutogeneratedTheme(v8::Isolate* isolate, + int id, + v8::Local<v8::Value> color); + static void RevertThemeChanges(); + static void ConfirmThemeChanges(); + static void BlocklistPromo(const std::string& promo_id); + static v8::Local<v8::Value> GetColorsInfo(v8::Isolate* isolate); + + DISALLOW_COPY_AND_ASSIGN(NewTabPageBindings); +}; + +gin::WrapperInfo NewTabPageBindings::kWrapperInfo = {gin::kEmbedderNativeGin}; + +NewTabPageBindings::NewTabPageBindings() = default; + +NewTabPageBindings::~NewTabPageBindings() = default; + +gin::ObjectTemplateBuilder NewTabPageBindings::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return gin::Wrappable<NewTabPageBindings>::GetObjectTemplateBuilder(isolate) + .SetProperty("isInputInProgress", &NewTabPageBindings::IsInputInProgress) + .SetProperty("mostVisited", &NewTabPageBindings::GetMostVisited) + .SetProperty("mostVisitedAvailable", + &NewTabPageBindings::GetMostVisitedAvailable) + .SetProperty("themeBackgroundInfo", + &NewTabPageBindings::GetThemeBackgroundInfo) + .SetProperty("isCustomLinks", &NewTabPageBindings::GetIsCustomLinks) + .SetProperty("isUsingMostVisited", + &NewTabPageBindings::GetIsUsingMostVisited) + .SetProperty("areShortcutsVisible", + &NewTabPageBindings::GetAreShortcutsVisible) + .SetMethod("deleteMostVisitedItem", + &NewTabPageBindings::DeleteMostVisitedItem) + .SetMethod("undoAllMostVisitedDeletions", + &NewTabPageBindings::UndoAllMostVisitedDeletions) + .SetMethod("undoMostVisitedDeletion", + &NewTabPageBindings::UndoMostVisitedDeletion) + .SetMethod("getMostVisitedItemData", + &NewTabPageBindings::GetMostVisitedItemData) + .SetMethod("updateCustomLink", &NewTabPageBindings::UpdateCustomLink) + .SetMethod("reorderCustomLink", &NewTabPageBindings::ReorderCustomLink) + .SetMethod("undoCustomLinkAction", + &NewTabPageBindings::UndoCustomLinkAction) + .SetMethod("resetCustomLinks", &NewTabPageBindings::ResetCustomLinks) + .SetMethod("toggleMostVisitedOrCustomLinks", + &NewTabPageBindings::ToggleMostVisitedOrCustomLinks) + .SetMethod("toggleShortcutsVisibility", + &NewTabPageBindings::ToggleShortcutsVisibility) + .SetMethod("fixupAndValidateUrl", + &NewTabPageBindings::FixupAndValidateUrl) + .SetMethod("logEvent", &NewTabPageBindings::LogEvent) + .SetMethod("logSuggestionEventWithValue", + &NewTabPageBindings::LogSuggestionEventWithValue) + .SetMethod("logMostVisitedImpression", + &NewTabPageBindings::LogMostVisitedImpression) + .SetMethod("logMostVisitedNavigation", + &NewTabPageBindings::LogMostVisitedNavigation) + .SetMethod("resetBackgroundInfo", + &NewTabPageBindings::ResetCustomBackgroundInfo) + .SetMethod("setBackgroundInfo", + &NewTabPageBindings::SetCustomBackgroundInfo) + .SetMethod("selectLocalBackgroundImage", + &NewTabPageBindings::SelectLocalBackgroundImage) + .SetMethod("blacklistSearchSuggestion", + &NewTabPageBindings::BlocklistSearchSuggestion) + .SetMethod("blacklistSearchSuggestionWithHash", + &NewTabPageBindings::BlocklistSearchSuggestionWithHash) + .SetMethod("searchSuggestionSelected", + &NewTabPageBindings::SearchSuggestionSelected) + .SetMethod("optOutOfSearchSuggestions", + &NewTabPageBindings::OptOutOfSearchSuggestions) + .SetMethod("useDefaultTheme", &NewTabPageBindings::UseDefaultTheme) + .SetMethod("applyDefaultTheme", &NewTabPageBindings::ApplyDefaultTheme) + .SetMethod("applyAutogeneratedTheme", + &NewTabPageBindings::ApplyAutogeneratedTheme) + .SetMethod("revertThemeChanges", &NewTabPageBindings::RevertThemeChanges) + .SetMethod("confirmThemeChanges", + &NewTabPageBindings::ConfirmThemeChanges) + .SetMethod("getColorsInfo", &NewTabPageBindings::GetColorsInfo) + .SetMethod("blocklistPromo", &NewTabPageBindings::BlocklistPromo); +} + +// static +bool NewTabPageBindings::HasOrigin(const GURL& origin) { + blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForCurrentContext(); + if (!frame) + return false; + GURL url(frame->GetDocument().Url()); + return url.GetOrigin() == origin.GetOrigin(); +} + +// static +bool NewTabPageBindings::IsInputInProgress() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return false; + return search_box->is_input_in_progress(); +} + +// static +v8::Local<v8::Value> NewTabPageBindings::GetMostVisited(v8::Isolate* isolate) { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return v8::Null(isolate); + + content::RenderFrame* render_frame = GetMainRenderFrameForCurrentContext(); + content::RenderView* render_view = render_frame->GetRenderView(); + + // This corresponds to "window.devicePixelRatio" in JavaScript. + float zoom_factor = + blink::PageZoomLevelToZoomFactor(render_view->GetZoomLevel()); + float device_pixel_ratio = render_frame->GetDeviceScaleFactor() * zoom_factor; + + int render_view_id = render_view->GetRoutingID(); + + std::vector<InstantMostVisitedItemIDPair> instant_mv_items; + search_box->GetMostVisitedItems(&instant_mv_items); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> v8_mv_items = + v8::Array::New(isolate, instant_mv_items.size()); + for (size_t i = 0; i < instant_mv_items.size(); ++i) { + InstantRestrictedID rid = instant_mv_items[i].first; + v8_mv_items + ->CreateDataProperty( + context, i, + GenerateMostVisitedItem(isolate, device_pixel_ratio, render_view_id, + rid)) + .Check(); + } + return v8_mv_items; +} + +// static +bool NewTabPageBindings::GetMostVisitedAvailable(v8::Isolate* isolate) { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return false; + + return search_box->AreMostVisitedItemsAvailable(); +} + +// static +v8::Local<v8::Value> NewTabPageBindings::GetThemeBackgroundInfo( + v8::Isolate* isolate) { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return v8::Null(isolate); + const ThemeBackgroundInfo* theme_info = search_box->GetThemeBackgroundInfo(); + if (!theme_info) + return v8::Null(isolate); + return GenerateThemeBackgroundInfo(isolate, *theme_info); +} + +// static +bool NewTabPageBindings::GetIsCustomLinks() { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) + return false; + + return search_box->IsCustomLinks(); +} + +// static +bool NewTabPageBindings::GetIsUsingMostVisited() { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !(HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)) || + HasOrigin(GURL(chrome::kChromeSearchLocalNtpUrl)))) { + return false; + } + + return search_box->IsUsingMostVisited(); +} + +// static +bool NewTabPageBindings::GetAreShortcutsVisible() { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !(HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl)) || + HasOrigin(GURL(chrome::kChromeSearchLocalNtpUrl)))) { + return true; + } + + return search_box->AreShortcutsVisible(); +} + +// static +void NewTabPageBindings::DeleteMostVisitedItem(v8::Isolate* isolate, + v8::Local<v8::Value> rid_value) { + // Manually convert to integer, so that the string "\"1\"" is also accepted. + base::Optional<int> rid = CoerceToInt(isolate, *rid_value); + if (!rid.has_value()) + return; + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + + // Treat the Most Visited item as a custom link if called from the Most + // Visited or edit custom link iframes. This will initialize custom links if + // they have not already been initialized. + if (HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) { + search_box->DeleteCustomLink(*rid); + search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_REMOVE); + } else { + search_box->DeleteMostVisitedItem(*rid); + } +} + +// static +void NewTabPageBindings::UndoAllMostVisitedDeletions() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->UndoAllMostVisitedDeletions(); +} + +// static +void NewTabPageBindings::UndoMostVisitedDeletion( + v8::Isolate* isolate, + v8::Local<v8::Value> rid_value) { + // Manually convert to integer, so that the string "\"1\"" is also accepted. + base::Optional<int> rid = CoerceToInt(isolate, *rid_value); + if (!rid.has_value()) + return; + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + + search_box->UndoMostVisitedDeletion(*rid); +} + +// static +v8::Local<v8::Value> NewTabPageBindings::GetMostVisitedItemData( + v8::Isolate* isolate, + int rid) { + const SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) + return v8::Null(isolate); + + InstantMostVisitedItem item; + if (!search_box->GetMostVisitedItemWithID(rid, &item)) + return v8::Null(isolate); + + int render_view_id = + GetMainRenderFrameForCurrentContext()->GetRenderView()->GetRoutingID(); + return GenerateMostVisitedItemData(isolate, render_view_id, rid, item); +} + +// static +void NewTabPageBindings::UpdateCustomLink(int rid, + const std::string& url, + const std::string& title) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) + return; + + // Limit the title to |kMaxCustomLinkTitleLength| characters. If truncated, + // adds an ellipsis. + base::string16 truncated_title = + gfx::TruncateString(base::UTF8ToUTF16(title), kMaxCustomLinkTitleLength, + gfx::CHARACTER_BREAK); + + const GURL gurl(url); + // If rid is -1, adds a new link. Otherwise, updates the existing link + // indicated by the rid (empty fields will passed as empty strings). This will + // initialize custom links if they have not already been initialized. + if (rid == -1) { + if (!gurl.is_valid() || truncated_title.empty()) + return; + search_box->AddCustomLink(gurl, base::UTF16ToUTF8(truncated_title)); + search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_ADD); + } else { + // Check that the URL, if provided, is valid. + if (!url.empty() && !gurl.is_valid()) + return; + search_box->UpdateCustomLink(rid, gurl, base::UTF16ToUTF8(truncated_title)); + search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_UPDATE); + } +} + +// static +void NewTabPageBindings::ReorderCustomLink(int rid, int new_pos) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) + return; + search_box->ReorderCustomLink(rid, new_pos); +} + +// static +void NewTabPageBindings::UndoCustomLinkAction() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->UndoCustomLinkAction(); + search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_UNDO); +} + +// static +void NewTabPageBindings::ResetCustomLinks() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->ResetCustomLinks(); + search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_RESTORE_ALL); +} + +// static +void NewTabPageBindings::ToggleMostVisitedOrCustomLinks() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->ToggleMostVisitedOrCustomLinks(); + search_box->LogEvent(NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_TOGGLE_TYPE); +} + +// static +void NewTabPageBindings::ToggleShortcutsVisibility(bool do_notify) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->ToggleShortcutsVisibility(do_notify); + search_box->LogEvent( + NTPLoggingEventType::NTP_CUSTOMIZE_SHORTCUT_TOGGLE_VISIBILITY); +} + +// static +std::string NewTabPageBindings::FixupAndValidateUrl(const std::string& url) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) + return std::string(); + return search_box->FixupAndValidateUrl(url); +} + +// static +void NewTabPageBindings::LogEvent(int event) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) { + return; + } + if (event <= NTP_EVENT_TYPE_LAST) + search_box->LogEvent(static_cast<NTPLoggingEventType>(event)); +} + +// static +void NewTabPageBindings::LogSuggestionEventWithValue(int event, int data) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) { + return; + } + if (event <= static_cast<int>(NTPSuggestionsLoggingEventType::kMaxValue)) { + search_box->LogSuggestionEventWithValue( + static_cast<NTPSuggestionsLoggingEventType>(event), data); + } +} + +// static +void NewTabPageBindings::LogMostVisitedImpression( + int position, + int tile_title_source, + int tile_source, + int tile_type, + v8::Local<v8::Value> data_generation_time) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) + return; + + if (tile_title_source <= static_cast<int>(ntp_tiles::TileTitleSource::LAST) && + tile_source <= static_cast<int>(ntp_tiles::TileSource::LAST) && + tile_type <= ntp_tiles::TileVisualType::TILE_TYPE_MAX) { + const ntp_tiles::NTPTileImpression impression( + position, static_cast<ntp_tiles::TileSource>(tile_source), + static_cast<ntp_tiles::TileTitleSource>(tile_title_source), + static_cast<ntp_tiles::TileVisualType>(tile_type), + favicon_base::IconType::kInvalid, + ConvertDateValueToTime(*data_generation_time), + /*url_for_rappor=*/GURL()); + search_box->LogMostVisitedImpression(impression); + } +} + +// static +void NewTabPageBindings::LogMostVisitedNavigation( + int position, + int tile_title_source, + int tile_source, + int tile_type, + v8::Local<v8::Value> data_generation_time) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !HasOrigin(GURL(chrome::kChromeSearchMostVisitedUrl))) + return; + + if (tile_title_source <= static_cast<int>(ntp_tiles::TileTitleSource::LAST) && + tile_source <= static_cast<int>(ntp_tiles::TileSource::LAST) && + tile_type <= ntp_tiles::TileVisualType::TILE_TYPE_MAX) { + const ntp_tiles::NTPTileImpression impression( + position, static_cast<ntp_tiles::TileSource>(tile_source), + static_cast<ntp_tiles::TileTitleSource>(tile_title_source), + static_cast<ntp_tiles::TileVisualType>(tile_type), + favicon_base::IconType::kInvalid, + ConvertDateValueToTime(*data_generation_time), + /*url_for_rappor=*/GURL()); + search_box->LogMostVisitedNavigation(impression); + } +} + +// static +void NewTabPageBindings::ResetCustomBackgroundInfo() { + SetCustomBackgroundInfo(std::string(), std::string(), std::string(), + std::string(), std::string()); +} + +// static +void NewTabPageBindings::SetCustomBackgroundInfo( + const std::string& background_url, + const std::string& attribution_line_1, + const std::string& attribution_line_2, + const std::string& attribution_action_url, + const std::string& collection_id) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + search_box->SetCustomBackgroundInfo( + GURL(background_url), attribution_line_1, attribution_line_2, + GURL(attribution_action_url), collection_id); + // Captures different events that occur when a background selection is made + // and 'Done' is clicked on the dialog. + if (!collection_id.empty()) { + search_box->LogEvent( + NTPLoggingEventType::NTP_BACKGROUND_DAILY_REFRESH_ENABLED); + } else if (background_url.empty()) { + search_box->LogEvent( + NTPLoggingEventType::NTP_CUSTOMIZE_RESTORE_BACKGROUND_CLICKED); + search_box->LogEvent(NTPLoggingEventType::NTP_BACKGROUND_IMAGE_RESET); + } else { + search_box->LogEvent( + NTPLoggingEventType::NTP_CUSTOMIZE_CHROME_BACKGROUND_DONE); + search_box->LogEvent(NTPLoggingEventType::NTP_BACKGROUND_IMAGE_SET); + } +} + +// static +void NewTabPageBindings::SelectLocalBackgroundImage() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + search_box->SelectLocalBackgroundImage(); +} + +// static +void NewTabPageBindings::BlocklistSearchSuggestion(const int task_version, + const int task_id) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->BlocklistSearchSuggestion(task_version, task_id); +} + +// static +void NewTabPageBindings::BlocklistSearchSuggestionWithHash( + int task_version, + int task_id, + const std::string& hash) { + if (hash.length() != 4) { + return; + } + + std::vector<uint8_t> data(hash.begin(), hash.end()); + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->BlocklistSearchSuggestionWithHash(task_version, task_id, data); +} + +// static +void NewTabPageBindings::SearchSuggestionSelected(int task_version, + int task_id, + const std::string& hash) { + if (hash.length() > 4) { + return; + } + + std::vector<uint8_t> data(hash.begin(), hash.end()); + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->SearchSuggestionSelected(task_version, task_id, data); +} + +// static +void NewTabPageBindings::OptOutOfSearchSuggestions() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->OptOutOfSearchSuggestions(); +} + +// static +void NewTabPageBindings::ApplyDefaultTheme() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->ApplyDefaultTheme(); + content::RenderThread::Get()->RecordAction( + base::UserMetricsAction("ChromeColors_DefaultApplied")); +} + +// static +void NewTabPageBindings::UseDefaultTheme() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->ApplyDefaultTheme(); + search_box->ConfirmThemeChanges(); + content::RenderThread::Get()->RecordAction( + base::UserMetricsAction("ChromeColors_ThemeUninstalled")); +} + +// static +void NewTabPageBindings::ApplyAutogeneratedTheme(v8::Isolate* isolate, + int id, + v8::Local<v8::Value> value) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box || !value->IsArray()) + return; + SkColor color; + if (ArrayToSkColor(isolate, value.As<v8::Array>(), &color)) { + search_box->ApplyAutogeneratedTheme(color); + content::RenderThread::Get()->RecordAction( + base::UserMetricsAction("ChromeColors_ColorApplied")); + if (id > 0 && id < static_cast<int>(chrome_colors::kNumColorsInfo)) { + UMA_HISTOGRAM_ENUMERATION("ChromeColors.AppliedColor", id, + chrome_colors::kNumColorsInfo); + } + } +} + +// static +void NewTabPageBindings::RevertThemeChanges() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->RevertThemeChanges(); +} + +// static +void NewTabPageBindings::ConfirmThemeChanges() { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->ConfirmThemeChanges(); +} + +v8::Local<v8::Value> NewTabPageBindings::GetColorsInfo(v8::Isolate* isolate) { + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> v8_colors = + v8::Array::New(isolate, chrome_colors::kNumColorsInfo); + int i = 0; + for (chrome_colors::ColorInfo color_info : + chrome_colors::kGeneratedColorsInfo) { + v8::Local<v8::Object> v8_color_info = + gin::DataObjectBuilder(isolate) + .Set("id", color_info.id) + .Set("color", SkColorToArray(isolate, color_info.color)) + .Set("label", l10n_util::GetStringUTF16(color_info.label_id)) + .Set("icon", std::string(color_info.icon_data)) + .Build(); + v8_colors->CreateDataProperty(context, i++, v8_color_info).Check(); + } + return v8_colors; +} + +void NewTabPageBindings::BlocklistPromo(const std::string& promo_id) { + SearchBox* search_box = GetSearchBoxForCurrentContext(); + if (!search_box) + return; + search_box->BlocklistPromo(promo_id); +} + +} // namespace + +// static +void SearchBoxExtension::Install(blink::WebLocalFrame* frame) { + v8::Isolate* isolate = blink::MainThreadIsolate(); + v8::HandleScope handle_scope(isolate); + v8::Local<v8::Context> context = frame->MainWorldScriptContext(); + if (context.IsEmpty()) + return; + + v8::Context::Scope context_scope(context); + + gin::Handle<SearchBoxBindings> searchbox_controller = + gin::CreateHandle(isolate, new SearchBoxBindings()); + if (searchbox_controller.IsEmpty()) + return; + + gin::Handle<NewTabPageBindings> newtabpage_controller = + gin::CreateHandle(isolate, new NewTabPageBindings()); + if (newtabpage_controller.IsEmpty()) + return; + + v8::Local<v8::Object> chrome = + content::GetOrCreateChromeObject(isolate, context); + v8::Local<v8::Object> embedded_search = v8::Object::New(isolate); + embedded_search + ->Set(context, gin::StringToV8(isolate, "searchBox"), + searchbox_controller.ToV8()) + .ToChecked(); + embedded_search + ->Set(context, gin::StringToV8(isolate, "newTabPage"), + newtabpage_controller.ToV8()) + .ToChecked(); + chrome + ->Set(context, gin::StringToSymbol(isolate, "embeddedSearch"), + embedded_search) + .ToChecked(); +} + +// static +void SearchBoxExtension::DispatchFocusChange(blink::WebLocalFrame* frame) { + Dispatch(frame, kDispatchFocusChangedScript); +} + +// static +void SearchBoxExtension::DispatchAddCustomLinkResult( + blink::WebLocalFrame* frame, + bool success) { + blink::WebString script(blink::WebString::FromUTF8(base::StringPrintf( + kDispatchAddCustomLinkResult, success ? "true" : "false"))); + Dispatch(frame, script); +} + +// static +void SearchBoxExtension::DispatchUpdateCustomLinkResult( + blink::WebLocalFrame* frame, + bool success) { + blink::WebString script(blink::WebString::FromUTF8(base::StringPrintf( + kDispatchUpdateCustomLinkResult, success ? "true" : "false"))); + Dispatch(frame, script); +} + +// static +void SearchBoxExtension::DispatchDeleteCustomLinkResult( + blink::WebLocalFrame* frame, + bool success) { + blink::WebString script(blink::WebString::FromUTF8(base::StringPrintf( + kDispatchDeleteCustomLinkResult, success ? "true" : "false"))); + Dispatch(frame, script); +} + +void SearchBoxExtension::DispatchQueryAutocompleteResult( + blink::WebLocalFrame* frame, + chrome::mojom::AutocompleteResultPtr result) { + base::Value dict(base::Value::Type::DICTIONARY); + dict.SetStringKey("input", result->input); + dict.SetDoubleKey("status", static_cast<double>(result->status)); + dict.SetKey("matches", CreateAutocompleteMatches(result->matches)); + + std::string json; + base::JSONWriter::Write(dict, &json); + Dispatch(frame, blink::WebString::FromUTF8(base::StringPrintf( + kDispatchQueryAutocompleteResult, json.c_str()))); +} + +void SearchBoxExtension::DispatchDeleteAutocompleteMatchResult( + blink::WebLocalFrame* frame, + chrome::mojom::DeleteAutocompleteMatchResultPtr result) { + base::Value dict(base::Value::Type::DICTIONARY); + dict.SetBoolKey("success", result->success); + dict.SetKey("matches", CreateAutocompleteMatches(result->matches)); + + std::string json; + base::JSONWriter::Write(dict, &json); + Dispatch(frame, blink::WebString::FromUTF8(base::StringPrintf( + kDispatchDeleteAutocompleteMatchResult, json.c_str()))); +} + +// static +void SearchBoxExtension::DispatchInputCancel(blink::WebLocalFrame* frame) { + Dispatch(frame, kDispatchInputCancelScript); +} + +// static +void SearchBoxExtension::DispatchInputStart(blink::WebLocalFrame* frame) { + Dispatch(frame, kDispatchInputStartScript); +} + +// static +void SearchBoxExtension::DispatchKeyCaptureChange(blink::WebLocalFrame* frame) { + Dispatch(frame, kDispatchKeyCaptureChangeScript); +} + +// static +void SearchBoxExtension::DispatchMostVisitedChanged( + blink::WebLocalFrame* frame) { + Dispatch(frame, kDispatchMostVisitedChangedScript); +} + +// static +void SearchBoxExtension::DispatchThemeChange(blink::WebLocalFrame* frame) { + Dispatch(frame, kDispatchThemeChangeEventScript); +} + +// static +void SearchBoxExtension::DispatchLocalBackgroundSelected( + blink::WebLocalFrame* frame) { + Dispatch(frame, kDispatchLocalBackgroundSelectedScript); +} diff --git a/chromium/chrome/renderer/searchbox/searchbox_extension.h b/chromium/chrome/renderer/searchbox/searchbox_extension.h new file mode 100644 index 00000000000..5d2052e21ec --- /dev/null +++ b/chromium/chrome/renderer/searchbox/searchbox_extension.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef CHROME_RENDERER_SEARCHBOX_SEARCHBOX_EXTENSION_H_ +#define CHROME_RENDERER_SEARCHBOX_SEARCHBOX_EXTENSION_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/strings/string16.h" +#include "chrome/common/search.mojom.h" +#include "ui/gfx/color_palette.h" + +namespace blink { +class WebLocalFrame; +} + +constexpr SkColor kNTPLightLogoColor = SkColorSetRGB(238, 238, 238); +constexpr SkColor kNTPLightIconColor = gfx::kGoogleGrey100; +constexpr SkColor kNTPDarkIconColor = gfx::kGoogleGrey900; + +// Javascript bindings for the chrome.embeddedSearch APIs. See +// https://www.chromium.org/embeddedsearch. +class SearchBoxExtension { + public: + static void Install(blink::WebLocalFrame* frame); + + // Helpers to dispatch Javascript events. + static void DispatchChromeIdentityCheckResult(blink::WebLocalFrame* frame, + const base::string16& identity, + bool identity_match); + static void DispatchFocusChange(blink::WebLocalFrame* frame); + static void DispatchAddCustomLinkResult(blink::WebLocalFrame* frame, + bool success); + static void DispatchUpdateCustomLinkResult(blink::WebLocalFrame* frame, + bool success); + static void DispatchDeleteCustomLinkResult(blink::WebLocalFrame* frame, + bool success); + static void DispatchQueryAutocompleteResult( + blink::WebLocalFrame* frame, + chrome::mojom::AutocompleteResultPtr result); + static void DispatchDeleteAutocompleteMatchResult( + blink::WebLocalFrame* frame, + chrome::mojom::DeleteAutocompleteMatchResultPtr result); + static void DispatchInputCancel(blink::WebLocalFrame* frame); + static void DispatchInputStart(blink::WebLocalFrame* frame); + static void DispatchKeyCaptureChange(blink::WebLocalFrame* frame); + static void DispatchMostVisitedChanged(blink::WebLocalFrame* frame); + static void DispatchThemeChange(blink::WebLocalFrame* frame); + static void DispatchLocalBackgroundSelected(blink::WebLocalFrame* frame); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(SearchBoxExtension); +}; + +#endif // CHROME_RENDERER_SEARCHBOX_SEARCHBOX_EXTENSION_H_ diff --git a/chromium/chrome/renderer/searchbox/searchbox_unittest.cc b/chromium/chrome/renderer/searchbox/searchbox_unittest.cc new file mode 100644 index 00000000000..747fe743cc5 --- /dev/null +++ b/chromium/chrome/renderer/searchbox/searchbox_unittest.cc @@ -0,0 +1,271 @@ +// Copyright 2013 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 <stddef.h> + +#include <map> +#include <string> + +#include "base/stl_util.h" +#include "chrome/common/search/instant_types.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace { + +const char* kUrlString1 = "http://www.google.com"; +const char* kUrlString2 = "http://www.chromium.org/path/q=3#r=4"; +const char* kUrlString3 = "http://www.youtube.com:8080/hosps"; + +// Mock helper to test internal::TranslateIconRestrictedUrl(). +class MockIconURLHelper: public SearchBox::IconURLHelper { + public: + MockIconURLHelper(); + ~MockIconURLHelper() override; + int GetViewID() const override; + std::string GetURLStringFromRestrictedID(InstantRestrictedID rid) const + override; + + private: + std::map<InstantRestrictedID, std::string> rid_to_url_string_; +}; + +MockIconURLHelper::MockIconURLHelper() { + rid_to_url_string_[1] = kUrlString1; + rid_to_url_string_[2] = kUrlString2; + rid_to_url_string_[3] = kUrlString3; +} + +MockIconURLHelper::~MockIconURLHelper() { +} + +int MockIconURLHelper::GetViewID() const { + return 137; +} + +std::string MockIconURLHelper::GetURLStringFromRestrictedID( + InstantRestrictedID rid) const { + auto it = rid_to_url_string_.find(rid); + return it == rid_to_url_string_.end() ? std::string() : it->second; +} + +} // namespace + +namespace internal { + +// Defined in searchbox.cc +bool ParseViewIdAndRestrictedId(const std::string& id_part, + int* view_id_out, + InstantRestrictedID* rid_out); + +// Defined in searchbox.cc +bool ParseIconRestrictedUrl(const GURL& url, + std::string* param_part, + int* view_id, + InstantRestrictedID* rid); + +// Defined in searchbox.cc +void TranslateIconRestrictedUrl(const GURL& transient_url, + const SearchBox::IconURLHelper& helper, + GURL* url); + +// Defined in searchbox.cc +std::string FixupAndValidateUrl(const std::string& url); + +TEST(SearchBoxUtilTest, ParseViewIdAndRestrictedIdSuccess) { + int view_id = -1; + InstantRestrictedID rid = -1; + + EXPECT_TRUE(ParseViewIdAndRestrictedId("2/3", &view_id, &rid)); + EXPECT_EQ(2, view_id); + EXPECT_EQ(3, rid); + + EXPECT_TRUE(ParseViewIdAndRestrictedId("0/0", &view_id, &rid)); + EXPECT_EQ(0, view_id); + EXPECT_EQ(0, rid); + + EXPECT_TRUE(ParseViewIdAndRestrictedId("1048576/314", &view_id, &rid)); + EXPECT_EQ(1048576, view_id); + EXPECT_EQ(314, rid); + + // Odd but not fatal. + EXPECT_TRUE(ParseViewIdAndRestrictedId("00/09", &view_id, &rid)); + EXPECT_EQ(0, view_id); + EXPECT_EQ(9, rid); + + // Tolerates multiple, leading, and trailing "/". + EXPECT_TRUE(ParseViewIdAndRestrictedId("2////3", &view_id, &rid)); + EXPECT_EQ(2, view_id); + EXPECT_EQ(3, rid); + + EXPECT_TRUE(ParseViewIdAndRestrictedId("5/6/", &view_id, &rid)); + EXPECT_EQ(5, view_id); + EXPECT_EQ(6, rid); + + EXPECT_TRUE(ParseViewIdAndRestrictedId("/7/8", &view_id, &rid)); + EXPECT_EQ(7, view_id); + EXPECT_EQ(8, rid); +} + +TEST(SearchBoxUtilTest, ParseViewIdAndRestrictedIdFailure) { + const char* test_cases[] = { + "", + " ", + "/", + "2/", + "/3", + "2a/3", + "2/3a", + " 2/3", + "2/ 3", + "2 /3 ", + "23", + "2,3", + "-2/3", + "2/-3", + "2/3/1", + "blahblah", + "0xA/0x10", + }; + for (size_t i = 0; i < base::size(test_cases); ++i) { + int view_id = -1; + InstantRestrictedID rid = -1; + EXPECT_FALSE(ParseViewIdAndRestrictedId(test_cases[i], &view_id, &rid)) + << " for test_cases[" << i << "]"; + EXPECT_EQ(-1, view_id); + EXPECT_EQ(-1, rid); + } +} + +TEST(SearchBoxUtilTest, ParseIconRestrictedUrlFaviconSuccess) { + struct { + const char* transient_url_str; + const char* expected_param_part; + int expected_view_id; + InstantRestrictedID expected_rid; + } test_cases[] = { + {"chrome-search://favicon/1/2", "", 1, 2}, + {"chrome-search://favicon/size/16@2x/3/4", "size/16@2x/", 3, 4}, + {"chrome-search://favicon/iconurl/9/10", "iconurl/", 9, 10}, + }; + for (size_t i = 0; i < base::size(test_cases); ++i) { + std::string param_part = "(unwritten)"; + int view_id = -1; + InstantRestrictedID rid = -1; + EXPECT_TRUE(ParseIconRestrictedUrl(GURL(test_cases[i].transient_url_str), + ¶m_part, &view_id, &rid)) + << " for test_cases[" << i << "]"; + EXPECT_EQ(test_cases[i].expected_param_part, param_part) + << " for test_cases[" << i << "]"; + EXPECT_EQ(test_cases[i].expected_view_id, view_id) + << " for test_cases[" << i << "]"; + EXPECT_EQ(test_cases[i].expected_rid, rid) + << " for test_cases[" << i << "]"; + } +} + +TEST(SearchBoxUtilTest, ParseIconRestrictedUrlFailure) { + struct { + const char* transient_url_str; + } test_cases[] = { + {"chrome-search://favicon/"}, + {"chrome-search://favicon/3/"}, + {"chrome-search://favicon/size/3/4"}, + {"chrome-search://favicon/largest/http://www.google.com"}, + {"chrome-search://favicon/size/16@2x/-1/10"}, + }; + for (size_t i = 0; i < base::size(test_cases); ++i) { + std::string param_part = "(unwritten)"; + int view_id = -1; + InstantRestrictedID rid = -1; + EXPECT_FALSE(ParseIconRestrictedUrl(GURL(test_cases[i].transient_url_str), + ¶m_part, &view_id, &rid)) + << " for test_cases[" << i << "]"; + EXPECT_EQ("(unwritten)", param_part); + EXPECT_EQ(-1, view_id); + EXPECT_EQ(-1, rid); + } +} + +TEST(SearchBoxUtilTest, TranslateIconRestrictedUrlSuccess) { + struct { + const char* transient_url_str; + std::string expected_url_str; + } test_cases[] = { + {"chrome-search://favicon/137/1", + std::string("chrome-search://favicon/") + kUrlString1}, + {"chrome-search://favicon/", "chrome-search://favicon/"}, + {"chrome-search://favicon/314", "chrome-search://favicon/"}, + {"chrome-search://favicon/314/1", "chrome-search://favicon/"}, + {"chrome-search://favicon/137/255", "chrome-search://favicon/"}, + {"chrome-search://favicon/-3/-1", "chrome-search://favicon/"}, + {"chrome-search://favicon/invalidstuff", "chrome-search://favicon/"}, + {"chrome-search://favicon/size/16@2x/http://www.google.com", + "chrome-search://favicon/"}, + }; + + MockIconURLHelper helper; + for (size_t i = 0; i < base::size(test_cases); ++i) { + GURL url; + GURL transient_url(test_cases[i].transient_url_str); + TranslateIconRestrictedUrl(transient_url, helper, &url); + EXPECT_EQ(GURL(test_cases[i].expected_url_str), url) + << " for test_cases[" << i << "]"; + } +} + +TEST(SearchBoxUtilTest, FixupAndValidateUrlReturnsEmptyIfInvalid) { + struct TestCase { + const char* url; + bool is_valid; + } test_cases[] = { + {" ", false}, + {"^&*@)^)", false}, + {"foo", true}, + {"http://foo", true}, + {"\thttp://foo", true}, + {" http://foo", true}, + {"https://foo", true}, + {"foo.com", true}, + {"http://foo.com", true}, + {"https://foo.com", true}, + {"blob://foo", true}, + + }; + + for (const TestCase& test_case : test_cases) { + const std::string& url = FixupAndValidateUrl(test_case.url); + EXPECT_EQ(!url.empty(), test_case.is_valid) + << " for test_case '" << test_case.url << "'"; + } +} + +TEST(SearchBoxUtilTest, FixupAndValidateUrlDefaultsToHttps) { + struct TestCase { + const char* url; + const char* expected_scheme; + } test_cases[] = { + // No scheme. + {"foo.com", url::kHttpsScheme}, + // With "http". + {"http://foo.com", url::kHttpScheme}, + // With "http" and whitespace. + {"\thttp://foo", url::kHttpScheme}, + {" http://foo", url::kHttpScheme}, + // With "https". + {"https://foo.com", url::kHttpsScheme}, + // Non "http"/"https". + {"blob://foo", url::kBlobScheme}, + }; + + for (const TestCase& test_case : test_cases) { + const GURL url(FixupAndValidateUrl(test_case.url)); + EXPECT_TRUE(url.SchemeIs(test_case.expected_scheme)) + << " for test case '" << test_case.url << "'"; + } +} + +} // namespace internal |