summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/resources/local_ntp/most_visited_util.js
blob: ecb48210b40d68a2246b4734df6fa5ba4a7cd13d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// 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.


/**
 * @fileoverview Utilities for rendering most visited thumbnails and titles.
 */

<include src="instant_iframe_validation.js">
<include src="window_disposition_util.js">


/**
 * The different types of events that are logged from the NTP.  This enum is
 * used to transfer information from the NTP javascript to the renderer and is
 * not used as a UMA enum histogram's logged value.
 * Note: Keep in sync with common/ntp_logging_events.h
 * @enum {number}
 * @const
 */
var NTP_LOGGING_EVENT_TYPE = {
  // The suggestion is coming from the server.
  NTP_SERVER_SIDE_SUGGESTION: 0,
  // The suggestion is coming from the client.
  NTP_CLIENT_SIDE_SUGGESTION: 1,
  // Indicates a tile was rendered, no matter if it's a thumbnail, a gray tile
  // or an external tile.
  NTP_TILE: 2,
  // The tile uses a local thumbnail image.
  NTP_THUMBNAIL_TILE: 3,
  // Used when no thumbnail is specified and a gray tile with the domain is used
  // as the main tile.
  NTP_GRAY_TILE: 4,
  // The visuals of that tile are handled externally by the page itself.
  NTP_EXTERNAL_TILE: 5,
  // There was an error in loading both the thumbnail image and the fallback
  // (if it was provided), resulting in a grey tile.
  NTP_THUMBNAIL_ERROR: 6,
  // Used a gray tile with the domain as the fallback for a failed thumbnail.
  NTP_GRAY_TILE_FALLBACK: 7,
  // The visuals of that tile's fallback are handled externally.
  NTP_EXTERNAL_TILE_FALLBACK: 8,
  // The user moused over an NTP tile or title.
  NTP_MOUSEOVER: 9
};

/**
 * Type of the impression provider for a generic client-provided suggestion.
 * @type {string}
 * @const
 */
var CLIENT_PROVIDER_NAME = 'client';

/**
 * Type of the impression provider for a generic server-provided suggestion.
 * @type {string}
 * @const
 */
var SERVER_PROVIDER_NAME = 'server';

/**
 * The origin of this request.
 * @const {string}
 */
var DOMAIN_ORIGIN = '{{ORIGIN}}';

/**
 * Parses query parameters from Location.
 * @param {string} location The URL to generate the CSS url for.
 * @return {Object} Dictionary containing name value pairs for URL.
 */
function parseQueryParams(location) {
  var params = Object.create(null);
  var query = location.search.substring(1);
  var vars = query.split('&');
  for (var i = 0; i < vars.length; i++) {
    var pair = vars[i].split('=');
    var k = decodeURIComponent(pair[0]);
    if (k in params) {
      // Duplicate parameters are not allowed to prevent attackers who can
      // append things to |location| from getting their parameter values to
      // override legitimate ones.
      return Object.create(null);
    } else {
      params[k] = decodeURIComponent(pair[1]);
    }
  }
  return params;
}


/**
 * Creates a new most visited link element.
 * @param {Object} params URL parameters containing styles for the link.
 * @param {string} href The destination for the link.
 * @param {string} title The title for the link.
 * @param {string|undefined} text The text for the link or none.
 * @param {string|undefined} direction The text direction.
 * @param {string|undefined} provider A provider name (max 8 alphanumeric
 *     characters) used for logging. Undefined if suggestion is not coming from
 *     the server.
 * @return {HTMLAnchorElement} A new link element.
 */
function createMostVisitedLink(params, href, title, text, direction, provider) {
  var styles = getMostVisitedStyles(params, !!text);
  var link = document.createElement('a');
  link.style.color = styles.color;
  link.style.fontSize = styles.fontSize + 'px';
  if (styles.fontFamily)
    link.style.fontFamily = styles.fontFamily;
  if (styles.textAlign)
    link.style.textAlign = styles.textAlign;
  if (styles.textFadePos) {
    var dir = /^rtl$/i.test(direction) ? 'to left' : 'to right';
    // The fading length in pixels is passed by the caller.
    var mask = 'linear-gradient(' + dir + ', rgba(0,0,0,1), rgba(0,0,0,1) ' +
        styles.textFadePos + 'px, rgba(0,0,0,0))';
    link.style.textOverflow = 'clip';
    link.style.webkitMask = mask;
  }

  link.href = href;
  link.title = title;
  link.target = '_top';
  // Include links in the tab order.  The tabIndex is necessary for
  // accessibility.
  link.tabIndex = '0';
  if (text)
    link.textContent = text;
  link.addEventListener('mouseover', function() {
    var ntpApiHandle = chrome.embeddedSearch.newTabPage;
    ntpApiHandle.logEvent(NTP_LOGGING_EVENT_TYPE.NTP_MOUSEOVER);
  });
  link.addEventListener('focus', function() {
    window.parent.postMessage('linkFocused', DOMAIN_ORIGIN);
  });
  link.addEventListener('blur', function() {
    window.parent.postMessage('linkBlurred', DOMAIN_ORIGIN);
  });

  // Webkit's security policy prevents some Most Visited thumbnails from
  // working (those with schemes different from http and https). Therefore,
  // navigateContentWindow is being used in order to get all schemes working.
  var navigateFunction = function handleNavigation(e) {
    var isServerSuggestion = 'url' in params;

    // Ping are only populated for server-side suggestions, never for MV.
    if (isServerSuggestion && params.ping) {
      generatePing(DOMAIN_ORIGIN + params.ping);
    }

    var ntpApiHandle = chrome.embeddedSearch.newTabPage;
    if ('pos' in params && isFinite(params.pos)) {
      ntpApiHandle.logMostVisitedNavigation(parseInt(params.pos, 10),
                                            provider || '');
    }

    if (!isServerSuggestion) {
      e.preventDefault();
      ntpApiHandle.navigateContentWindow(href, getDispositionFromEvent(e));
    }
    // Else follow <a> normally, so transition type would be LINK.
  };

  link.addEventListener('click', navigateFunction);
  link.addEventListener('keydown', function(event) {
    if (event.keyCode == 46 /* DELETE */ ||
        event.keyCode == 8 /* BACKSPACE */) {
      event.preventDefault();
      window.parent.postMessage('tileBlacklisted,' + params.pos, DOMAIN_ORIGIN);
    } else if (event.keyCode == 13 /* ENTER */ ||
               event.keyCode == 32 /* SPACE */) {
      navigateFunction(event);
    }
  });

  return link;
}


/**
 * Returns the color to display string with, depending on whether title is
 * displayed, the current theme, and URL parameters.
 * @param {Object.<string, string>} params URL parameters specifying style.
 * @param {boolean} isTitle if the style is for the Most Visited Title.
 * @return {string} The color to use, in "rgba(#,#,#,#)" format.
 */
function getTextColor(params, isTitle) {
  // 'RRGGBBAA' color format overrides everything.
  if ('c' in params && params.c.match(/^[0-9A-Fa-f]{8}$/)) {
    // Extract the 4 pairs of hex digits, map to number, then form rgba().
    var t = params.c.match(/(..)(..)(..)(..)/).slice(1).map(function(s) {
      return parseInt(s, 16);
    });
    return 'rgba(' + t[0] + ',' + t[1] + ',' + t[2] + ',' + t[3] / 255 + ')';
  }

  // For backward compatibility with server-side NTP, look at themes directly
  // and use param.c for non-title or as fallback.
  var apiHandle = chrome.embeddedSearch.newTabPage;
  var themeInfo = apiHandle.themeBackgroundInfo;
  var c = '#777';
  if (isTitle && themeInfo && !themeInfo.usingDefaultTheme) {
    // Read from theme directly
    c = convertArrayToRGBAColor(themeInfo.textColorRgba) || c;
  } else if ('c' in params) {
    c = convertToHexColor(parseInt(params.c, 16)) || c;
  }
  return c;
}


/**
 * Decodes most visited styles from URL parameters.
 * - c: A hexadecimal number interpreted as a hex color code.
 * - f: font-family.
 * - fs: font-size as a number in pixels.
 * - ta: text-align property, as a string.
 * - tf: specifying a text fade starting position, in pixels.
 * @param {Object.<string, string>} params URL parameters specifying style.
 * @param {boolean} isTitle if the style is for the Most Visited Title.
 * @return {Object} Styles suitable for CSS interpolation.
 */
function getMostVisitedStyles(params, isTitle) {
  var styles = {
    color: getTextColor(params, isTitle),  // Handles 'c' in params.
    fontFamily: '',
    fontSize: 11
  };
  if ('f' in params && /^[-0-9a-zA-Z ,]+$/.test(params.f))
    styles.fontFamily = params.f;
  if ('fs' in params && isFinite(parseInt(params.fs, 10)))
    styles.fontSize = parseInt(params.fs, 10);
  if ('ta' in params && /^[-0-9a-zA-Z ,]+$/.test(params.ta))
    styles.textAlign = params.ta;
  if ('tf' in params) {
    var tf = parseInt(params.tf, 10);
    if (isFinite(tf))
      styles.textFadePos = tf;
  }
  return styles;
}


/**
 * @param {string} location A location containing URL parameters.
 * @param {function(Object, Object)} fill A function called with styles and
 *     data to fill.
 */
function fillMostVisited(location, fill) {
  var params = parseQueryParams(location);
  params.rid = parseInt(params.rid, 10);
  if (!isFinite(params.rid) && !params.url)
    return;
  // Log whether the suggestion was obtained from the server or the client.
  chrome.embeddedSearch.newTabPage.logEvent(params.url ?
      NTP_LOGGING_EVENT_TYPE.NTP_SERVER_SIDE_SUGGESTION :
      NTP_LOGGING_EVENT_TYPE.NTP_CLIENT_SIDE_SUGGESTION);
  var data;
  if (params.url) {
    // Means that the suggestion data comes from the server. Create data object.
    data = {
      url: params.url,
      thumbnailUrl: params.tu || '',
      title: params.ti || '',
      direction: params.di || '',
      domain: params.dom || '',
      provider: params.pr || SERVER_PROVIDER_NAME
    };
    // Log the fact that suggestion was obtained from the server.
    var ntpApiHandle = chrome.embeddedSearch.newTabPage;
    ntpApiHandle.logEvent(NTP_LOGGING_EVENT_TYPE.NTP_SERVER_SIDE_SUGGESTION);
  } else {
    var apiHandle = chrome.embeddedSearch.searchBox;
    data = apiHandle.getMostVisitedItemData(params.rid);
    if (!data)
      return;
    // Allow server-side provider override.
    data.provider = params.pr || CLIENT_PROVIDER_NAME;
  }
  if (isFinite(params.dummy) && parseInt(params.dummy, 10)) {
    data.dummy = true;
  }
  if (/^javascript:/i.test(data.url) ||
      /^javascript:/i.test(data.thumbnailUrl) ||
      !/^[a-z0-9]{0,8}$/i.test(data.provider))
    return;
  if (data.direction)
    document.body.dir = data.direction;
  fill(params, data);
}


/**
 * Sends a POST request to ping url.
 * @param {string} url URL to be pinged.
 */
function generatePing(url) {
  if (navigator.sendBeacon) {
    navigator.sendBeacon(url);
  } else {
    // if sendBeacon is not enabled, we fallback for "a ping".
    var a = document.createElement('a');
    a.href = '#';
    a.ping = url;
    a.click();
  }
}