blob: 4ca1e0ae1831de9cbf5eb703fc420106d797f2a0 [file] [log] [blame]
// 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/string_escape.h"
#include "base/macros.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/instant_types.h"
#include "chrome/common/search/ntp_logging_events.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/constants.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/common/page_zoom.h"
#include "content/public/renderer/chrome_object_extensions_utils.h"
#include "content/public/renderer/render_frame.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/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/resource/resource_bundle.h"
#include "ui/base/window_open_disposition.h"
#include "ui/events/keycodes/keyboard_codes.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 internal { // for testing.
// Returns an array with the RGBA color components.
v8::Local<v8::Value> RGBAColorToArray(v8::Isolate* isolate,
const RGBAColor& 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, color.r))
.Check();
color_array->CreateDataProperty(context, 1, v8::Int32::New(isolate, color.g))
.Check();
color_array->CreateDataProperty(context, 2, v8::Int32::New(isolate, color.b))
.Check();
color_array->CreateDataProperty(context, 3, v8::Int32::New(isolate, color.a))
.Check();
return color_array;
}
} // namespace internal
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();
}
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);
// True if dark mode should be applied to the NTP.
// Value is always valid.
builder.Set("usingDarkMode", theme_info.using_dark_mode);
// Theme color for background as an array with the RGBA components in order.
// Value is always valid.
builder.Set("backgroundColorRgba",
internal::RGBAColorToArray(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",
internal::RGBAColorToArray(isolate, theme_info.text_color));
// Theme color for links as an array with the RGBA components in order.
// Value is always valid.
builder.Set("linkColorRgba",
internal::RGBAColorToArray(isolate, theme_info.link_color));
// Theme color for light text as an array with the RGBA components in order.
// Value is always valid.
builder.Set("textColorLightRgba",
internal::RGBAColorToArray(isolate, theme_info.text_color_light));
// Theme color for header as an array with the RGBA components in order.
// Value is always valid.
builder.Set("headerColorRgba",
internal::RGBAColorToArray(isolate, theme_info.header_color));
// Theme color for section border as an array with the RGBA components in
// order. Value is always valid.
builder.Set(
"sectionBorderColorRgba",
internal::RGBAColorToArray(isolate, theme_info.section_border_color));
// The theme alternate logo value indicates a white logo 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 (crx_file::id_util::IdIsValid(theme_info.theme_id)) {
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()));
}
}
// Assume that a custom background has not been configured and then
// override based on the condition below.
builder.Set("customBackgroundConfigured", false);
// If a custom background has been set provide the relevant information to the
// page.
if (!theme_info.custom_background_url.is_empty()) {
builder.Set("alternateLogo", true);
RGBAColor whiteTextRgba = RGBAColor{255, 255, 255, 255};
builder.Set("textColorRgba",
internal::RGBAColorToArray(isolate, whiteTextRgba));
builder.Set("customBackgroundConfigured", true);
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);
// Clear the theme attribution url, as it shouldn't be shown when
// a custom background is set.
builder.Set("attributionUrl", std::string());
}
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);
}
static const char kDispatchChromeIdentityCheckResult[] =
"if (window.chrome &&"
" window.chrome.embeddedSearch &&"
" window.chrome.embeddedSearch.newTabPage &&"
" window.chrome.embeddedSearch.newTabPage.onsignedincheckdone &&"
" typeof window.chrome.embeddedSearch.newTabPage"
" .onsignedincheckdone === 'function') {"
" window.chrome.embeddedSearch.newTabPage.onsignedincheckdone(%s, %s);"
" true;"
"}";
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 kDispatchHistorySyncCheckResult[] =
"if (window.chrome &&"
" window.chrome.embeddedSearch &&"
" window.chrome.embeddedSearch.newTabPage &&"
" window.chrome.embeddedSearch.newTabPage.onhistorysynccheckdone &&"
" typeof window.chrome.embeddedSearch.newTabPage"
" .onhistorysynccheckdone === 'function') {"
" window.chrome.embeddedSearch.newTabPage.onhistorysynccheckdone(%s);"
" 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 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;"
"}";
// ----------------------------------------------------------------------------
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 GetRightToLeft();
static bool IsFocused();
static bool IsKeyCaptureEnabled();
// Handlers for JS functions.
static void Paste(const std::string& text);
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", &SearchBoxBindings::GetRightToLeft)
.SetProperty("isFocused", &SearchBoxBindings::IsFocused)
.SetProperty("isKeyCaptureEnabled",
&SearchBoxBindings::IsKeyCaptureEnabled)
.SetMethod("paste", &SearchBoxBindings::Paste)
.SetMethod("startCapturingKeyStrokes",
&SearchBoxBindings::StartCapturingKeyStrokes)
.SetMethod("stopCapturingKeyStrokes",
&SearchBoxBindings::StopCapturingKeyStrokes);
}
bool SearchBoxBindings::GetRightToLeft() {
return base::i18n::IsRTL();
}
// 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::Paste(const std::string& text) {
SearchBox* search_box = GetSearchBoxForCurrentContext();
if (!search_box)
return;
search_box->Paste(base::UTF8ToUTF16(text));
}
// 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();
// Handlers for JS functions visible to all NTPs.
static void CheckIsUserSignedInToChromeAs(const std::string& identity);
static void CheckIsUserSyncingHistory();
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 std::string FixupAndValidateUrl(const std::string& url);
static void LogEvent(int event);
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 SetCustomBackgroundURL(const std::string& background_url);
static void SetCustomBackgroundURLWithAttributions(
const std::string& background_url,
const std::string& attribution_line_1,
const std::string& attribution_line_2,
const std::string& attributionActionUrl);
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();
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)
.SetMethod("checkIsUserSignedIntoChromeAs",
&NewTabPageBindings::CheckIsUserSignedInToChromeAs)
.SetMethod("checkIsUserSyncingHistory",
&NewTabPageBindings::CheckIsUserSyncingHistory)
.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("fixupAndValidateUrl",
&NewTabPageBindings::FixupAndValidateUrl)
.SetMethod("logEvent", &NewTabPageBindings::LogEvent)
.SetMethod("logMostVisitedImpression",
&NewTabPageBindings::LogMostVisitedImpression)
.SetMethod("logMostVisitedNavigation",
&NewTabPageBindings::LogMostVisitedNavigation)
.SetMethod("setBackgroundURL",
&NewTabPageBindings::SetCustomBackgroundURL)
.SetMethod("setBackgroundURLWithAttributions",
&NewTabPageBindings::SetCustomBackgroundURLWithAttributions)
.SetMethod("selectLocalBackgroundImage",
&NewTabPageBindings::SelectLocalBackgroundImage)
.SetMethod("blacklistSearchSuggestion",
&NewTabPageBindings::BlocklistSearchSuggestion)
.SetMethod("blacklistSearchSuggestionWithHash",
&NewTabPageBindings::BlocklistSearchSuggestionWithHash)
.SetMethod("searchSuggestionSelected",
&NewTabPageBindings::SearchSuggestionSelected)
.SetMethod("optOutOfSearchSuggestions",
&NewTabPageBindings::OptOutOfSearchSuggestions);
}
// 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::RenderView* render_view =
GetMainRenderFrameForCurrentContext()->GetRenderView();
// This corresponds to "window.devicePixelRatio" in JavaScript.
float zoom_factor =
content::ZoomLevelToZoomFactor(render_view->GetZoomLevel());
float device_pixel_ratio = render_view->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();
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
void NewTabPageBindings::CheckIsUserSignedInToChromeAs(
const std::string& identity) {
SearchBox* search_box = GetSearchBoxForCurrentContext();
if (!search_box)
return;
search_box->CheckIsUserSignedInToChromeAs(base::UTF8ToUTF16(identity));
}
// static
void NewTabPageBindings::CheckIsUserSyncingHistory() {
SearchBox* search_box = GetSearchBoxForCurrentContext();
if (!search_box)
return;
search_box->CheckIsUserSyncingHistory();
}
// 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
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::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::SetCustomBackgroundURL(
const std::string& background_url) {
SearchBox* search_box = GetSearchBoxForCurrentContext();
GURL url(background_url);
search_box->SetCustomBackgroundURL(url);
}
// static
void NewTabPageBindings::SetCustomBackgroundURLWithAttributions(
const std::string& background_url,
const std::string& attribution_line_1,
const std::string& attribution_line_2,
const std::string& attribution_action_url) {
SearchBox* search_box = GetSearchBoxForCurrentContext();
search_box->SetCustomBackgroundURLWithAttributions(
GURL(background_url), attribution_line_1, attribution_line_2,
GURL(attribution_action_url));
// Captures saving the background by double-clicking, or clicking 'Done'.
search_box->LogEvent(
NTPLoggingEventType::NTP_CUSTOMIZE_CHROME_BACKGROUND_DONE);
}
// 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();
}
} // 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::DispatchChromeIdentityCheckResult(
blink::WebLocalFrame* frame,
const base::string16& identity,
bool identity_match) {
std::string escaped_identity = base::GetQuotedJSONString(identity);
blink::WebString script(blink::WebString::FromUTF8(base::StringPrintf(
kDispatchChromeIdentityCheckResult, escaped_identity.c_str(),
identity_match ? "true" : "false")));
Dispatch(frame, script);
}
// static
void SearchBoxExtension::DispatchFocusChange(blink::WebLocalFrame* frame) {
Dispatch(frame, kDispatchFocusChangedScript);
}
// static
void SearchBoxExtension::DispatchHistorySyncCheckResult(
blink::WebLocalFrame* frame,
bool sync_history) {
blink::WebString script(blink::WebString::FromUTF8(base::StringPrintf(
kDispatchHistorySyncCheckResult, sync_history ? "true" : "false")));
Dispatch(frame, script);
}
// 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);
}
// 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);
}