blob: c957279ac85aa2785912ca229906324f3d480dcb [file] [log] [blame]
// 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;
data.thumbnailUrl = params.tu || '';
data.title = params.ti || '';
data.direction = params.di || '';
data.domain = params.dom || '';
data.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 (/^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();
}
}