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. 4 5 6 /** 7 * @fileoverview Utilities for rendering most visited thumbnails and titles. 8 */ 9 10 <include src="instant_iframe_validation.js"> 11 <include src="window_disposition_util.js"> 12 13 14 /** 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 19 * @enum {number} 20 * @const 21 */ 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. 29 NTP_TILE: 2, 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 33 // as the main tile. 34 NTP_GRAY_TILE: 4, 35 // The visuals of that tile are handled externally by the page itself. 36 NTP_EXTERNAL_TILE: 5, 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. 45 NTP_MOUSEOVER: 9 46 }; 47 48 /** 49 * Type of the impression provider for a generic client-provided suggestion. 50 * @type {string} 51 * @const 52 */ 53 var CLIENT_PROVIDER_NAME = 'client'; 54 55 /** 56 * Type of the impression provider for a generic server-provided suggestion. 57 * @type {string} 58 * @const 59 */ 60 var SERVER_PROVIDER_NAME = 'server'; 61 62 /** 63 * The origin of this request. 64 * @const {string} 65 */ 66 var DOMAIN_ORIGIN = '{{ORIGIN}}'; 67 68 /** 69 * Parses query parameters from Location. 70 * @param {string} location The URL to generate the CSS url for. 71 * @return {Object} Dictionary containing name value pairs for URL. 72 */ 73 function parseQueryParams(location) { 74 var params = Object.create(null); 75 var query = location.search.substring(1); 76 var vars = query.split('&'); 77 for (var i = 0; i < vars.length; i++) { 78 var pair = vars[i].split('='); 79 var k = decodeURIComponent(pair[0]); 80 if (k in params) { 81 // Duplicate parameters are not allowed to prevent attackers who can 82 // append things to |location| from getting their parameter values to 83 // override legitimate ones. 84 return Object.create(null); 85 } else { 86 params[k] = decodeURIComponent(pair[1]); 87 } 88 } 89 return params; 90 } 91 92 93 /** 94 * Creates a new most visited link element. 95 * @param {Object} params URL parameters containing styles for the link. 96 * @param {string} href The destination for the link. 97 * @param {string} title The title for the link. 98 * @param {string|undefined} text The text for the link or none. 99 * @param {string|undefined} direction The text direction. 100 * @param {string|undefined} provider A provider name (max 8 alphanumeric 101 * characters) used for logging. Undefined if suggestion is not coming from 102 * the server. 103 * @return {HTMLAnchorElement} A new link element. 104 */ 105 function createMostVisitedLink(params, href, title, text, direction, provider) { 106 var styles = getMostVisitedStyles(params, !!text); 107 var link = document.createElement('a'); 108 link.style.color = styles.color; 109 link.style.fontSize = styles.fontSize + 'px'; 110 if (styles.fontFamily) 111 link.style.fontFamily = styles.fontFamily; 112 if (styles.textAlign) 113 link.style.textAlign = styles.textAlign; 114 if (styles.textFadePos) { 115 var dir = /^rtl$/i.test(direction) ? 'to left' : 'to right'; 116 // The fading length in pixels is passed by the caller. 117 var mask = 'linear-gradient(' + dir + ', rgba(0,0,0,1), rgba(0,0,0,1) ' + 118 styles.textFadePos + 'px, rgba(0,0,0,0))'; 119 link.style.textOverflow = 'clip'; 120 link.style.webkitMask = mask; 121 } 122 123 link.href = href; 124 link.title = title; 125 link.target = '_top'; 126 // Include links in the tab order. The tabIndex is necessary for 127 // accessibility. 128 link.tabIndex = '0'; 129 if (text) 130 link.textContent = text; 131 link.addEventListener('mouseover', function() { 132 var ntpApiHandle = chrome.embeddedSearch.newTabPage; 133 ntpApiHandle.logEvent(NTP_LOGGING_EVENT_TYPE.NTP_MOUSEOVER); 134 }); 135 link.addEventListener('focus', function() { 136 window.parent.postMessage('linkFocused', DOMAIN_ORIGIN); 137 }); 138 link.addEventListener('blur', function() { 139 window.parent.postMessage('linkBlurred', DOMAIN_ORIGIN); 140 }); 141 142 // Webkit's security policy prevents some Most Visited thumbnails from 143 // working (those with schemes different from http and https). Therefore, 144 // navigateContentWindow is being used in order to get all schemes working. 145 var navigateFunction = function handleNavigation(e) { 146 var isServerSuggestion = 'url' in params; 147 148 // Ping are only populated for server-side suggestions, never for MV. 149 if (isServerSuggestion && params.ping) { 150 generatePing(DOMAIN_ORIGIN + params.ping); 151 } 152 153 var ntpApiHandle = chrome.embeddedSearch.newTabPage; 154 if ('pos' in params && isFinite(params.pos)) { 155 ntpApiHandle.logMostVisitedNavigation(parseInt(params.pos, 10), 156 provider || ''); 157 } 158 159 if (!isServerSuggestion) { 160 e.preventDefault(); 161 ntpApiHandle.navigateContentWindow(href, getDispositionFromEvent(e)); 162 } 163 // Else follow <a> normally, so transition type would be LINK. 164 }; 165 166 link.addEventListener('click', navigateFunction); 167 link.addEventListener('keydown', function(event) { 168 if (event.keyCode == 46 /* DELETE */ || 169 event.keyCode == 8 /* BACKSPACE */) { 170 event.preventDefault(); 171 window.parent.postMessage('tileBlacklisted,' + params.pos, DOMAIN_ORIGIN); 172 } else if (event.keyCode == 13 /* ENTER */ || 173 event.keyCode == 32 /* SPACE */) { 174 navigateFunction(event); 175 } 176 }); 177 178 return link; 179 } 180 181 182 /** 183 * Returns the color to display string with, depending on whether title is 184 * displayed, the current theme, and URL parameters. 185 * @param {Object.<string, string>} params URL parameters specifying style. 186 * @param {boolean} isTitle if the style is for the Most Visited Title. 187 * @return {string} The color to use, in "rgba(#,#,#,#)" format. 188 */ 189 function getTextColor(params, isTitle) { 190 // 'RRGGBBAA' color format overrides everything. 191 if ('c' in params && params.c.match(/^[0-9A-Fa-f]{8}$/)) { 192 // Extract the 4 pairs of hex digits, map to number, then form rgba(). 193 var t = params.c.match(/(..)(..)(..)(..)/).slice(1).map(function(s) { 194 return parseInt(s, 16); 195 }); 196 return 'rgba(' + t[0] + ',' + t[1] + ',' + t[2] + ',' + t[3] / 255 + ')'; 197 } 198 199 // For backward compatibility with server-side NTP, look at themes directly 200 // and use param.c for non-title or as fallback. 201 var apiHandle = chrome.embeddedSearch.newTabPage; 202 var themeInfo = apiHandle.themeBackgroundInfo; 203 var c = '#777'; 204 if (isTitle && themeInfo && !themeInfo.usingDefaultTheme) { 205 // Read from theme directly 206 c = convertArrayToRGBAColor(themeInfo.textColorRgba) || c; 207 } else if ('c' in params) { 208 c = convertToHexColor(parseInt(params.c, 16)) || c; 209 } 210 return c; 211 } 212 213 214 /** 215 * Decodes most visited styles from URL parameters. 216 * - c: A hexadecimal number interpreted as a hex color code. 217 * - f: font-family. 218 * - fs: font-size as a number in pixels. 219 * - ta: text-align property, as a string. 220 * - tf: specifying a text fade starting position, in pixels. 221 * @param {Object.<string, string>} params URL parameters specifying style. 222 * @param {boolean} isTitle if the style is for the Most Visited Title. 223 * @return {Object} Styles suitable for CSS interpolation. 224 */ 225 function getMostVisitedStyles(params, isTitle) { 226 var styles = { 227 color: getTextColor(params, isTitle), // Handles 'c' in params. 228 fontFamily: '', 229 fontSize: 11 230 }; 231 if ('f' in params && /^[-0-9a-zA-Z ,]+$/.test(params.f)) 232 styles.fontFamily = params.f; 233 if ('fs' in params && isFinite(parseInt(params.fs, 10))) 234 styles.fontSize = parseInt(params.fs, 10); 235 if ('ta' in params && /^[-0-9a-zA-Z ,]+$/.test(params.ta)) 236 styles.textAlign = params.ta; 237 if ('tf' in params) { 238 var tf = parseInt(params.tf, 10); 239 if (isFinite(tf)) 240 styles.textFadePos = tf; 241 } 242 return styles; 243 } 244 245 246 /** 247 * @param {string} location A location containing URL parameters. 248 * @param {function(Object, Object)} fill A function called with styles and 249 * data to fill. 250 */ 251 function fillMostVisited(location, fill) { 252 var params = parseQueryParams(location); 253 params.rid = parseInt(params.rid, 10); 254 if (!isFinite(params.rid) && !params.url) 255 return; 256 // Log whether the suggestion was obtained from the server or the client. 257 chrome.embeddedSearch.newTabPage.logEvent(params.url ? 258 NTP_LOGGING_EVENT_TYPE.NTP_SERVER_SIDE_SUGGESTION : 259 NTP_LOGGING_EVENT_TYPE.NTP_CLIENT_SIDE_SUGGESTION); 260 var data = {}; 261 if (params.url) { 262 // Means that the suggestion data comes from the server. Create data object. 263 data.url = params.url; 264 data.thumbnailUrl = params.tu || ''; 265 data.title = params.ti || ''; 266 data.direction = params.di || ''; 267 data.domain = params.dom || ''; 268 data.provider = params.pr || SERVER_PROVIDER_NAME; 269 270 // Log the fact that suggestion was obtained from the server. 271 var ntpApiHandle = chrome.embeddedSearch.newTabPage; 272 ntpApiHandle.logEvent(NTP_LOGGING_EVENT_TYPE.NTP_SERVER_SIDE_SUGGESTION); 273 } else { 274 var apiHandle = chrome.embeddedSearch.searchBox; 275 data = apiHandle.getMostVisitedItemData(params.rid); 276 if (!data) 277 return; 278 // Allow server-side provider override. 279 data.provider = params.pr || CLIENT_PROVIDER_NAME; 280 } 281 if (/^javascript:/i.test(data.url) || 282 /^javascript:/i.test(data.thumbnailUrl) || 283 !/^[a-z0-9]{0,8}$/i.test(data.provider)) 284 return; 285 if (data.direction) 286 document.body.dir = data.direction; 287 fill(params, data); 288 } 289 290 291 /** 292 * Sends a POST request to ping url. 293 * @param {string} url URL to be pinged. 294 */ 295 function generatePing(url) { 296 if (navigator.sendBeacon) { 297 navigator.sendBeacon(url); 298 } else { 299 // if sendBeacon is not enabled, we fallback for "a ping". 300 var a = document.createElement('a'); 301 a.href = '#'; 302 a.ping = url; 303 a.click(); 304 } 305 } 306