Home | History | Annotate | Download | only in local_ntp
      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