1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * @fileoverview Utilities for rendering most visited thumbnails and titles.
10 <include src
="instant_iframe_validation.js">
11 <include src
="window_disposition_util.js">
15 * The different types of events that are logged from the NTP. This enum is
16 * used to transfer information from the NTP javascript to the renderer and is
17 * not used as a UMA enum histogram's logged value.
18 * Note: Keep in sync with common/ntp_logging_events.h
22 var NTP_LOGGING_EVENT_TYPE
= {
23 // The suggestion is coming from the server.
24 NTP_SERVER_SIDE_SUGGESTION
: 0,
25 // The suggestion is coming from the client.
26 NTP_CLIENT_SIDE_SUGGESTION
: 1,
27 // Indicates a tile was rendered, no matter if it's a thumbnail, a gray tile
28 // or an external tile.
30 // The tile uses a local thumbnail image.
31 NTP_THUMBNAIL_TILE
: 3,
32 // Used when no thumbnail is specified and a gray tile with the domain is used
35 // The visuals of that tile are handled externally by the page itself.
37 // There was an error in loading both the thumbnail image and the fallback
38 // (if it was provided), resulting in a grey tile.
39 NTP_THUMBNAIL_ERROR
: 6,
40 // Used a gray tile with the domain as the fallback for a failed thumbnail.
41 NTP_GRAY_TILE_FALLBACK
: 7,
42 // The visuals of that tile's fallback are handled externally.
43 NTP_EXTERNAL_TILE_FALLBACK
: 8,
44 // The user moused over an NTP tile or title.
46 // A NTP Tile has finished loading (successfully or failing).
51 * Type of the impression provider for a generic client-provided suggestion.
55 var CLIENT_PROVIDER_NAME
= 'client';
58 * Type of the impression provider for a generic server-provided suggestion.
62 var SERVER_PROVIDER_NAME
= 'server';
65 * The origin of this request.
68 var DOMAIN_ORIGIN
= '{{ORIGIN}}';
71 * Parses query parameters from Location.
72 * @param {string} location The URL to generate the CSS url for.
73 * @return {Object} Dictionary containing name value pairs for URL.
75 function parseQueryParams(location
) {
76 var params
= Object
.create(null);
77 var query
= location
.search
.substring(1);
78 var vars
= query
.split('&');
79 for (var i
= 0; i
< vars
.length
; i
++) {
80 var pair
= vars
[i
].split('=');
81 var k
= decodeURIComponent(pair
[0]);
83 // Duplicate parameters are not allowed to prevent attackers who can
84 // append things to |location| from getting their parameter values to
85 // override legitimate ones.
86 return Object
.create(null);
88 params
[k
] = decodeURIComponent(pair
[1]);
96 * Creates a new most visited link element.
97 * @param {Object} params URL parameters containing styles for the link.
98 * @param {string} href The destination for the link.
99 * @param {string} title The title for the link.
100 * @param {string|undefined} text The text for the link or none.
101 * @param {string|undefined} direction The text direction.
102 * @param {string|undefined} provider A provider name (max 8 alphanumeric
103 * characters) used for logging. Undefined if suggestion is not coming from
105 * @return {HTMLAnchorElement} A new link element.
107 function createMostVisitedLink(params
, href
, title
, text
, direction
, provider
) {
108 var styles
= getMostVisitedStyles(params
, !!text
);
109 var link
= document
.createElement('a');
110 link
.style
.color
= styles
.color
;
111 link
.style
.fontSize
= styles
.fontSize
+ 'px';
112 if (styles
.fontFamily
)
113 link
.style
.fontFamily
= styles
.fontFamily
;
114 if (styles
.textAlign
)
115 link
.style
.textAlign
= styles
.textAlign
;
116 if (styles
.textFadePos
) {
117 var dir
= /^rtl$/i.test(direction
) ? 'to left' : 'to right';
118 // The fading length in pixels is passed by the caller.
119 var mask
= 'linear-gradient(' + dir
+ ', rgba(0,0,0,1), rgba(0,0,0,1) ' +
120 styles
.textFadePos
+ 'px, rgba(0,0,0,0))';
121 link
.style
.textOverflow
= 'clip';
122 link
.style
.webkitMask
= mask
;
124 if (styles
.numTitleLines
&& styles
.numTitleLines
> 1) {
125 link
.classList
.add('multiline');
130 link
.target
= '_top';
131 // Include links in the tab order. The tabIndex is necessary for
135 // Wrap text with span so ellipsis will appear at the end of multiline.
136 var spanWrap
= document
.createElement('span');
137 spanWrap
.textContent
= text
;
138 link
.appendChild(spanWrap
);
140 link
.addEventListener('mouseover', function() {
141 var ntpApiHandle
= chrome
.embeddedSearch
.newTabPage
;
142 ntpApiHandle
.logEvent(NTP_LOGGING_EVENT_TYPE
.NTP_MOUSEOVER
);
144 link
.addEventListener('focus', function() {
145 window
.parent
.postMessage('linkFocused', DOMAIN_ORIGIN
);
147 link
.addEventListener('blur', function() {
148 window
.parent
.postMessage('linkBlurred', DOMAIN_ORIGIN
);
151 // Webkit's security policy prevents some Most Visited thumbnails from
152 // working (those with schemes different from http and https). Therefore,
153 // navigateContentWindow is being used in order to get all schemes working.
154 var navigateFunction
= function handleNavigation(e
) {
155 var isServerSuggestion
= 'url' in params
;
157 // Ping are only populated for server-side suggestions, never for MV.
158 if (isServerSuggestion
&& params
.ping
) {
159 generatePing(DOMAIN_ORIGIN
+ params
.ping
);
162 var ntpApiHandle
= chrome
.embeddedSearch
.newTabPage
;
163 if ('pos' in params
&& isFinite(params
.pos
)) {
164 ntpApiHandle
.logMostVisitedNavigation(parseInt(params
.pos
, 10),
168 if (!isServerSuggestion
) {
170 ntpApiHandle
.navigateContentWindow(href
, getDispositionFromEvent(e
));
172 // Else follow <a> normally, so transition type would be LINK.
175 link
.addEventListener('click', navigateFunction
);
176 link
.addEventListener('keydown', function(event
) {
177 if (event
.keyCode
== 46 /* DELETE */ ||
178 event
.keyCode
== 8 /* BACKSPACE */) {
179 event
.preventDefault();
180 window
.parent
.postMessage('tileBlacklisted,' + params
.pos
, DOMAIN_ORIGIN
);
181 } else if (event
.keyCode
== 13 /* ENTER */ ||
182 event
.keyCode
== 32 /* SPACE */) {
183 // Event target is the <a> tag. Send a click event on it, which will
184 // trigger the 'click' event registered above.
185 event
.preventDefault();
186 event
.target
.click();
195 * Returns the color to display string with, depending on whether title is
196 * displayed, the current theme, and URL parameters.
197 * @param {Object<string>} params URL parameters specifying style.
198 * @param {boolean} isTitle if the style is for the Most Visited Title.
199 * @return {string} The color to use, in "rgba(#,#,#,#)" format.
201 function getTextColor(params
, isTitle
) {
202 // 'RRGGBBAA' color format overrides everything.
203 if ('c' in params
&& params
.c
.match(/^[0-9A-Fa-f]{8}$/)) {
204 // Extract the 4 pairs of hex digits, map to number, then form rgba().
205 var t
= params
.c
.match(/(..)(..)(..)(..)/).slice(1).map(function(s
) {
206 return parseInt(s
, 16);
208 return 'rgba(' + t
[0] + ',' + t
[1] + ',' + t
[2] + ',' + t
[3] / 255 + ')';
211 // For backward compatibility with server-side NTP, look at themes directly
212 // and use param.c for non-title or as fallback.
213 var apiHandle
= chrome
.embeddedSearch
.newTabPage
;
214 var themeInfo
= apiHandle
.themeBackgroundInfo
;
216 if (isTitle
&& themeInfo
&& !themeInfo
.usingDefaultTheme
) {
217 // Read from theme directly
218 c
= convertArrayToRGBAColor(themeInfo
.textColorRgba
) || c
;
219 } else if ('c' in params
) {
220 c
= convertToHexColor(parseInt(params
.c
, 16)) || c
;
227 * Decodes most visited styles from URL parameters.
228 * - c: A hexadecimal number interpreted as a hex color code.
230 * - fs: font-size as a number in pixels.
231 * - ta: text-align property, as a string.
232 * - tf: text fade starting position, in pixels.
233 * - ntl: number of lines in the title.
234 * @param {Object<string>} params URL parameters specifying style.
235 * @param {boolean} isTitle if the style is for the Most Visited Title.
236 * @return {Object} Styles suitable for CSS interpolation.
238 function getMostVisitedStyles(params
, isTitle
) {
240 color
: getTextColor(params
, isTitle
), // Handles 'c' in params.
244 if ('f' in params
&& /^[-0-9a-zA-Z ,]+$/.test(params
.f
))
245 styles
.fontFamily
= params
.f
;
246 if ('fs' in params
&& isFinite(parseInt(params
.fs
, 10)))
247 styles
.fontSize
= parseInt(params
.fs
, 10);
248 if ('ta' in params
&& /^[-0-9a-zA-Z ,]+$/.test(params
.ta
))
249 styles
.textAlign
= params
.ta
;
250 if ('tf' in params
) {
251 var tf
= parseInt(params
.tf
, 10);
253 styles
.textFadePos
= tf
;
255 if ('ntl' in params
) {
256 var ntl
= parseInt(params
.ntl
, 10);
258 styles
.numTitleLines
= ntl
;
265 * @param {string} location A location containing URL parameters.
266 * @param {function(Object, Object)} fill A function called with styles and
269 function fillMostVisited(location
, fill
) {
270 var params
= parseQueryParams(location
);
271 params
.rid
= parseInt(params
.rid
, 10);
272 if (!isFinite(params
.rid
) && !params
.url
)
274 // Log whether the suggestion was obtained from the server or the client.
275 chrome
.embeddedSearch
.newTabPage
.logEvent(params
.url
?
276 NTP_LOGGING_EVENT_TYPE
.NTP_SERVER_SIDE_SUGGESTION
:
277 NTP_LOGGING_EVENT_TYPE
.NTP_CLIENT_SIDE_SUGGESTION
);
280 // Means that the suggestion data comes from the server. Create data object.
283 largeIconUrl
: params
.liu
|| '',
284 thumbnailUrl
: params
.tu
|| '',
285 title
: params
.ti
|| '',
286 direction
: params
.di
|| '',
287 domain
: params
.dom
|| '',
288 provider
: params
.pr
|| SERVER_PROVIDER_NAME
291 var apiHandle
= chrome
.embeddedSearch
.searchBox
;
292 data
= apiHandle
.getMostVisitedItemData(params
.rid
);
295 // Allow server-side provider override.
296 data
.provider
= params
.pr
|| CLIENT_PROVIDER_NAME
;
299 if (isFinite(params
.dummy
) && parseInt(params
.dummy
, 10)) {
302 if (/^javascript:/i.test(data
.url
) ||
303 /^javascript:/i.test(data
.thumbnailUrl
) ||
304 !/^[a-z0-9]{0,8}$/i.test(data
.provider
))
307 document
.body
.dir
= data
.direction
;
313 * Sends a POST request to ping url.
314 * @param {string} url URL to be pinged.
316 function generatePing(url
) {
317 if (navigator
.sendBeacon
) {
318 navigator
.sendBeacon(url
);
320 // if sendBeacon is not enabled, we fallback for "a ping".
321 var a
= document
.createElement('a');