| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 | 5 |
| 6 // NOTE: If you modify this file, you also have to change its hash in | 6 // NOTE: If you modify this file, you also have to change its hash in |
| 7 // local_ntp.html and in LocalNtpSource::GetContentSecurityPolicyScriptSrc. | 7 // local_ntp.html and in LocalNtpSource::GetContentSecurityPolicyScriptSrc. |
| 8 // To calculate the sum execute the following command | 8 // To calculate the sum execute the following command |
| 9 // sha256sum local_ntp.js | cut -d " " -f 1 | xxd -r -p | base64 | 9 // sha256sum local_ntp.js | cut -d " " -f 1 | xxd -r -p | base64 |
| 10 | 10 |
| 11 | 11 |
| 12 /** | 12 /** |
| 13 * @fileoverview The local InstantExtended NTP. | 13 * @fileoverview The local InstantExtended NTP. |
| 14 */ | 14 */ |
| 15 | 15 |
| 16 | 16 |
| 17 /** | 17 /** |
| 18 * Controls rendering the new tab page for InstantExtended. | 18 * Controls rendering the new tab page for InstantExtended. |
| 19 * @return {Object} A limited interface for testing the local NTP. | 19 * @return {Object} A limited interface for testing the local NTP. |
| 20 */ | 20 */ |
| 21 function LocalNTP() { | 21 function LocalNTP() { |
| 22 'use strict'; | 22 'use strict'; |
| 23 | 23 |
| 24 | 24 |
| 25 /** | 25 /** |
| 26 * Alias for document.getElementById. | 26 * Alias for document.getElementById. |
| 27 * @param {string} id The ID of the element to find. | 27 * @param {string} id The ID of the element to find. |
| 28 * @return {HTMLElement} The found element or null if not found. | 28 * @return {HTMLElement} The found element or null if not found. |
| 29 */ | 29 */ |
| 30 function $(id) { | 30 function $(id) { |
| 31 return document.getElementById(id); | 31 return document.getElementById(id); |
| 32 } | 32 } |
| 33 | 33 |
| 34 | 34 |
| 35 /** | 35 /** |
| 36 * Specifications for an NTP design (not comprehensive). | 36 * Specifications for an NTP design (not comprehensive). |
| 37 * | 37 * |
| 38 * numTitleLines: Number of lines to display in titles. | 38 * numTitleLines: Number of lines to display in titles. |
| 39 * tileWidth: The width of each suggestion tile, in px. | 39 * tileWidth: The width of each suggestion tile, in px. |
| 40 * tileMargin: Spacing between successive tiles, in px. | 40 * tileMargin: Spacing between successive tiles, in px. |
| 41 * titleColor: The 4-component color of title text. | 41 * titleColor: The 4-component color of title text. |
| 42 * titleColorAgainstDark: The 4-component color of title text against a dark | 42 * titleColorAgainstDark: The 4-component color of title text against a dark |
| 43 * theme. | 43 * theme. |
| 44 * | 44 * |
| 45 * @type {{ | 45 * @type {{ |
| 46 * numTitleLines: number, | 46 * numTitleLines: number, |
| 47 * tileWidth: number, | 47 * tileWidth: number, |
| 48 * tileMargin: number, | 48 * tileMargin: number, |
| 49 * titleColor: string, | 49 * titleColor: string, |
| 50 * titleColorAgainstDark: string, | 50 * titleColorAgainstDark: string, |
| 51 * }} | 51 * }} |
| 52 */ | 52 */ |
| 53 var NTP_DESIGN = { | 53 var NTP_DESIGN = { |
| 54 numTitleLines: 1, | 54 numTitleLines: 1, |
| 55 tileWidth: 154, | 55 tileWidth: 154, |
| 56 tileMargin: 16, | 56 tileMargin: 16, |
| 57 titleColor: [50, 50, 50, 255], | 57 titleColor: [50, 50, 50, 255], |
| 58 titleColorAgainstDark: [210, 210, 210, 255], | 58 titleColorAgainstDark: [210, 210, 210, 255], |
| 59 }; | 59 }; |
| 60 | 60 |
| 61 | 61 |
| 62 /** | 62 /** |
| 63 * Enum for classnames. | 63 * Enum for classnames. |
| 64 * @enum {string} | 64 * @enum {string} |
| 65 * @const | 65 * @const |
| 66 */ | 66 */ |
| 67 var CLASSES = { | 67 var CLASSES = { |
| 68 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme | 68 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme |
| 69 DARK: 'dark', | 69 DARK: 'dark', |
| 70 DEFAULT_THEME: 'default-theme', | 70 DEFAULT_THEME: 'default-theme', |
| 71 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', | 71 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', |
| 72 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox | 72 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox |
| 73 // Applies drag focus style to the fakebox | 73 // Applies drag focus style to the fakebox |
| 74 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', | 74 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', |
| 75 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', | 75 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', |
| 76 HIDE_NOTIFICATION: 'mv-notice-hide', | 76 HIDE_NOTIFICATION: 'mv-notice-hide', |
| 77 LEFT_ALIGN_ATTRIBUTION: 'left-align-attribution', | 77 LEFT_ALIGN_ATTRIBUTION: 'left-align-attribution', |
| 78 // Vertically centers the most visited section for a non-Google provided page. | 78 // Vertically centers the most visited section for a non-Google provided |
| 79 NON_GOOGLE_PAGE: 'non-google-page', | 79 // page. |
| 80 RTL: 'rtl' // Right-to-left language text. | 80 NON_GOOGLE_PAGE: 'non-google-page', |
| 81 }; | 81 RTL: 'rtl' // Right-to-left language text. |
| 82 | 82 }; |
| 83 | 83 |
| 84 /** | 84 |
| 85 * Enum for HTML element ids. | 85 /** |
| 86 * @enum {string} | 86 * Enum for HTML element ids. |
| 87 * @const | 87 * @enum {string} |
| 88 */ | 88 * @const |
| 89 var IDS = { | 89 */ |
| 90 ATTRIBUTION: 'attribution', | 90 var IDS = { |
| 91 ATTRIBUTION_TEXT: 'attribution-text', | 91 ATTRIBUTION: 'attribution', |
| 92 CUSTOM_THEME_STYLE: 'ct-style', | 92 ATTRIBUTION_TEXT: 'attribution-text', |
| 93 FAKEBOX: 'fakebox', | 93 CUSTOM_THEME_STYLE: 'ct-style', |
| 94 FAKEBOX_INPUT: 'fakebox-input', | 94 FAKEBOX: 'fakebox', |
| 95 FAKEBOX_TEXT: 'fakebox-text', | 95 FAKEBOX_INPUT: 'fakebox-input', |
| 96 LOGO: 'logo', | 96 FAKEBOX_TEXT: 'fakebox-text', |
| 97 NOTIFICATION: 'mv-notice', | 97 LOGO: 'logo', |
| 98 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', | 98 NOTIFICATION: 'mv-notice', |
| 99 NOTIFICATION_MESSAGE: 'mv-msg', | 99 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', |
| 100 NTP_CONTENTS: 'ntp-contents', | 100 NOTIFICATION_MESSAGE: 'mv-msg', |
| 101 RESTORE_ALL_LINK: 'mv-restore', | 101 NTP_CONTENTS: 'ntp-contents', |
| 102 TILES: 'mv-tiles', | 102 RESTORE_ALL_LINK: 'mv-restore', |
| 103 TILES_IFRAME: 'mv-single', | 103 TILES: 'mv-tiles', |
| 104 UNDO_LINK: 'mv-undo' | 104 TILES_IFRAME: 'mv-single', |
| 105 }; | 105 UNDO_LINK: 'mv-undo' |
| 106 | 106 }; |
| 107 | 107 |
| 108 /** | 108 |
| 109 * Enum for keycodes. | 109 /** |
| 110 * @enum {number} | 110 * Enum for keycodes. |
| 111 * @const | 111 * @enum {number} |
| 112 */ | 112 * @const |
| 113 var KEYCODE = { | 113 */ |
| 114 ENTER: 13 | 114 var KEYCODE = {ENTER: 13}; |
| 115 }; | 115 |
| 116 | 116 |
| 117 | 117 /** |
| 118 /** | 118 * The last blacklisted tile rid if any, which by definition should not be |
| 119 * The last blacklisted tile rid if any, which by definition should not be | 119 * filler. |
| 120 * filler. | 120 * @type {?number} |
| 121 * @type {?number} | 121 */ |
| 122 */ | 122 var lastBlacklistedTile = null; |
| 123 var lastBlacklistedTile = null; | 123 |
| 124 | 124 |
| 125 | 125 /** |
| 126 /** | 126 * Current number of tiles columns shown based on the window width, including |
| 127 * Current number of tiles columns shown based on the window width, including | 127 * those that just contain filler. |
| 128 * those that just contain filler. | 128 * @type {number} |
| 129 * @type {number} | 129 */ |
| 130 */ | 130 var numColumnsShown = 0; |
| 131 var numColumnsShown = 0; | 131 |
| 132 | 132 |
| 133 | 133 /** |
| 134 /** | 134 * The browser embeddedSearch.newTabPage object. |
| 135 * The browser embeddedSearch.newTabPage object. | 135 * @type {Object} |
| 136 * @type {Object} | 136 */ |
| 137 */ | 137 var ntpApiHandle; |
| 138 var ntpApiHandle; | 138 |
| 139 | 139 |
| 140 | 140 /** @type {number} @const */ |
| 141 /** @type {number} @const */ | 141 var MAX_NUM_TILES_TO_SHOW = 8; |
| 142 var MAX_NUM_TILES_TO_SHOW = 8; | 142 |
| 143 | 143 |
| 144 | 144 /** @type {number} @const */ |
| 145 /** @type {number} @const */ | 145 var MIN_NUM_COLUMNS = 2; |
| 146 var MIN_NUM_COLUMNS = 2; | 146 |
| 147 | 147 |
| 148 | 148 /** @type {number} @const */ |
| 149 /** @type {number} @const */ | 149 var MAX_NUM_COLUMNS = 4; |
| 150 var MAX_NUM_COLUMNS = 4; | 150 |
| 151 | 151 |
| 152 | 152 /** @type {number} @const */ |
| 153 /** @type {number} @const */ | 153 var NUM_ROWS = 2; |
| 154 var NUM_ROWS = 2; | 154 |
| 155 | 155 |
| 156 | 156 /** |
| 157 /** | 157 * Minimum total padding to give to the left and right of the most visited |
| 158 * Minimum total padding to give to the left and right of the most visited | 158 * section. Used to determine how many tiles to show. |
| 159 * section. Used to determine how many tiles to show. | 159 * @type {number} |
| 160 * @type {number} | 160 * @const |
| 161 * @const | 161 */ |
| 162 */ | 162 var MIN_TOTAL_HORIZONTAL_PADDING = 200; |
| 163 var MIN_TOTAL_HORIZONTAL_PADDING = 200; | 163 |
| 164 | 164 |
| 165 | 165 /** |
| 166 /** | 166 * Heuristic to determine whether a theme should be considered to be dark, so |
| 167 * Heuristic to determine whether a theme should be considered to be dark, so | 167 * the colors of various UI elements can be adjusted. |
| 168 * the colors of various UI elements can be adjusted. | 168 * @param {ThemeBackgroundInfo|undefined} info Theme background information. |
| 169 * @param {ThemeBackgroundInfo|undefined} info Theme background information. | 169 * @return {boolean} Whether the theme is dark. |
| 170 * @return {boolean} Whether the theme is dark. | 170 * @private |
| 171 * @private | 171 */ |
| 172 */ | 172 function getIsThemeDark(info) { |
| 173 function getIsThemeDark(info) { | 173 if (!info) |
| 174 if (!info) | 174 return false; |
| 175 return false; | 175 // Heuristic: light text implies dark theme. |
| 176 // Heuristic: light text implies dark theme. | 176 var rgba = info.textColorRgba; |
| 177 var rgba = info.textColorRgba; | 177 var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2]; |
| 178 var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2]; | 178 return luminance >= 128; |
| 179 return luminance >= 128; | 179 } |
| 180 } | 180 |
| 181 | 181 |
| 182 | 182 /** |
| 183 /** | 183 * Updates the NTP based on the current theme. |
| 184 * Updates the NTP based on the current theme. | 184 * @private |
| 185 * @private | 185 */ |
| 186 */ | 186 function renderTheme() { |
| 187 function renderTheme() { | 187 var info = ntpApiHandle.themeBackgroundInfo; |
| 188 var info = ntpApiHandle.themeBackgroundInfo; | 188 var isThemeDark = getIsThemeDark(info); |
| 189 var isThemeDark = getIsThemeDark(info); | 189 $(IDS.NTP_CONTENTS).classList.toggle(CLASSES.DARK, isThemeDark); |
| 190 $(IDS.NTP_CONTENTS).classList.toggle(CLASSES.DARK, isThemeDark); | 190 if (!info) { |
| 191 if (!info) { | 191 return; |
| 192 return; | 192 } |
| 193 } | 193 |
| 194 | 194 var background = [ |
| 195 var background = [convertToRGBAColor(info.backgroundColorRgba), | 195 convertToRGBAColor(info.backgroundColorRgba), info.imageUrl, |
| 196 info.imageUrl, | 196 info.imageTiling, info.imageHorizontalAlignment, |
| 197 info.imageTiling, | 197 info.imageVerticalAlignment |
| 198 info.imageHorizontalAlignment, | 198 ].join(' ').trim(); |
| 199 info.imageVerticalAlignment].join(' ').trim(); | 199 |
| 200 | 200 document.body.style.background = background; |
| 201 document.body.style.background = background; | 201 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo); |
| 202 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo); | 202 updateThemeAttribution(info.attributionUrl, info.imageHorizontalAlignment); |
| 203 updateThemeAttribution(info.attributionUrl, info.imageHorizontalAlignment); | 203 setCustomThemeStyle(info); |
| 204 setCustomThemeStyle(info); | 204 |
| 205 | 205 // Inform the most visited iframe of the new theme. |
| 206 // Inform the most visited iframe of the new theme. | 206 var themeinfo = {cmd: 'updateTheme'}; |
| 207 var themeinfo = {cmd: 'updateTheme'}; | 207 themeinfo.tileBorderColor = convertToRGBAColor(info.sectionBorderColorRgba); |
| 208 themeinfo.tileBorderColor = convertToRGBAColor(info.sectionBorderColorRgba); | 208 themeinfo.tileHoverBorderColor = convertToRGBAColor(info.headerColorRgba); |
| 209 themeinfo.tileHoverBorderColor = convertToRGBAColor(info.headerColorRgba); | 209 themeinfo.isThemeDark = isThemeDark; |
| 210 themeinfo.isThemeDark = isThemeDark; | 210 |
| 211 | 211 var titleColor = NTP_DESIGN.titleColor; |
| 212 var titleColor = NTP_DESIGN.titleColor; | 212 if (!info.usingDefaultTheme && info.textColorRgba) { |
| 213 if (!info.usingDefaultTheme && info.textColorRgba) { | 213 titleColor = info.textColorRgba; |
| 214 titleColor = info.textColorRgba; | 214 } else if (isThemeDark) { |
| 215 } else if (isThemeDark) { | 215 titleColor = NTP_DESIGN.titleColorAgainstDark; |
| 216 titleColor = NTP_DESIGN.titleColorAgainstDark; | 216 } |
| 217 } | 217 themeinfo.tileTitleColor = convertToRGBAColor(titleColor); |
| 218 themeinfo.tileTitleColor = convertToRGBAColor(titleColor); | 218 |
| 219 | 219 $(IDS.TILES_IFRAME).contentWindow.postMessage(themeinfo, '*'); |
| 220 $(IDS.TILES_IFRAME).contentWindow.postMessage(themeinfo, '*'); | 220 } |
| 221 } | 221 |
| 222 | 222 |
| 223 | 223 /** |
| 224 /** | 224 * Callback for embeddedSearch.newTabPage.onthemechange. |
| 225 * Callback for embeddedSearch.newTabPage.onthemechange. | 225 * @private |
| 226 * @private | 226 */ |
| 227 */ | 227 function onThemeChange() { |
| 228 function onThemeChange() { | 228 renderTheme(); |
| 229 renderTheme(); | 229 } |
| 230 } | 230 |
| 231 | 231 |
| 232 | 232 /** |
| 233 /** | 233 * Updates the NTP style according to theme. |
| 234 * Updates the NTP style according to theme. | 234 * @param {Object=} opt_themeInfo The information about the theme. If it is |
| 235 * @param {Object=} opt_themeInfo The information about the theme. If it is | 235 * omitted the style will be reverted to the default. |
| 236 * omitted the style will be reverted to the default. | 236 * @private |
| 237 * @private | 237 */ |
| 238 */ | 238 // TODO(treib): We actually never call this without a themeInfo. Should we? |
| 239 // TODO(treib): We actually never call this without a themeInfo. Should we? | 239 function setCustomThemeStyle(opt_themeInfo) { |
| 240 function setCustomThemeStyle(opt_themeInfo) { | 240 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE); |
| 241 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE); | 241 var head = document.head; |
| 242 var head = document.head; | 242 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) { |
| 243 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) { | 243 $(IDS.NTP_CONTENTS).classList.remove(CLASSES.DEFAULT_THEME); |
| 244 $(IDS.NTP_CONTENTS).classList.remove(CLASSES.DEFAULT_THEME); | 244 var themeStyle = '#attribution {' + |
| 245 var themeStyle = | 245 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + |
| 246 '#attribution {' + | 246 ';' + |
| 247 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + | 247 '}' + |
| 248 '}' + | 248 '#mv-msg {' + |
| 249 '#mv-msg {' + | 249 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' + |
| 250 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' + | 250 '}' + |
| 251 '}' + | 251 '#mv-notice-links span {' + |
| 252 '#mv-notice-links span {' + | 252 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + |
| 253 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + | 253 ';' + |
| 254 '}' + | 254 '}' + |
| 255 '#mv-notice-x {' + | 255 '#mv-notice-x {' + |
| 256 ' -webkit-filter: drop-shadow(0 0 0 ' + | 256 ' -webkit-filter: drop-shadow(0 0 0 ' + |
| 257 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' + | 257 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' + |
| 258 '}' + | 258 '}' + |
| 259 '.mv-page-ready .mv-mask {' + | 259 '.mv-page-ready .mv-mask {' + |
| 260 ' border: 1px solid ' + | 260 ' border: 1px solid ' + |
| 261 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' + | 261 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' + |
| 262 '}' + | 262 '}' + |
| 263 '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask {' + | 263 '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask
{' + |
| 264 ' border-color: ' + | 264 ' border-color: ' + |
| 265 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' + | 265 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' + |
| 266 '}'; | 266 '}'; |
| 267 | 267 |
| 268 if (customStyleElement) { | 268 if (customStyleElement) { |
| 269 customStyleElement.textContent = themeStyle; | 269 customStyleElement.textContent = themeStyle; |
| 270 } else { |
| 271 customStyleElement = document.createElement('style'); |
| 272 customStyleElement.type = 'text/css'; |
| 273 customStyleElement.id = IDS.CUSTOM_THEME_STYLE; |
| 274 customStyleElement.textContent = themeStyle; |
| 275 head.appendChild(customStyleElement); |
| 276 } |
| 277 |
| 270 } else { | 278 } else { |
| 271 customStyleElement = document.createElement('style'); | 279 $(IDS.NTP_CONTENTS).classList.add(CLASSES.DEFAULT_THEME); |
| 272 customStyleElement.type = 'text/css'; | 280 if (customStyleElement) |
| 273 customStyleElement.id = IDS.CUSTOM_THEME_STYLE; | 281 head.removeChild(customStyleElement); |
| 274 customStyleElement.textContent = themeStyle; | 282 } |
| 275 head.appendChild(customStyleElement); | 283 } |
| 276 } | 284 |
| 277 | 285 |
| 278 } else { | 286 /** |
| 279 $(IDS.NTP_CONTENTS).classList.add(CLASSES.DEFAULT_THEME); | 287 * Renders the attribution if the URL is present, otherwise hides it. |
| 280 if (customStyleElement) | 288 * @param {string} url The URL of the attribution image, if any. |
| 281 head.removeChild(customStyleElement); | 289 * @param {string} themeBackgroundAlignment The alignment of the theme |
| 282 } | 290 * background image. This is used to compute the attribution's alignment. |
| 283 } | 291 * @private |
| 284 | 292 */ |
| 285 | 293 function updateThemeAttribution(url, themeBackgroundAlignment) { |
| 286 /** | 294 if (!url) { |
| 287 * Renders the attribution if the URL is present, otherwise hides it. | 295 setAttributionVisibility_(false); |
| 288 * @param {string} url The URL of the attribution image, if any. | 296 return; |
| 289 * @param {string} themeBackgroundAlignment The alignment of the theme | 297 } |
| 290 * background image. This is used to compute the attribution's alignment. | 298 |
| 291 * @private | 299 var attribution = $(IDS.ATTRIBUTION); |
| 292 */ | 300 var attributionImage = attribution.querySelector('img'); |
| 293 function updateThemeAttribution(url, themeBackgroundAlignment) { | 301 if (!attributionImage) { |
| 294 if (!url) { | 302 attributionImage = new Image(); |
| 295 setAttributionVisibility_(false); | 303 attribution.appendChild(attributionImage); |
| 296 return; | 304 } |
| 297 } | 305 attributionImage.style.content = url; |
| 298 | 306 |
| 299 var attribution = $(IDS.ATTRIBUTION); | 307 // To avoid conflicts, place the attribution on the left for themes that |
| 300 var attributionImage = attribution.querySelector('img'); | 308 // right align their background images. |
| 301 if (!attributionImage) { | 309 attribution.classList.toggle( |
| 302 attributionImage = new Image(); | 310 CLASSES.LEFT_ALIGN_ATTRIBUTION, themeBackgroundAlignment == 'right'); |
| 303 attribution.appendChild(attributionImage); | 311 setAttributionVisibility_(true); |
| 304 } | 312 } |
| 305 attributionImage.style.content = url; | 313 |
| 306 | 314 |
| 307 // To avoid conflicts, place the attribution on the left for themes that | 315 /** |
| 308 // right align their background images. | 316 * Sets the visibility of the theme attribution. |
| 309 attribution.classList.toggle(CLASSES.LEFT_ALIGN_ATTRIBUTION, | 317 * @param {boolean} show True to show the attribution. |
| 310 themeBackgroundAlignment == 'right'); | 318 * @private |
| 311 setAttributionVisibility_(true); | 319 */ |
| 312 } | 320 function setAttributionVisibility_(show) { |
| 313 | 321 $(IDS.ATTRIBUTION).style.display = show ? '' : 'none'; |
| 314 | 322 } |
| 315 /** | 323 |
| 316 * Sets the visibility of the theme attribution. | 324 |
| 317 * @param {boolean} show True to show the attribution. | 325 /** |
| 318 * @private | 326 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)". |
| 319 */ | 327 * @param {Array<number>} color Array of rgba color components. |
| 320 function setAttributionVisibility_(show) { | 328 * @return {string} CSS color in RGBA format. |
| 321 $(IDS.ATTRIBUTION).style.display = show ? '' : 'none'; | 329 * @private |
| 322 } | 330 */ |
| 323 | 331 function convertToRGBAColor(color) { |
| 324 | 332 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + |
| 325 /** | 333 color[3] / 255 + ')'; |
| 326 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)". | 334 } |
| 327 * @param {Array<number>} color Array of rgba color components. | 335 |
| 328 * @return {string} CSS color in RGBA format. | 336 |
| 329 * @private | 337 /** |
| 330 */ | 338 * Callback for embeddedSearch.newTabPage.onmostvisitedchange. Called when the |
| 331 function convertToRGBAColor(color) { | 339 * NTP tiles are updated. |
| 332 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + | 340 */ |
| 333 color[3] / 255 + ')'; | 341 function onMostVisitedChange() { |
| 334 } | 342 reloadTiles(); |
| 335 | 343 } |
| 336 | 344 |
| 337 /** | 345 |
| 338 * Callback for embeddedSearch.newTabPage.onmostvisitedchange. Called when the | 346 /** |
| 339 * NTP tiles are updated. | 347 * Fetches new data (RIDs) from the embeddedSearch.newTabPage API and passes |
| 340 */ | 348 * them to the iframe. |
| 341 function onMostVisitedChange() { | 349 */ |
| 342 reloadTiles(); | 350 function reloadTiles() { |
| 343 } | 351 var pages = ntpApiHandle.mostVisited; |
| 344 | 352 var cmds = []; |
| 345 | 353 for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) { |
| 346 /** | 354 cmds.push({cmd: 'tile', rid: pages[i].rid}); |
| 347 * Fetches new data (RIDs) from the embeddedSearch.newTabPage API and passes | 355 } |
| 348 * them to the iframe. | 356 cmds.push({cmd: 'show', maxVisible: numColumnsShown * NUM_ROWS}); |
| 349 */ | 357 |
| 350 function reloadTiles() { | 358 $(IDS.TILES_IFRAME).contentWindow.postMessage(cmds, '*'); |
| 351 var pages = ntpApiHandle.mostVisited; | 359 } |
| 352 var cmds = []; | 360 |
| 353 for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) { | 361 |
| 354 cmds.push({cmd: 'tile', rid: pages[i].rid}); | 362 /** |
| 355 } | 363 * Shows the blacklist notification and triggers a delay to hide it. |
| 356 cmds.push({cmd: 'show', maxVisible: numColumnsShown * NUM_ROWS}); | 364 */ |
| 357 | 365 function showNotification() { |
| 358 $(IDS.TILES_IFRAME).contentWindow.postMessage(cmds, '*'); | 366 var notification = $(IDS.NOTIFICATION); |
| 359 } | 367 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); |
| 360 | 368 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); |
| 361 | 369 notification.scrollTop; |
| 362 /** | 370 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); |
| 363 * Shows the blacklist notification and triggers a delay to hide it. | 371 } |
| 364 */ | 372 |
| 365 function showNotification() { | 373 |
| 366 var notification = $(IDS.NOTIFICATION); | 374 /** |
| 367 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); | 375 * Hides the blacklist notification. |
| 368 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); | 376 */ |
| 369 notification.scrollTop; | 377 function hideNotification() { |
| 370 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); | 378 var notification = $(IDS.NOTIFICATION); |
| 371 } | 379 notification.classList.add(CLASSES.HIDE_NOTIFICATION); |
| 372 | 380 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); |
| 373 | 381 } |
| 374 /** | 382 |
| 375 * Hides the blacklist notification. | 383 |
| 376 */ | 384 /** |
| 377 function hideNotification() { | 385 * Handles a click on the notification undo link by hiding the notification |
| 378 var notification = $(IDS.NOTIFICATION); | 386 * and informing Chrome. |
| 379 notification.classList.add(CLASSES.HIDE_NOTIFICATION); | 387 */ |
| 380 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); | 388 function onUndo() { |
| 381 } | 389 hideNotification(); |
| 382 | 390 if (lastBlacklistedTile != null) { |
| 383 | 391 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile); |
| 384 /** | 392 } |
| 385 * Handles a click on the notification undo link by hiding the notification and | 393 } |
| 386 * informing Chrome. | 394 |
| 387 */ | 395 |
| 388 function onUndo() { | 396 /** |
| 389 hideNotification(); | 397 * Handles a click on the restore all notification link by hiding the |
| 390 if (lastBlacklistedTile != null) { | 398 * notification and informing Chrome. |
| 391 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile); | 399 */ |
| 392 } | 400 function onRestoreAll() { |
| 393 } | 401 hideNotification(); |
| 394 | 402 ntpApiHandle.undoAllMostVisitedDeletions(); |
| 395 | 403 } |
| 396 /** | 404 |
| 397 * Handles a click on the restore all notification link by hiding the | 405 |
| 398 * notification and informing Chrome. | 406 /** |
| 399 */ | 407 * Recomputes the number of tile columns, and width of various contents based |
| 400 function onRestoreAll() { | 408 * on the width of the window. |
| 401 hideNotification(); | 409 * @return {boolean} Whether the number of tile columns has changed. |
| 402 ntpApiHandle.undoAllMostVisitedDeletions(); | 410 */ |
| 403 } | 411 function updateContentWidth() { |
| 404 | 412 var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin; |
| 405 | 413 // If innerWidth is zero, then use the maximum snap size. |
| 406 /** | 414 var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth - |
| 407 * Recomputes the number of tile columns, and width of various contents based | 415 NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING; |
| 408 * on the width of the window. | 416 var innerWidth = window.innerWidth || maxSnapSize; |
| 409 * @return {boolean} Whether the number of tile columns has changed. | 417 // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin. |
| 410 */ | 418 var availableWidth = |
| 411 function updateContentWidth() { | 419 innerWidth + NTP_DESIGN.tileMargin - MIN_TOTAL_HORIZONTAL_PADDING; |
| 412 var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin; | 420 var newNumColumns = Math.floor(availableWidth / tileRequiredWidth); |
| 413 // If innerWidth is zero, then use the maximum snap size. | 421 newNumColumns = |
| 414 var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth - | 422 Math.max(MIN_NUM_COLUMNS, Math.min(newNumColumns, MAX_NUM_COLUMNS)); |
| 415 NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING; | 423 |
| 416 var innerWidth = window.innerWidth || maxSnapSize; | 424 if (numColumnsShown === newNumColumns) |
| 417 // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin. | 425 return false; |
| 418 var availableWidth = innerWidth + NTP_DESIGN.tileMargin - | 426 |
| 419 MIN_TOTAL_HORIZONTAL_PADDING; | 427 numColumnsShown = newNumColumns; |
| 420 var newNumColumns = Math.floor(availableWidth / tileRequiredWidth); | 428 // We add an extra pixel because rounding errors on different zooms can |
| 421 newNumColumns = | 429 // make the width shorter than it should be. |
| 422 Math.max(MIN_NUM_COLUMNS, Math.min(newNumColumns, MAX_NUM_COLUMNS)); | 430 var tilesContainerWidth = |
| 423 | 431 Math.ceil(numColumnsShown * tileRequiredWidth) + 1; |
| 424 if (numColumnsShown === newNumColumns) | 432 $(IDS.TILES).style.width = tilesContainerWidth + 'px'; |
| 425 return false; | 433 // -2 to account for border. |
| 426 | 434 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2); |
| 427 numColumnsShown = newNumColumns; | 435 $(IDS.FAKEBOX).style.width = fakeboxWidth + 'px'; |
| 428 // We add an extra pixel because rounding errors on different zooms can | 436 return true; |
| 429 // make the width shorter than it should be. | 437 } |
| 430 var tilesContainerWidth = Math.ceil(numColumnsShown * tileRequiredWidth) + 1; | 438 |
| 431 $(IDS.TILES).style.width = tilesContainerWidth + 'px'; | 439 |
| 432 // -2 to account for border. | 440 /** |
| 433 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2); | 441 * Resizes elements because the number of tile columns may need to change in |
| 434 $(IDS.FAKEBOX).style.width = fakeboxWidth + 'px'; | 442 * response to resizing. Also shows or hides extra tiles tiles according to |
| 435 return true; | 443 * the new width of the page. |
| 436 } | 444 */ |
| 437 | 445 function onResize() { |
| 438 | 446 if (updateContentWidth()) { |
| 439 /** | 447 // If the number of tile columns changes, inform the iframe. |
| 440 * Resizes elements because the number of tile columns may need to change in | 448 $(IDS.TILES_IFRAME) |
| 441 * response to resizing. Also shows or hides extra tiles tiles according to the | 449 .contentWindow.postMessage( |
| 442 * new width of the page. | 450 {cmd: 'tilesVisible', maxVisible: numColumnsShown * NUM_ROWS}, |
| 443 */ | 451 '*'); |
| 444 function onResize() { | 452 } |
| 445 if (updateContentWidth()) { | 453 } |
| 446 // If the number of tile columns changes, inform the iframe. | 454 |
| 447 $(IDS.TILES_IFRAME).contentWindow.postMessage( | 455 |
| 448 {cmd: 'tilesVisible', maxVisible: numColumnsShown * NUM_ROWS}, '*'); | 456 /** |
| 449 } | 457 * Callback for embeddedSearch.newTabPage.oninputstart. Handles new input by |
| 450 } | 458 * disposing the NTP, according to where the input was entered. |
| 451 | 459 */ |
| 452 | 460 function onInputStart() { |
| 453 /** | 461 if (isFakeboxFocused()) { |
| 454 * Callback for embeddedSearch.newTabPage.oninputstart. Handles new input by | 462 setFakeboxFocus(false); |
| 455 * disposing the NTP, according to where the input was entered. | 463 setFakeboxDragFocus(false); |
| 456 */ | 464 setFakeboxAndLogoVisibility(false); |
| 457 function onInputStart() { | 465 } |
| 458 if (isFakeboxFocused()) { | 466 } |
| 459 setFakeboxFocus(false); | 467 |
| 460 setFakeboxDragFocus(false); | 468 |
| 461 setFakeboxAndLogoVisibility(false); | 469 /** |
| 462 } | 470 * Callback for embeddedSearch.newTabPage.oninputcancel. Restores the NTP |
| 463 } | 471 * (re-enables the fakebox and unhides the logo.) |
| 464 | 472 */ |
| 465 | 473 function onInputCancel() { |
| 466 /** | 474 setFakeboxAndLogoVisibility(true); |
| 467 * Callback for embeddedSearch.newTabPage.oninputcancel. Restores the NTP | 475 } |
| 468 * (re-enables the fakebox and unhides the logo.) | 476 |
| 469 */ | 477 |
| 470 function onInputCancel() { | 478 /** |
| 471 setFakeboxAndLogoVisibility(true); | 479 * @param {boolean} focus True to focus the fakebox. |
| 472 } | 480 */ |
| 473 | 481 function setFakeboxFocus(focus) { |
| 474 | 482 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus); |
| 475 /** | 483 } |
| 476 * @param {boolean} focus True to focus the fakebox. | 484 |
| 477 */ | 485 /** |
| 478 function setFakeboxFocus(focus) { | 486 * @param {boolean} focus True to show a dragging focus on the fakebox. |
| 479 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus); | 487 */ |
| 480 } | 488 function setFakeboxDragFocus(focus) { |
| 481 | 489 document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus); |
| 482 /** | 490 } |
| 483 * @param {boolean} focus True to show a dragging focus on the fakebox. | 491 |
| 484 */ | 492 /** |
| 485 function setFakeboxDragFocus(focus) { | 493 * @return {boolean} True if the fakebox has focus. |
| 486 document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus); | 494 */ |
| 487 } | 495 function isFakeboxFocused() { |
| 488 | 496 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) || |
| 489 /** | 497 document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS); |
| 490 * @return {boolean} True if the fakebox has focus. | 498 } |
| 491 */ | 499 |
| 492 function isFakeboxFocused() { | 500 |
| 493 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) || | 501 /** |
| 494 document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS); | 502 * @param {!Event} event The click event. |
| 495 } | 503 * @return {boolean} True if the click occurred in an enabled fakebox. |
| 496 | 504 */ |
| 497 | 505 function isFakeboxClick(event) { |
| 498 /** | 506 return $(IDS.FAKEBOX).contains(event.target); |
| 499 * @param {!Event} event The click event. | 507 } |
| 500 * @return {boolean} True if the click occurred in an enabled fakebox. | 508 |
| 501 */ | 509 |
| 502 function isFakeboxClick(event) { | 510 /** |
| 503 return $(IDS.FAKEBOX).contains(event.target); | 511 * @param {boolean} show True to show the fakebox and logo. |
| 504 } | 512 */ |
| 505 | 513 function setFakeboxAndLogoVisibility(show) { |
| 506 | 514 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show); |
| 507 /** | 515 } |
| 508 * @param {boolean} show True to show the fakebox and logo. | 516 |
| 509 */ | 517 |
| 510 function setFakeboxAndLogoVisibility(show) { | 518 /** |
| 511 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show); | 519 * @param {!Element} element The element to register the handler for. |
| 512 } | 520 * @param {number} keycode The keycode of the key to register. |
| 513 | 521 * @param {!Function} handler The key handler to register. |
| 514 | 522 */ |
| 515 /** | 523 function registerKeyHandler(element, keycode, handler) { |
| 516 * @param {!Element} element The element to register the handler for. | 524 element.addEventListener('keydown', function(event) { |
| 517 * @param {number} keycode The keycode of the key to register. | 525 if (event.keyCode == keycode) |
| 518 * @param {!Function} handler The key handler to register. | 526 handler(event); |
| 519 */ | 527 }); |
| 520 function registerKeyHandler(element, keycode, handler) { | 528 } |
| 521 element.addEventListener('keydown', function(event) { | 529 |
| 522 if (event.keyCode == keycode) | 530 |
| 523 handler(event); | 531 /** |
| 524 }); | 532 * Event handler for the focus changed and blacklist messages on link |
| 525 } | 533 * elements. Used to toggle visual treatment on the tiles (depending on the |
| 526 | 534 * message). |
| 527 | 535 * @param {Event} event Event received. |
| 528 /** | 536 */ |
| 529 * Event handler for the focus changed and blacklist messages on link elements. | 537 function handlePostMessage(event) { |
| 530 * Used to toggle visual treatment on the tiles (depending on the message). | 538 var cmd = event.data.cmd; |
| 531 * @param {Event} event Event received. | 539 var args = event.data; |
| 532 */ | 540 if (cmd == 'tileBlacklisted') { |
| 533 function handlePostMessage(event) { | 541 showNotification(); |
| 534 var cmd = event.data.cmd; | 542 lastBlacklistedTile = args.tid; |
| 535 var args = event.data; | 543 |
| 536 if (cmd == 'tileBlacklisted') { | 544 ntpApiHandle.deleteMostVisitedItem(args.tid); |
| 537 showNotification(); | 545 } |
| 538 lastBlacklistedTile = args.tid; | 546 // TODO(treib): Should we also handle the 'loaded' message from the iframe |
| 539 | 547 // here? We could hide the page until it arrives, to avoid flicker. |
| 540 ntpApiHandle.deleteMostVisitedItem(args.tid); | 548 } |
| 541 } | 549 |
| 542 // TODO(treib): Should we also handle the 'loaded' message from the iframe | 550 |
| 543 // here? We could hide the page until it arrives, to avoid flicker. | 551 /** |
| 544 } | 552 * Prepares the New Tab Page by adding listeners, the most visited pages |
| 545 | 553 * section, and Google-specific elements for a Google-provided page. |
| 546 | 554 */ |
| 547 /** | 555 function init() { |
| 548 * Prepares the New Tab Page by adding listeners, the most visited pages | 556 // Hide notifications after fade out, so we can't focus on links via |
| 549 * section, and Google-specific elements for a Google-provided page. | 557 // keyboard. |
| 550 */ | 558 $(IDS.NOTIFICATION).addEventListener('transitionend', hideNotification); |
| 551 function init() { | 559 |
| 552 // Hide notifications after fade out, so we can't focus on links via keyboard. | 560 $(IDS.NOTIFICATION_MESSAGE).textContent = |
| 553 $(IDS.NOTIFICATION).addEventListener('transitionend', hideNotification); | 561 configData.translatedStrings.thumbnailRemovedNotification; |
| 554 | 562 |
| 555 $(IDS.NOTIFICATION_MESSAGE).textContent = | 563 var undoLink = $(IDS.UNDO_LINK); |
| 556 configData.translatedStrings.thumbnailRemovedNotification; | 564 undoLink.addEventListener('click', onUndo); |
| 557 | 565 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo); |
| 558 var undoLink = $(IDS.UNDO_LINK); | 566 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove; |
| 559 undoLink.addEventListener('click', onUndo); | 567 |
| 560 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo); | 568 var restoreAllLink = $(IDS.RESTORE_ALL_LINK); |
| 561 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove; | 569 restoreAllLink.addEventListener('click', onRestoreAll); |
| 562 | 570 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onRestoreAll); |
| 563 var restoreAllLink = $(IDS.RESTORE_ALL_LINK); | 571 restoreAllLink.textContent = |
| 564 restoreAllLink.addEventListener('click', onRestoreAll); | 572 configData.translatedStrings.restoreThumbnailsShort; |
| 565 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onRestoreAll); | 573 |
| 566 restoreAllLink.textContent = | 574 $(IDS.ATTRIBUTION_TEXT).textContent = |
| 567 configData.translatedStrings.restoreThumbnailsShort; | 575 configData.translatedStrings.attributionIntro; |
| 568 | 576 |
| 569 $(IDS.ATTRIBUTION_TEXT).textContent = | 577 $(IDS.NOTIFICATION_CLOSE_BUTTON) |
| 570 configData.translatedStrings.attributionIntro; | 578 .addEventListener('click', hideNotification); |
| 571 | 579 |
| 572 $(IDS.NOTIFICATION_CLOSE_BUTTON).addEventListener('click', hideNotification); | 580 window.addEventListener('resize', onResize); |
| 573 | 581 updateContentWidth(); |
| 574 window.addEventListener('resize', onResize); | 582 |
| 575 updateContentWidth(); | 583 var embeddedSearchApiHandle = window.chrome.embeddedSearch; |
| 576 | 584 |
| 577 var embeddedSearchApiHandle = window.chrome.embeddedSearch; | 585 ntpApiHandle = embeddedSearchApiHandle.newTabPage; |
| 578 | 586 ntpApiHandle.onthemechange = onThemeChange; |
| 579 ntpApiHandle = embeddedSearchApiHandle.newTabPage; | 587 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; |
| 580 ntpApiHandle.onthemechange = onThemeChange; | 588 |
| 581 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; | 589 var searchboxApiHandle = embeddedSearchApiHandle.searchBox; |
| 582 | 590 |
| 583 var searchboxApiHandle = embeddedSearchApiHandle.searchBox; | 591 if (configData.isGooglePage) { |
| 584 | 592 // Set up the fakebox (which only exists on the Google NTP). |
| 585 if (configData.isGooglePage) { | 593 ntpApiHandle.oninputstart = onInputStart; |
| 586 // Set up the fakebox (which only exists on the Google NTP). | 594 ntpApiHandle.oninputcancel = onInputCancel; |
| 587 ntpApiHandle.oninputstart = onInputStart; | 595 |
| 588 ntpApiHandle.oninputcancel = onInputCancel; | 596 if (ntpApiHandle.isInputInProgress) { |
| 589 | 597 onInputStart(); |
| 590 if (ntpApiHandle.isInputInProgress) { | 598 } |
| 591 onInputStart(); | 599 |
| 592 } | 600 $(IDS.FAKEBOX_TEXT).textContent = |
| 593 | 601 configData.translatedStrings.searchboxPlaceholder; |
| 594 $(IDS.FAKEBOX_TEXT).textContent = | 602 |
| 595 configData.translatedStrings.searchboxPlaceholder; | 603 // Listener for updating the key capture state. |
| 596 | 604 document.body.onmousedown = function(event) { |
| 597 // Listener for updating the key capture state. | 605 if (isFakeboxClick(event)) |
| 598 document.body.onmousedown = function(event) { | 606 searchboxApiHandle.startCapturingKeyStrokes(); |
| 599 if (isFakeboxClick(event)) | 607 else if (isFakeboxFocused()) |
| 600 searchboxApiHandle.startCapturingKeyStrokes(); | 608 searchboxApiHandle.stopCapturingKeyStrokes(); |
| 601 else if (isFakeboxFocused()) | 609 }; |
| 602 searchboxApiHandle.stopCapturingKeyStrokes(); | 610 searchboxApiHandle.onkeycapturechange = function() { |
| 611 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); |
| 612 }; |
| 613 var inputbox = $(IDS.FAKEBOX_INPUT); |
| 614 inputbox.onpaste = function(event) { |
| 615 event.preventDefault(); |
| 616 // Send pasted text to Omnibox. |
| 617 var text = event.clipboardData.getData('text/plain'); |
| 618 if (text) |
| 619 searchboxApiHandle.paste(text); |
| 620 }; |
| 621 inputbox.ondrop = function(event) { |
| 622 event.preventDefault(); |
| 623 var text = event.dataTransfer.getData('text/plain'); |
| 624 if (text) { |
| 625 searchboxApiHandle.paste(text); |
| 626 } |
| 627 setFakeboxDragFocus(false); |
| 628 }; |
| 629 inputbox.ondragenter = function() { |
| 630 setFakeboxDragFocus(true); |
| 631 }; |
| 632 inputbox.ondragleave = function() { |
| 633 setFakeboxDragFocus(false); |
| 634 }; |
| 635 |
| 636 // Update the fakebox style to match the current key capturing state. |
| 637 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); |
| 638 |
| 639 // Inject the OneGoogleBar loader script. It'll create a global variable |
| 640 // named "og" with the following fields: |
| 641 // .html - the main bar HTML. |
| 642 // .end_of_body_html - HTML to be inserted at the end of the body. |
| 643 var ogScript = document.createElement('script'); |
| 644 ogScript.src = 'chrome-search://local-ntp/one-google.js'; |
| 645 document.body.appendChild(ogScript); |
| 646 ogScript.onload = function() { |
| 647 injectOneGoogleBar(og.html, og.end_of_body_html); |
| 648 }; |
| 649 } else { |
| 650 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); |
| 651 } |
| 652 |
| 653 if (searchboxApiHandle.rtl) { |
| 654 $(IDS.NOTIFICATION).dir = 'rtl'; |
| 655 // Grabbing the root HTML element. |
| 656 document.documentElement.setAttribute('dir', 'rtl'); |
| 657 // Add class for setting alignments based on language directionality. |
| 658 document.documentElement.classList.add(CLASSES.RTL); |
| 659 } |
| 660 |
| 661 // Collect arguments for the most visited iframe. |
| 662 var args = []; |
| 663 |
| 664 if (searchboxApiHandle.rtl) |
| 665 args.push('rtl=1'); |
| 666 if (NTP_DESIGN.numTitleLines > 1) |
| 667 args.push('ntl=' + NTP_DESIGN.numTitleLines); |
| 668 |
| 669 args.push( |
| 670 'removeTooltip=' + |
| 671 encodeURIComponent( |
| 672 configData.translatedStrings.removeThumbnailTooltip)); |
| 673 |
| 674 // Create the most visited iframe. |
| 675 var iframe = document.createElement('iframe'); |
| 676 iframe.id = IDS.TILES_IFRAME; |
| 677 iframe.tabIndex = 1; |
| 678 iframe.src = 'chrome-search://most-visited/single.html?' + args.join('&'); |
| 679 $(IDS.TILES).appendChild(iframe); |
| 680 |
| 681 iframe.onload = function() { |
| 682 reloadTiles(); |
| 683 renderTheme(); |
| 603 }; | 684 }; |
| 604 searchboxApiHandle.onkeycapturechange = function() { | 685 |
| 605 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); | 686 window.addEventListener('message', handlePostMessage); |
| 606 }; | 687 } |
| 607 var inputbox = $(IDS.FAKEBOX_INPUT); | 688 |
| 608 inputbox.onpaste = function(event) { | 689 |
| 609 event.preventDefault(); | 690 /** |
| 610 // Send pasted text to Omnibox. | 691 * Binds event listeners. |
| 611 var text = event.clipboardData.getData('text/plain'); | 692 */ |
| 612 if (text) | 693 function listen() { |
| 613 searchboxApiHandle.paste(text); | 694 document.addEventListener('DOMContentLoaded', init); |
| 614 }; | 695 } |
| 615 inputbox.ondrop = function(event) { | 696 |
| 616 event.preventDefault(); | 697 |
| 617 var text = event.dataTransfer.getData('text/plain'); | 698 /** |
| 618 if (text) { | 699 * Injects the One Google Bar into the page. Called asynchronously, so that it |
| 619 searchboxApiHandle.paste(text); | 700 * doesn't block the main page load. |
| 620 } | 701 */ |
| 621 setFakeboxDragFocus(false); | 702 function injectOneGoogleBar(barHtml, endOfBodyHtml) { |
| 622 }; | 703 var inHeadStyle = document.createElement('link'); |
| 623 inputbox.ondragenter = function() { | 704 inHeadStyle.rel = 'stylesheet'; |
| 624 setFakeboxDragFocus(true); | 705 inHeadStyle.href = 'chrome-search://local-ntp/one-google/in-head.css'; |
| 625 }; | 706 document.head.appendChild(inHeadStyle); |
| 626 inputbox.ondragleave = function() { | 707 |
| 627 setFakeboxDragFocus(false); | 708 inHeadStyle.onload = function() { |
| 628 }; | 709 var inHeadScript = document.createElement('script'); |
| 629 | 710 inHeadScript.src = 'chrome-search://local-ntp/one-google/in-head.js'; |
| 630 // Update the fakebox style to match the current key capturing state. | 711 document.head.appendChild(inHeadScript); |
| 631 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); | 712 |
| 632 | 713 inHeadScript.onload = function() { |
| 633 // Inject the OneGoogleBar loader script. It'll create a global variable | 714 var ogElem = $('one-google'); |
| 634 // named "og" with the following fields: | 715 ogElem.innerHTML = barHtml; |
| 635 // .html - the main bar HTML. | 716 ogElem.classList.remove('hidden'); |
| 636 // .end_of_body_html - HTML to be inserted at the end of the body. | 717 |
| 637 var ogScript = document.createElement('script'); | 718 var afterBarScript = document.createElement('script'); |
| 638 ogScript.src = 'chrome-search://local-ntp/one-google.js'; | 719 afterBarScript.src = |
| 639 document.body.appendChild(ogScript); | 720 'chrome-search://local-ntp/one-google/after-bar.js'; |
| 640 ogScript.onload = function() { | 721 ogElem.parentNode.insertBefore(afterBarScript, ogElem.nextSibling); |
| 641 injectOneGoogleBar(og.html, og.end_of_body_html); | 722 |
| 642 }; | 723 afterBarScript.onload = function() { |
| 643 } else { | 724 $('one-google-end-of-body').innerHTML = endOfBodyHtml; |
| 644 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); | 725 |
| 645 } | 726 var endOfBodyScript = document.createElement('script'); |
| 646 | 727 endOfBodyScript.src = |
| 647 if (searchboxApiHandle.rtl) { | 728 'chrome-search://local-ntp/one-google/end-of-body.js'; |
| 648 $(IDS.NOTIFICATION).dir = 'rtl'; | 729 document.body.appendChild(endOfBodyScript); |
| 649 // Grabbing the root HTML element. | 730 }; |
| 650 document.documentElement.setAttribute('dir', 'rtl'); | |
| 651 // Add class for setting alignments based on language directionality. | |
| 652 document.documentElement.classList.add(CLASSES.RTL); | |
| 653 } | |
| 654 | |
| 655 // Collect arguments for the most visited iframe. | |
| 656 var args = []; | |
| 657 | |
| 658 if (searchboxApiHandle.rtl) | |
| 659 args.push('rtl=1'); | |
| 660 if (NTP_DESIGN.numTitleLines > 1) | |
| 661 args.push('ntl=' + NTP_DESIGN.numTitleLines); | |
| 662 | |
| 663 args.push('removeTooltip=' + | |
| 664 encodeURIComponent(configData.translatedStrings.removeThumbnailTooltip)); | |
| 665 | |
| 666 // Create the most visited iframe. | |
| 667 var iframe = document.createElement('iframe'); | |
| 668 iframe.id = IDS.TILES_IFRAME; | |
| 669 iframe.tabIndex = 1; | |
| 670 iframe.src = 'chrome-search://most-visited/single.html?' + args.join('&'); | |
| 671 $(IDS.TILES).appendChild(iframe); | |
| 672 | |
| 673 iframe.onload = function() { | |
| 674 reloadTiles(); | |
| 675 renderTheme(); | |
| 676 }; | |
| 677 | |
| 678 window.addEventListener('message', handlePostMessage); | |
| 679 } | |
| 680 | |
| 681 | |
| 682 /** | |
| 683 * Binds event listeners. | |
| 684 */ | |
| 685 function listen() { | |
| 686 document.addEventListener('DOMContentLoaded', init); | |
| 687 } | |
| 688 | |
| 689 | |
| 690 /** | |
| 691 * Injects the One Google Bar into the page. Called asynchronously, so that it | |
| 692 * doesn't block the main page load. | |
| 693 */ | |
| 694 function injectOneGoogleBar(barHtml, endOfBodyHtml) { | |
| 695 var inHeadStyle = document.createElement('link'); | |
| 696 inHeadStyle.rel = "stylesheet"; | |
| 697 inHeadStyle.href = 'chrome-search://local-ntp/one-google/in-head.css'; | |
| 698 document.head.appendChild(inHeadStyle); | |
| 699 | |
| 700 inHeadStyle.onload = function() { | |
| 701 var inHeadScript = document.createElement('script'); | |
| 702 inHeadScript.src = 'chrome-search://local-ntp/one-google/in-head.js'; | |
| 703 document.head.appendChild(inHeadScript); | |
| 704 | |
| 705 inHeadScript.onload = function() { | |
| 706 var ogElem = $('one-google'); | |
| 707 ogElem.innerHTML = barHtml; | |
| 708 ogElem.classList.remove('hidden'); | |
| 709 | |
| 710 var afterBarScript = document.createElement('script'); | |
| 711 afterBarScript.src = | |
| 712 'chrome-search://local-ntp/one-google/after-bar.js'; | |
| 713 ogElem.parentNode.insertBefore(afterBarScript, ogElem.nextSibling); | |
| 714 | |
| 715 afterBarScript.onload = function() { | |
| 716 $('one-google-end-of-body').innerHTML = endOfBodyHtml; | |
| 717 | |
| 718 var endOfBodyScript = document.createElement('script'); | |
| 719 endOfBodyScript.src = | |
| 720 'chrome-search://local-ntp/one-google/end-of-body.js'; | |
| 721 document.body.appendChild(endOfBodyScript); | |
| 722 }; | 731 }; |
| 723 }; | 732 }; |
| 733 } |
| 734 |
| 735 |
| 736 return { |
| 737 init: init, // Exposed for testing. |
| 738 listen: listen |
| 724 }; | 739 }; |
| 725 } | 740 } |
| 726 | 741 |
| 727 | |
| 728 return { | |
| 729 init: init, // Exposed for testing. | |
| 730 listen: listen | |
| 731 }; | |
| 732 | |
| 733 } | |
| 734 | |
| 735 if (!window.localNTPUnitTest) { | 742 if (!window.localNTPUnitTest) { |
| 736 LocalNTP().listen(); | 743 LocalNTP().listen(); |
| 737 } | 744 } |
| OLD | NEW |