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;
127 link.target = '_top';
128 // Include links in the tab order. The tabIndex is necessary for
132 link.textContent = text;
133 link.addEventListener('mouseover', function() {
134 var ntpApiHandle = chrome.embeddedSearch.newTabPage;
135 ntpApiHandle.logEvent(NTP_LOGGING_EVENT_TYPE.NTP_MOUSEOVER);
137 link.addEventListener('focus', function() {
138 window.parent.postMessage('linkFocused', DOMAIN_ORIGIN);
140 link.addEventListener('blur', function() {
141 window.parent.postMessage('linkBlurred', DOMAIN_ORIGIN);
144 // Webkit's security policy prevents some Most Visited thumbnails from
145 // working (those with schemes different from http and https). Therefore,
146 // navigateContentWindow is being used in order to get all schemes working.
147 var navigateFunction = function handleNavigation(e) {
148 var isServerSuggestion = 'url' in params;
150 // Ping are only populated for server-side suggestions, never for MV.
151 if (isServerSuggestion && params.ping) {
152 generatePing(DOMAIN_ORIGIN + params.ping);
155 var ntpApiHandle = chrome.embeddedSearch.newTabPage;
156 if ('pos' in params && isFinite(params.pos)) {
157 ntpApiHandle.logMostVisitedNavigation(parseInt(params.pos, 10),
161 if (!isServerSuggestion) {
163 ntpApiHandle.navigateContentWindow(href, getDispositionFromEvent(e));
165 // Else follow <a> normally, so transition type would be LINK.
168 link.addEventListener('click', navigateFunction);
169 link.addEventListener('keydown', function(event) {
170 if (event.keyCode == 46 /* DELETE */ ||
171 event.keyCode == 8 /* BACKSPACE */) {
172 event.preventDefault();
173 window.parent.postMessage('tileBlacklisted,' + params.pos, DOMAIN_ORIGIN);
174 } else if (event.keyCode == 13 /* ENTER */ ||
175 event.keyCode == 32 /* SPACE */) {
176 // Event target is the <a> tag. Send a click event on it, which will
177 // trigger the 'click' event registered above.
178 event.preventDefault();
179 event.target.click();
188 * Returns the color to display string with, depending on whether title is
189 * displayed, the current theme, and URL parameters.
190 * @param {Object<string, string>} params URL parameters specifying style.
191 * @param {boolean} isTitle if the style is for the Most Visited Title.
192 * @return {string} The color to use, in "rgba(#,#,#,#)" format.
194 function getTextColor(params, isTitle) {
195 // 'RRGGBBAA' color format overrides everything.
196 if ('c' in params && params.c.match(/^[0-9A-Fa-f]{8}$/)) {
197 // Extract the 4 pairs of hex digits, map to number, then form rgba().
198 var t = params.c.match(/(..)(..)(..)(..)/).slice(1).map(function(s) {
199 return parseInt(s, 16);
201 return 'rgba(' + t[0] + ',' + t[1] + ',' + t[2] + ',' + t[3] / 255 + ')';
204 // For backward compatibility with server-side NTP, look at themes directly
205 // and use param.c for non-title or as fallback.
206 var apiHandle = chrome.embeddedSearch.newTabPage;
207 var themeInfo = apiHandle.themeBackgroundInfo;
209 if (isTitle && themeInfo && !themeInfo.usingDefaultTheme) {
210 // Read from theme directly
211 c = convertArrayToRGBAColor(themeInfo.textColorRgba) || c;
212 } else if ('c' in params) {
213 c = convertToHexColor(parseInt(params.c, 16)) || c;
220 * Decodes most visited styles from URL parameters.
221 * - c: A hexadecimal number interpreted as a hex color code.
223 * - fs: font-size as a number in pixels.
224 * - ta: text-align property, as a string.
225 * - tf: specifying a text fade starting position, in pixels.
226 * @param {Object<string, string>} params URL parameters specifying style.
227 * @param {boolean} isTitle if the style is for the Most Visited Title.
228 * @return {Object} Styles suitable for CSS interpolation.
230 function getMostVisitedStyles(params, isTitle) {
232 color: getTextColor(params, isTitle), // Handles 'c' in params.
236 if ('f' in params && /^[-0-9a-zA-Z ,]+$/.test(params.f))
237 styles.fontFamily = params.f;
238 if ('fs' in params && isFinite(parseInt(params.fs, 10)))
239 styles.fontSize = parseInt(params.fs, 10);
240 if ('ta' in params && /^[-0-9a-zA-Z ,]+$/.test(params.ta))
241 styles.textAlign = params.ta;
242 if ('tf' in params) {
243 var tf = parseInt(params.tf, 10);
245 styles.textFadePos = tf;
252 * @param {string} location A location containing URL parameters.
253 * @param {function(Object, Object)} fill A function called with styles and
256 function fillMostVisited(location, fill) {
257 var params = parseQueryParams(location);
258 params.rid = parseInt(params.rid, 10);
259 if (!isFinite(params.rid) && !params.url)
261 // Log whether the suggestion was obtained from the server or the client.
262 chrome.embeddedSearch.newTabPage.logEvent(params.url ?
263 NTP_LOGGING_EVENT_TYPE.NTP_SERVER_SIDE_SUGGESTION :
264 NTP_LOGGING_EVENT_TYPE.NTP_CLIENT_SIDE_SUGGESTION);
267 // Means that the suggestion data comes from the server. Create data object.
270 thumbnailUrl: params.tu || '',
271 title: params.ti || '',
272 direction: params.di || '',
273 domain: params.dom || '',
274 provider: params.pr || SERVER_PROVIDER_NAME
277 var apiHandle = chrome.embeddedSearch.searchBox;
278 data = apiHandle.getMostVisitedItemData(params.rid);
281 // Allow server-side provider override.
282 data.provider = params.pr || CLIENT_PROVIDER_NAME;
284 if (isFinite(params.dummy) && parseInt(params.dummy, 10)) {
287 if (/^javascript:/i.test(data.url) ||
288 /^javascript:/i.test(data.thumbnailUrl) ||
289 !/^[a-z0-9]{0,8}$/i.test(data.provider))
292 document.body.dir = data.direction;
298 * Sends a POST request to ping url.
299 * @param {string} url URL to be pinged.
301 function generatePing(url) {
302 if (navigator.sendBeacon) {
303 navigator.sendBeacon(url);
305 // if sendBeacon is not enabled, we fallback for "a ping".
306 var a = document.createElement('a');