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 /** | 6 /** |
7 * @fileoverview The local InstantExtended NTP. | 7 * @fileoverview The local InstantExtended NTP. |
8 */ | 8 */ |
9 | 9 |
10 | 10 |
11 /** | 11 /** |
12 * Controls rendering the new tab page for InstantExtended. | 12 * Controls rendering the new tab page for InstantExtended. |
13 * @return {Object} A limited interface for testing the local NTP. | 13 * @return {Object} A limited interface for testing the local NTP. |
14 */ | 14 */ |
15 function LocalNTP() { | 15 function LocalNTP() { |
16 'use strict'; | 16 'use strict'; |
17 | 17 |
18 | 18 |
19 /** | 19 /** |
20 * Alias for document.getElementById. | 20 * Alias for document.getElementById. |
21 * @param {string} id The ID of the element to find. | 21 * @param {string} id The ID of the element to find. |
22 * @return {HTMLElement} The found element or null if not found. | 22 * @return {HTMLElement} The found element or null if not found. |
23 */ | 23 */ |
24 function $(id) { | 24 function $(id) { |
25 return document.getElementById(id); | 25 return document.getElementById(id); |
26 } | 26 } |
27 | 27 |
28 | 28 |
29 /** | 29 /** |
30 * Specifications for an NTP design (not comprehensive). | 30 * Specifications for an NTP design (not comprehensive). |
31 * | 31 * |
32 * fakeboxWingSize: Extra distance for fakebox to extend beyond beyond the list | 32 * fakeboxWingSize: Extra distance for fakebox to extend beyond beyond the |
33 * of tiles. | 33 * list |
34 * fontFamily: Font family to use for title and thumbnail iframes. | 34 * of tiles. |
35 * fontSize: Font size to use for the iframes, in px. | 35 * fontFamily: Font family to use for title and thumbnail iframes. |
36 * mainClass: Class applied to #ntp-contents to control CSS. | 36 * fontSize: Font size to use for the iframes, in px. |
37 * numTitleLines: Number of lines to display in titles. | 37 * mainClass: Class applied to #ntp-contents to control CSS. |
38 * showFavicon: Whether to show favicon. | 38 * numTitleLines: Number of lines to display in titles. |
39 * thumbnailTextColor: The 4-component color that thumbnail iframe may use to | 39 * showFavicon: Whether to show favicon. |
40 * display text message in place of missing thumbnail. | 40 * thumbnailTextColor: The 4-component color that thumbnail iframe may use to |
41 * thumbnailFallback: (Optional) A value in THUMBNAIL_FALLBACK to specify the | 41 * display text message in place of missing thumbnail. |
42 * thumbnail fallback strategy. If unassigned, then the thumbnail.html | 42 * thumbnailFallback: (Optional) A value in THUMBNAIL_FALLBACK to specify the |
43 * iframe would handle the fallback. | 43 * thumbnail fallback strategy. If unassigned, then the thumbnail.html |
44 * tileWidth: The width of each suggestion tile, in px. | 44 * iframe would handle the fallback. |
45 * tileMargin: Spacing between successive tiles, in px. | 45 * tileWidth: The width of each suggestion tile, in px. |
46 * titleColor: The 4-component color of title text. | 46 * tileMargin: Spacing between successive tiles, in px. |
47 * titleColorAgainstDark: The 4-component color of title text against a dark | 47 * titleColor: The 4-component color of title text. |
48 * theme. | 48 * titleColorAgainstDark: The 4-component color of title text against a dark |
49 * titleTextAlign: (Optional) The alignment of title text. If unspecified, the | 49 * theme. |
50 * default value is 'center'. | 50 * titleTextAlign: (Optional) The alignment of title text. If unspecified, the |
51 * titleTextFade: (Optional) The number of pixels beyond which title | 51 * default value is 'center'. |
52 * text begins to fade. This overrides the default ellipsis style. | 52 * titleTextFade: (Optional) The number of pixels beyond which title |
53 * | 53 * text begins to fade. This overrides the default ellipsis style. |
54 * @type {{ | 54 * |
55 * fakeboxWingSize: number, | 55 * @type {{ |
56 * fontFamily: string, | 56 * fakeboxWingSize: number, |
57 * fontSize: number, | 57 * fontFamily: string, |
58 * mainClass: string, | 58 * fontSize: number, |
59 * numTitleLines: number, | 59 * mainClass: string, |
60 * showFavicon: boolean, | 60 * numTitleLines: number, |
61 * thumbnailTextColor: string, | 61 * showFavicon: boolean, |
62 * thumbnailFallback: string|null|undefined, | 62 * thumbnailTextColor: string, |
63 * tileWidth: number, | 63 * thumbnailFallback: string|null|undefined, |
64 * tileMargin: number, | 64 * tileWidth: number, |
65 * titleColor: string, | 65 * tileMargin: number, |
66 * titleColorAgainstDark: string, | 66 * titleColor: string, |
67 * titleTextAlign: string|null|undefined, | 67 * titleColorAgainstDark: string, |
68 * titleTextFade: number|null|undefined | 68 * titleTextAlign: string|null|undefined, |
69 * }} | 69 * titleTextFade: number|null|undefined |
70 */ | 70 * }} |
71 var NTP_DESIGN = { | 71 */ |
72 fakeboxWingSize: 0, | 72 var NTP_DESIGN = { |
73 fontFamily: 'arial, sans-serif', | 73 fakeboxWingSize: 0, |
74 fontSize: 12, | 74 fontFamily: 'arial, sans-serif', |
75 mainClass: 'thumb-ntp', | 75 fontSize: 12, |
76 numTitleLines: 1, | 76 mainClass: 'thumb-ntp', |
77 showFavicon: true, | 77 numTitleLines: 1, |
78 thumbnailTextColor: [50, 50, 50, 255], | 78 showFavicon: true, |
79 thumbnailFallback: 'dot', // Draw single dot. | 79 thumbnailTextColor: [50, 50, 50, 255], |
80 tileWidth: 154, | 80 thumbnailFallback: 'dot', // Draw single dot. |
81 tileMargin: 16, | 81 tileWidth: 154, |
82 titleColor: [50, 50, 50, 255], | 82 tileMargin: 16, |
83 titleColorAgainstDark: [210, 210, 210, 255], | 83 titleColor: [50, 50, 50, 255], |
84 titleTextAlign: 'inherit', | 84 titleColorAgainstDark: [210, 210, 210, 255], |
85 titleTextFade: 122 - 36 // 112px wide title with 32 pixel fade at end. | 85 titleTextAlign: 'inherit', |
86 }; | 86 titleTextFade: 122 - 36 // 112px wide title with 32 pixel fade at end. |
87 | 87 }; |
88 | 88 |
89 /** | 89 |
90 * Modifies NTP_DESIGN parameters for icon NTP. | 90 /** |
91 */ | 91 * Modifies NTP_DESIGN parameters for icon NTP. |
92 function modifyNtpDesignForIcons() { | 92 */ |
93 NTP_DESIGN.fakeboxWingSize = 132; | 93 function modifyNtpDesignForIcons() { |
94 NTP_DESIGN.mainClass = 'icon-ntp'; | 94 NTP_DESIGN.fakeboxWingSize = 132; |
95 NTP_DESIGN.numTitleLines = 2; | 95 NTP_DESIGN.mainClass = 'icon-ntp'; |
96 NTP_DESIGN.showFavicon = false; | 96 NTP_DESIGN.numTitleLines = 2; |
97 NTP_DESIGN.thumbnailFallback = null; | 97 NTP_DESIGN.showFavicon = false; |
98 NTP_DESIGN.tileWidth = 48 + 2 * 18; | 98 NTP_DESIGN.thumbnailFallback = null; |
99 NTP_DESIGN.tileMargin = 60 - 18 * 2; | 99 NTP_DESIGN.tileWidth = 48 + 2 * 18; |
100 NTP_DESIGN.titleColor = [120, 120, 120, 255]; | 100 NTP_DESIGN.tileMargin = 60 - 18 * 2; |
101 NTP_DESIGN.titleColorAgainstDark = [210, 210, 210, 255]; | 101 NTP_DESIGN.titleColor = [120, 120, 120, 255]; |
102 NTP_DESIGN.titleTextAlign = 'center'; | 102 NTP_DESIGN.titleColorAgainstDark = [210, 210, 210, 255]; |
103 delete NTP_DESIGN.titleTextFade; | 103 NTP_DESIGN.titleTextAlign = 'center'; |
104 } | 104 delete NTP_DESIGN.titleTextFade; |
105 | 105 } |
106 | 106 |
107 /** | 107 |
108 * Enum for classnames. | 108 /** |
109 * @enum {string} | 109 * Enum for classnames. |
110 * @const | 110 * @enum {string} |
111 */ | 111 * @const |
112 var CLASSES = { | 112 */ |
113 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme | 113 var CLASSES = { |
114 DARK: 'dark', | 114 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme |
115 DEFAULT_THEME: 'default-theme', | 115 DARK: 'dark', |
116 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', | 116 DEFAULT_THEME: 'default-theme', |
117 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive | 117 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', |
118 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox | 118 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive |
119 // Applies drag focus style to the fakebox | 119 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox |
120 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', | 120 // Applies drag focus style to the fakebox |
121 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', | 121 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', |
122 HIDE_NOTIFICATION: 'mv-notice-hide', | 122 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', |
123 LEFT_ALIGN_ATTRIBUTION: 'left-align-attribution', | 123 HIDE_NOTIFICATION: 'mv-notice-hide', |
124 // Vertically centers the most visited section for a non-Google provided page. | 124 LEFT_ALIGN_ATTRIBUTION: 'left-align-attribution', |
125 NON_GOOGLE_PAGE: 'non-google-page', | 125 // Vertically centers the most visited section for a non-Google provided |
126 RTL: 'rtl' // Right-to-left language text. | 126 // page. |
127 }; | 127 NON_GOOGLE_PAGE: 'non-google-page', |
128 | 128 RTL: 'rtl' // Right-to-left language text. |
129 | 129 }; |
130 /** | 130 |
131 * Enum for HTML element ids. | 131 |
132 * @enum {string} | 132 /** |
133 * @const | 133 * Enum for HTML element ids. |
134 */ | 134 * @enum {string} |
135 var IDS = { | 135 * @const |
136 ATTRIBUTION: 'attribution', | 136 */ |
137 ATTRIBUTION_TEXT: 'attribution-text', | 137 var IDS = { |
138 CUSTOM_THEME_STYLE: 'ct-style', | 138 ATTRIBUTION: 'attribution', |
139 FAKEBOX: 'fakebox', | 139 ATTRIBUTION_TEXT: 'attribution-text', |
140 FAKEBOX_INPUT: 'fakebox-input', | 140 CUSTOM_THEME_STYLE: 'ct-style', |
141 FAKEBOX_TEXT: 'fakebox-text', | 141 FAKEBOX: 'fakebox', |
142 LOGO: 'logo', | 142 FAKEBOX_INPUT: 'fakebox-input', |
143 NOTIFICATION: 'mv-notice', | 143 FAKEBOX_TEXT: 'fakebox-text', |
144 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', | 144 LOGO: 'logo', |
145 NOTIFICATION_MESSAGE: 'mv-msg', | 145 NOTIFICATION: 'mv-notice', |
146 NTP_CONTENTS: 'ntp-contents', | 146 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', |
147 RESTORE_ALL_LINK: 'mv-restore', | 147 NOTIFICATION_MESSAGE: 'mv-msg', |
148 TILES: 'mv-tiles', | 148 NTP_CONTENTS: 'ntp-contents', |
149 UNDO_LINK: 'mv-undo' | 149 RESTORE_ALL_LINK: 'mv-restore', |
150 }; | 150 TILES: 'mv-tiles', |
151 | 151 UNDO_LINK: 'mv-undo' |
152 | 152 }; |
153 /** | 153 |
154 * Enum for keycodes. | 154 |
155 * @enum {number} | 155 /** |
156 * @const | 156 * Enum for keycodes. |
157 */ | 157 * @enum {number} |
158 var KEYCODE = { | 158 * @const |
159 ENTER: 13 | 159 */ |
160 }; | 160 var KEYCODE = {ENTER: 13}; |
161 | 161 |
162 | 162 |
163 /** | 163 /** |
164 * Enum for the state of the NTP when it is disposed. | 164 * Enum for the state of the NTP when it is disposed. |
165 * @enum {number} | 165 * @enum {number} |
166 * @const | 166 * @const |
167 */ | 167 */ |
168 var NTP_DISPOSE_STATE = { | 168 var NTP_DISPOSE_STATE = { |
169 NONE: 0, // Preserve the NTP appearance and functionality | 169 NONE: 0, // Preserve the NTP appearance and functionality |
170 DISABLE_FAKEBOX: 1, | 170 DISABLE_FAKEBOX: 1, |
171 HIDE_FAKEBOX_AND_LOGO: 2 | 171 HIDE_FAKEBOX_AND_LOGO: 2 |
172 }; | 172 }; |
173 | 173 |
174 | 174 |
175 /** | 175 /** |
176 * The notification displayed when a page is blacklisted. | 176 * The notification displayed when a page is blacklisted. |
177 * @type {Element} | 177 * @type {Element} |
178 */ | 178 */ |
179 var notification; | 179 var notification; |
180 | 180 |
181 | 181 |
182 /** | 182 /** |
183 * The container for the theme attribution. | 183 * The container for the theme attribution. |
184 * @type {Element} | 184 * @type {Element} |
185 */ | 185 */ |
186 var attribution; | 186 var attribution; |
187 | 187 |
188 | 188 |
189 /** | 189 /** |
190 * The "fakebox" - an input field that looks like a regular searchbox. When it | 190 * The "fakebox" - an input field that looks like a regular searchbox. When |
191 * is focused, any text the user types goes directly into the omnibox. | 191 * it |
192 * @type {Element} | 192 * is focused, any text the user types goes directly into the omnibox. |
193 */ | 193 * @type {Element} |
194 var fakebox; | 194 */ |
195 | 195 var fakebox; |
196 | 196 |
197 /** | 197 |
198 * The container for NTP elements. | 198 /** |
199 * @type {Element} | 199 * The container for NTP elements. |
200 */ | 200 * @type {Element} |
201 var ntpContents; | 201 */ |
202 | 202 var ntpContents; |
203 | 203 |
204 /** | 204 |
205 * The last blacklisted tile rid if any, which by definition should not be | 205 /** |
206 * filler. | 206 * The last blacklisted tile rid if any, which by definition should not be |
207 * @type {?number} | 207 * filler. |
208 */ | 208 * @type {?number} |
209 var lastBlacklistedTile = null; | 209 */ |
210 | 210 var lastBlacklistedTile = null; |
211 | 211 |
212 /** | 212 |
213 * Current number of tiles columns shown based on the window width, including | 213 /** |
214 * those that just contain filler. | 214 * Current number of tiles columns shown based on the window width, including |
215 * @type {number} | 215 * those that just contain filler. |
216 */ | 216 * @type {number} |
217 var numColumnsShown = 0; | 217 */ |
218 | 218 var numColumnsShown = 0; |
219 | 219 |
220 /** | 220 |
221 * The browser embeddedSearch.newTabPage object. | 221 /** |
222 * @type {Object} | 222 * The browser embeddedSearch.newTabPage object. |
223 */ | 223 * @type {Object} |
224 var ntpApiHandle; | 224 */ |
225 | 225 var ntpApiHandle; |
226 | 226 |
227 /** | 227 |
228 * The browser embeddedSearch.searchBox object. | 228 /** |
229 * @type {Object} | 229 * The browser embeddedSearch.searchBox object. |
230 */ | 230 * @type {Object} |
231 var searchboxApiHandle; | 231 */ |
232 | 232 var searchboxApiHandle; |
233 | 233 |
234 /** | 234 |
235 * The state of the NTP when a query is entered into the Omnibox. | 235 /** |
236 * @type {NTP_DISPOSE_STATE} | 236 * The state of the NTP when a query is entered into the Omnibox. |
237 */ | 237 * @type {NTP_DISPOSE_STATE} |
238 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE; | 238 */ |
239 | 239 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE; |
240 | 240 |
241 /** | 241 |
242 * The state of the NTP when a query is entered into the Fakebox. | 242 /** |
243 * @type {NTP_DISPOSE_STATE} | 243 * The state of the NTP when a query is entered into the Fakebox. |
244 */ | 244 * @type {NTP_DISPOSE_STATE} |
245 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO; | 245 */ |
246 | 246 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO; |
247 | 247 |
248 /** @type {number} @const */ | 248 |
249 var MAX_NUM_TILES_TO_SHOW = 8; | 249 /** @type {number} @const */ |
250 | 250 var MAX_NUM_TILES_TO_SHOW = 8; |
251 | 251 |
252 /** @type {number} @const */ | 252 |
253 var MIN_NUM_COLUMNS = 2; | 253 /** @type {number} @const */ |
254 | 254 var MIN_NUM_COLUMNS = 2; |
255 | 255 |
256 /** @type {number} @const */ | 256 |
257 var MAX_NUM_COLUMNS = 4; | 257 /** @type {number} @const */ |
258 | 258 var MAX_NUM_COLUMNS = 4; |
259 | 259 |
260 /** @type {number} @const */ | 260 |
261 var NUM_ROWS = 2; | 261 /** @type {number} @const */ |
262 | 262 var NUM_ROWS = 2; |
263 | 263 |
264 /** | 264 |
265 * Minimum total padding to give to the left and right of the most visited | 265 /** |
266 * section. Used to determine how many tiles to show. | 266 * Minimum total padding to give to the left and right of the most visited |
267 * @type {number} | 267 * section. Used to determine how many tiles to show. |
268 * @const | 268 * @type {number} |
269 */ | 269 * @const |
270 var MIN_TOTAL_HORIZONTAL_PADDING = 200; | 270 */ |
271 | 271 var MIN_TOTAL_HORIZONTAL_PADDING = 200; |
272 | 272 |
273 /** | 273 |
| 274 /** |
274 * Heuristic to determine whether a theme should be considered to be dark, so | 275 * Heuristic to determine whether a theme should be considered to be dark, so |
275 * the colors of various UI elements can be adjusted. | 276 * the colors of various UI elements can be adjusted. |
276 * @param {ThemeBackgroundInfo|undefined} info Theme background information. | 277 * @param {ThemeBackgroundInfo|undefined} info Theme background information. |
277 * @return {boolean} Whether the theme is dark. | 278 * @return {boolean} Whether the theme is dark. |
278 * @private | 279 * @private |
279 */ | 280 */ |
280 function getIsThemeDark(info) { | 281 function getIsThemeDark(info) { |
281 if (!info) | 282 if (!info) |
282 return false; | 283 return false; |
283 // Heuristic: light text implies dark theme. | 284 // Heuristic: light text implies dark theme. |
284 var rgba = info.textColorRgba; | 285 var rgba = info.textColorRgba; |
285 var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2]; | 286 var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2]; |
286 return luminance >= 128; | 287 return luminance >= 128; |
287 } | 288 } |
288 | 289 |
289 | 290 |
290 /** | 291 /** |
291 * Updates the NTP based on the current theme. | 292 * Updates the NTP based on the current theme. |
292 * @private | 293 * @private |
293 */ | 294 */ |
294 function renderTheme() { | 295 function renderTheme() { |
295 var fakeboxText = $(IDS.FAKEBOX_TEXT); | 296 var fakeboxText = $(IDS.FAKEBOX_TEXT); |
296 if (fakeboxText) { | 297 if (fakeboxText) { |
297 fakeboxText.innerHTML = ''; | 298 fakeboxText.innerHTML = ''; |
298 if (configData.translatedStrings.searchboxPlaceholder) { | 299 if (configData.translatedStrings.searchboxPlaceholder) { |
299 fakeboxText.textContent = | 300 fakeboxText.textContent = |
300 configData.translatedStrings.searchboxPlaceholder; | 301 configData.translatedStrings.searchboxPlaceholder; |
301 } | 302 } |
302 } | 303 } |
303 | 304 |
304 var info = ntpApiHandle.themeBackgroundInfo; | 305 var info = ntpApiHandle.themeBackgroundInfo; |
305 var isThemeDark = getIsThemeDark(info); | 306 var isThemeDark = getIsThemeDark(info); |
306 ntpContents.classList.toggle(CLASSES.DARK, isThemeDark); | 307 ntpContents.classList.toggle(CLASSES.DARK, isThemeDark); |
307 if (!info) { | 308 if (!info) { |
308 return; | 309 return; |
309 } | 310 } |
310 | 311 |
311 var background = [convertToRGBAColor(info.backgroundColorRgba), | 312 var background = [ |
312 info.imageUrl, | 313 convertToRGBAColor(info.backgroundColorRgba), info.imageUrl, |
313 info.imageTiling, | 314 info.imageTiling, info.imageHorizontalAlignment, |
314 info.imageHorizontalAlignment, | 315 info.imageVerticalAlignment |
315 info.imageVerticalAlignment].join(' ').trim(); | 316 ].join(' ').trim(); |
316 | 317 |
317 document.body.style.background = background; | 318 document.body.style.background = background; |
318 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo); | 319 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo); |
319 updateThemeAttribution(info.attributionUrl, info.imageHorizontalAlignment); | 320 updateThemeAttribution(info.attributionUrl, info.imageHorizontalAlignment); |
320 setCustomThemeStyle(info); | 321 setCustomThemeStyle(info); |
321 | 322 |
322 var themeinfo = {cmd: 'updateTheme'}; | 323 var themeinfo = {cmd: 'updateTheme'}; |
323 if (!info.usingDefaultTheme) { | 324 if (!info.usingDefaultTheme) { |
324 themeinfo.tileBorderColor = convertToRGBAColor(info.sectionBorderColorRgba); | 325 themeinfo.tileBorderColor = |
325 themeinfo.tileHoverBorderColor = convertToRGBAColor(info.headerColorRgba); | 326 convertToRGBAColor(info.sectionBorderColorRgba); |
326 } | 327 themeinfo.tileHoverBorderColor = convertToRGBAColor(info.headerColorRgba); |
327 themeinfo.isThemeDark = isThemeDark; | 328 } |
328 | 329 themeinfo.isThemeDark = isThemeDark; |
329 var titleColor = NTP_DESIGN.titleColor; | 330 |
330 if (!info.usingDefaultTheme && info.textColorRgba) { | 331 var titleColor = NTP_DESIGN.titleColor; |
331 titleColor = info.textColorRgba; | 332 if (!info.usingDefaultTheme && info.textColorRgba) { |
332 } else if (isThemeDark) { | 333 titleColor = info.textColorRgba; |
333 titleColor = NTP_DESIGN.titleColorAgainstDark; | 334 } else if (isThemeDark) { |
334 } | 335 titleColor = NTP_DESIGN.titleColorAgainstDark; |
335 themeinfo.tileTitleColor = convertToRGBAColor(titleColor); | 336 } |
336 | 337 themeinfo.tileTitleColor = convertToRGBAColor(titleColor); |
337 $('mv-single').contentWindow.postMessage(themeinfo, '*'); | 338 |
338 } | 339 $('mv-single').contentWindow.postMessage(themeinfo, '*'); |
339 | 340 } |
340 | 341 |
341 /** | 342 |
342 * Updates the NTP based on the current theme, then rerenders all tiles. | 343 /** |
343 * @private | 344 * Updates the NTP based on the current theme, then rerenders all tiles. |
344 */ | 345 * @private |
345 function onThemeChange() { | 346 */ |
346 renderTheme(); | 347 function onThemeChange() { |
347 } | 348 renderTheme(); |
348 | 349 } |
349 | 350 |
350 /** | 351 |
351 * Updates the NTP style according to theme. | 352 /** |
352 * @param {Object=} opt_themeInfo The information about the theme. If it is | 353 * Updates the NTP style according to theme. |
353 * omitted the style will be reverted to the default. | 354 * @param {Object=} opt_themeInfo The information about the theme. If it is |
354 * @private | 355 * omitted the style will be reverted to the default. |
355 */ | 356 * @private |
356 function setCustomThemeStyle(opt_themeInfo) { | 357 */ |
357 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE); | 358 function setCustomThemeStyle(opt_themeInfo) { |
358 var head = document.head; | 359 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE); |
359 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) { | 360 var head = document.head; |
360 ntpContents.classList.remove(CLASSES.DEFAULT_THEME); | 361 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) { |
361 var themeStyle = | 362 ntpContents.classList.remove(CLASSES.DEFAULT_THEME); |
362 '#attribution {' + | 363 var themeStyle = '#attribution {' + |
363 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + | 364 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + |
364 '}' + | 365 ';' + |
365 '#mv-msg {' + | 366 '}' + |
366 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' + | 367 '#mv-msg {' + |
367 '}' + | 368 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' + |
368 '#mv-notice-links span {' + | 369 '}' + |
369 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + | 370 '#mv-notice-links span {' + |
370 '}' + | 371 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + |
371 '#mv-notice-x {' + | 372 ';' + |
372 ' -webkit-filter: drop-shadow(0 0 0 ' + | 373 '}' + |
| 374 '#mv-notice-x {' + |
| 375 ' -webkit-filter: drop-shadow(0 0 0 ' + |
373 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' + | 376 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' + |
374 '}' + | 377 '}' + |
375 '.mv-page-ready .mv-mask {' + | 378 '.mv-page-ready .mv-mask {' + |
376 ' border: 1px solid ' + | 379 ' border: 1px solid ' + |
377 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' + | 380 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' + |
378 '}' + | 381 '}' + |
379 '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask {' + | 382 '.mv-page-ready:hover .mv-mask, ' + |
380 ' border-color: ' + | 383 '.mv-page-ready .mv-focused ~ .mv-mask {' + |
| 384 ' border-color: ' + |
381 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' + | 385 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' + |
382 '}'; | 386 '}'; |
383 | 387 |
384 if (customStyleElement) { | 388 if (customStyleElement) { |
385 customStyleElement.textContent = themeStyle; | 389 customStyleElement.textContent = themeStyle; |
| 390 } else { |
| 391 customStyleElement = document.createElement('style'); |
| 392 customStyleElement.type = 'text/css'; |
| 393 customStyleElement.id = IDS.CUSTOM_THEME_STYLE; |
| 394 customStyleElement.textContent = themeStyle; |
| 395 head.appendChild(customStyleElement); |
| 396 } |
| 397 |
386 } else { | 398 } else { |
387 customStyleElement = document.createElement('style'); | 399 ntpContents.classList.add(CLASSES.DEFAULT_THEME); |
388 customStyleElement.type = 'text/css'; | 400 if (customStyleElement) |
389 customStyleElement.id = IDS.CUSTOM_THEME_STYLE; | 401 head.removeChild(customStyleElement); |
390 customStyleElement.textContent = themeStyle; | 402 } |
391 head.appendChild(customStyleElement); | 403 } |
392 } | 404 |
393 | 405 |
394 } else { | 406 /** |
395 ntpContents.classList.add(CLASSES.DEFAULT_THEME); | 407 * Renders the attribution if the URL is present, otherwise hides it. |
396 if (customStyleElement) | 408 * @param {string} url The URL of the attribution image, if any. |
397 head.removeChild(customStyleElement); | 409 * @param {string} themeBackgroundAlignment The alignment of the theme |
398 } | 410 * background image. This is used to compute the attribution's alignment. |
399 } | 411 * @private |
400 | 412 */ |
401 | 413 function updateThemeAttribution(url, themeBackgroundAlignment) { |
402 /** | 414 if (!url) { |
403 * Renders the attribution if the URL is present, otherwise hides it. | 415 setAttributionVisibility_(false); |
404 * @param {string} url The URL of the attribution image, if any. | 416 return; |
405 * @param {string} themeBackgroundAlignment The alignment of the theme | 417 } |
406 * background image. This is used to compute the attribution's alignment. | 418 |
407 * @private | 419 var attributionImage = attribution.querySelector('img'); |
408 */ | 420 if (!attributionImage) { |
409 function updateThemeAttribution(url, themeBackgroundAlignment) { | 421 attributionImage = new Image(); |
410 if (!url) { | 422 attribution.appendChild(attributionImage); |
411 setAttributionVisibility_(false); | 423 } |
412 return; | 424 attributionImage.style.content = url; |
413 } | 425 |
414 | 426 // To avoid conflicts, place the attribution on the left for themes that |
415 var attributionImage = attribution.querySelector('img'); | 427 // right align their background images. |
416 if (!attributionImage) { | 428 attribution.classList.toggle( |
417 attributionImage = new Image(); | 429 CLASSES.LEFT_ALIGN_ATTRIBUTION, themeBackgroundAlignment == 'right'); |
418 attribution.appendChild(attributionImage); | 430 setAttributionVisibility_(true); |
419 } | 431 } |
420 attributionImage.style.content = url; | 432 |
421 | 433 |
422 // To avoid conflicts, place the attribution on the left for themes that | 434 /** |
423 // right align their background images. | 435 * Sets the visibility of the theme attribution. |
424 attribution.classList.toggle(CLASSES.LEFT_ALIGN_ATTRIBUTION, | 436 * @param {boolean} show True to show the attribution. |
425 themeBackgroundAlignment == 'right'); | 437 * @private |
426 setAttributionVisibility_(true); | 438 */ |
427 } | 439 function setAttributionVisibility_(show) { |
428 | 440 if (attribution) { |
429 | 441 attribution.style.display = show ? '' : 'none'; |
430 /** | 442 } |
431 * Sets the visibility of the theme attribution. | 443 } |
432 * @param {boolean} show True to show the attribution. | 444 |
433 * @private | 445 |
434 */ | 446 /** |
435 function setAttributionVisibility_(show) { | |
436 if (attribution) { | |
437 attribution.style.display = show ? '' : 'none'; | |
438 } | |
439 } | |
440 | |
441 | |
442 /** | |
443 * Converts an Array of color components into RRGGBBAA format. | 447 * Converts an Array of color components into RRGGBBAA format. |
444 * @param {Array<number>} color Array of rgba color components. | 448 * @param {Array<number>} color Array of rgba color components. |
445 * @return {string} Color string in RRGGBBAA format. | 449 * @return {string} Color string in RRGGBBAA format. |
446 * @private | 450 * @private |
447 */ | 451 */ |
448 function convertToRRGGBBAAColor(color) { | 452 function convertToRRGGBBAAColor(color) { |
449 return color.map(function(t) { | 453 return color |
450 return ('0' + t.toString(16)).slice(-2); // To 2-digit, 0-padded hex. | 454 .map(function(t) { |
451 }).join(''); | 455 return ('0' + t.toString(16)).slice(-2); // To 2-digit, 0-padded hex. |
452 } | 456 }) |
453 | 457 .join(''); |
454 | 458 } |
455 /** | 459 |
| 460 |
| 461 /** |
456 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)". | 462 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)". |
457 * @param {Array<number>} color Array of rgba color components. | 463 * @param {Array<number>} color Array of rgba color components. |
458 * @return {string} CSS color in RGBA format. | 464 * @return {string} CSS color in RGBA format. |
459 * @private | 465 * @private |
460 */ | 466 */ |
461 function convertToRGBAColor(color) { | 467 function convertToRGBAColor(color) { |
462 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + | 468 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + |
463 color[3] / 255 + ')'; | 469 color[3] / 255 + ')'; |
464 } | 470 } |
465 | 471 |
466 | 472 |
467 /** | 473 /** |
468 * Called when page data change. | 474 * Called when page data change. |
469 */ | 475 */ |
470 function onMostVisitedChange() { | 476 function onMostVisitedChange() { |
471 reloadTiles(); | 477 reloadTiles(); |
472 } | 478 } |
473 | 479 |
474 | 480 |
475 /** | 481 /** |
476 * Fetches new data, creates, and renders tiles. | 482 * Fetches new data, creates, and renders tiles. |
477 */ | 483 */ |
478 function reloadTiles() { | 484 function reloadTiles() { |
479 var pages = ntpApiHandle.mostVisited; | 485 var pages = ntpApiHandle.mostVisited; |
480 var cmds = []; | 486 var cmds = []; |
481 for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) { | 487 for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) { |
482 cmds.push({cmd: 'tile', rid: pages[i].rid}); | 488 cmds.push({cmd: 'tile', rid: pages[i].rid}); |
483 } | 489 } |
484 cmds.push({cmd: 'show', maxVisible: numColumnsShown * NUM_ROWS}); | 490 cmds.push({cmd: 'show', maxVisible: numColumnsShown * NUM_ROWS}); |
485 | 491 |
486 $('mv-single').contentWindow.postMessage(cmds, '*'); | 492 $('mv-single').contentWindow.postMessage(cmds, '*'); |
487 } | 493 } |
488 | 494 |
489 | 495 |
490 /** | 496 /** |
491 * Shows the blacklist notification and triggers a delay to hide it. | 497 * Shows the blacklist notification and triggers a delay to hide it. |
492 */ | 498 */ |
493 function showNotification() { | 499 function showNotification() { |
494 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); | 500 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); |
495 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); | 501 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); |
496 notification.scrollTop; | 502 notification.scrollTop; |
497 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); | 503 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); |
498 } | 504 } |
499 | 505 |
500 | 506 |
501 /** | 507 /** |
502 * Hides the blacklist notification. | 508 * Hides the blacklist notification. |
503 */ | 509 */ |
504 function hideNotification() { | 510 function hideNotification() { |
505 notification.classList.add(CLASSES.HIDE_NOTIFICATION); | 511 notification.classList.add(CLASSES.HIDE_NOTIFICATION); |
506 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); | 512 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); |
507 } | 513 } |
508 | 514 |
509 | 515 |
510 /** | 516 /** |
511 * Handles a click on the notification undo link by hiding the notification and | 517 * Handles a click on the notification undo link by hiding the notification |
512 * informing Chrome. | 518 * and |
513 */ | 519 * informing Chrome. |
514 function onUndo() { | 520 */ |
515 hideNotification(); | 521 function onUndo() { |
516 if (lastBlacklistedTile != null) { | 522 hideNotification(); |
517 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile); | 523 if (lastBlacklistedTile != null) { |
518 } | 524 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile); |
519 } | 525 } |
520 | 526 } |
521 | 527 |
522 /** | 528 |
523 * Handles a click on the restore all notification link by hiding the | 529 /** |
524 * notification and informing Chrome. | 530 * Handles a click on the restore all notification link by hiding the |
525 */ | 531 * notification and informing Chrome. |
526 function onRestoreAll() { | 532 */ |
527 hideNotification(); | 533 function onRestoreAll() { |
528 ntpApiHandle.undoAllMostVisitedDeletions(); | 534 hideNotification(); |
529 } | 535 ntpApiHandle.undoAllMostVisitedDeletions(); |
530 | 536 } |
531 | 537 |
532 /** | 538 |
| 539 /** |
533 * Recomputes the number of tile columns, and width of various contents based | 540 * Recomputes the number of tile columns, and width of various contents based |
534 * on the width of the window. | 541 * on the width of the window. |
535 * @return {boolean} Whether the number of tile columns has changed. | 542 * @return {boolean} Whether the number of tile columns has changed. |
536 */ | 543 */ |
537 function updateContentWidth() { | 544 function updateContentWidth() { |
538 var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin; | 545 var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin; |
539 // If innerWidth is zero, then use the maximum snap size. | 546 // If innerWidth is zero, then use the maximum snap size. |
540 var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth - | 547 var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth - |
541 NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING; | 548 NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING; |
542 var innerWidth = window.innerWidth || maxSnapSize; | 549 var innerWidth = window.innerWidth || maxSnapSize; |
543 // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin. | 550 // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin. |
544 var availableWidth = innerWidth + NTP_DESIGN.tileMargin - | 551 var availableWidth = innerWidth + NTP_DESIGN.tileMargin - |
545 NTP_DESIGN.fakeboxWingSize * 2 - MIN_TOTAL_HORIZONTAL_PADDING; | 552 NTP_DESIGN.fakeboxWingSize * 2 - MIN_TOTAL_HORIZONTAL_PADDING; |
546 var newNumColumns = Math.floor(availableWidth / tileRequiredWidth); | 553 var newNumColumns = Math.floor(availableWidth / tileRequiredWidth); |
547 if (newNumColumns < MIN_NUM_COLUMNS) | 554 if (newNumColumns < MIN_NUM_COLUMNS) |
548 newNumColumns = MIN_NUM_COLUMNS; | 555 newNumColumns = MIN_NUM_COLUMNS; |
549 else if (newNumColumns > MAX_NUM_COLUMNS) | 556 else if (newNumColumns > MAX_NUM_COLUMNS) |
550 newNumColumns = MAX_NUM_COLUMNS; | 557 newNumColumns = MAX_NUM_COLUMNS; |
551 | 558 |
552 if (numColumnsShown === newNumColumns) | 559 if (numColumnsShown === newNumColumns) |
553 return false; | 560 return false; |
554 | 561 |
555 numColumnsShown = newNumColumns; | 562 numColumnsShown = newNumColumns; |
556 // We add an extra pixel because rounding errors on different zooms can | 563 // We add an extra pixel because rounding errors on different zooms can |
557 // make the width shorter than it should be. | 564 // make the width shorter than it should be. |
558 var tilesContainerWidth = Math.ceil(numColumnsShown * tileRequiredWidth) + 1; | 565 var tilesContainerWidth = |
559 $(IDS.TILES).style.width = tilesContainerWidth + 'px'; | 566 Math.ceil(numColumnsShown * tileRequiredWidth) + 1; |
560 if (fakebox) { | 567 $(IDS.TILES).style.width = tilesContainerWidth + 'px'; |
561 // -2 to account for border. | 568 if (fakebox) { |
562 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2); | 569 // -2 to account for border. |
563 fakeboxWidth += NTP_DESIGN.fakeboxWingSize * 2; | 570 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2); |
564 fakebox.style.width = fakeboxWidth + 'px'; | 571 fakeboxWidth += NTP_DESIGN.fakeboxWingSize * 2; |
565 } | 572 fakebox.style.width = fakeboxWidth + 'px'; |
566 return true; | 573 } |
567 } | 574 return true; |
568 | 575 } |
569 | 576 |
570 /** | 577 |
571 * Resizes elements because the number of tile columns may need to change in | 578 /** |
572 * response to resizing. Also shows or hides extra tiles tiles according to the | 579 * Resizes elements because the number of tile columns may need to change in |
573 * new width of the page. | 580 * response to resizing. Also shows or hides extra tiles tiles according to |
574 */ | 581 * the |
575 function onResize() { | 582 * new width of the page. |
576 updateContentWidth(); | 583 */ |
577 $('mv-single').contentWindow.postMessage( | 584 function onResize() { |
578 {cmd: 'tilesVisible', maxVisible: numColumnsShown * NUM_ROWS}, '*'); | 585 updateContentWidth(); |
579 } | 586 $('mv-single') |
580 | 587 .contentWindow.postMessage( |
581 | 588 {cmd: 'tilesVisible', maxVisible: numColumnsShown * NUM_ROWS}, '*'); |
582 /** | 589 } |
583 * Handles new input by disposing the NTP, according to where the input was | 590 |
584 * entered. | 591 |
585 */ | 592 /** |
586 function onInputStart() { | 593 * Handles new input by disposing the NTP, according to where the input was |
587 if (fakebox && isFakeboxFocused()) { | 594 * entered. |
588 setFakeboxFocus(false); | 595 */ |
589 setFakeboxDragFocus(false); | 596 function onInputStart() { |
590 disposeNtp(true); | 597 if (fakebox && isFakeboxFocused()) { |
591 } else if (!isFakeboxFocused()) { | 598 setFakeboxFocus(false); |
592 disposeNtp(false); | 599 setFakeboxDragFocus(false); |
593 } | 600 disposeNtp(true); |
594 } | 601 } else if (!isFakeboxFocused()) { |
595 | 602 disposeNtp(false); |
596 | 603 } |
597 /** | 604 } |
598 * Disposes the NTP, according to where the input was entered. | 605 |
599 * @param {boolean} wasFakeboxInput True if the input was in the fakebox. | 606 |
600 */ | 607 /** |
601 function disposeNtp(wasFakeboxInput) { | 608 * Disposes the NTP, according to where the input was entered. |
602 var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior; | 609 * @param {boolean} wasFakeboxInput True if the input was in the fakebox. |
603 if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX) | 610 */ |
604 setFakeboxActive(false); | 611 function disposeNtp(wasFakeboxInput) { |
605 else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO) | 612 var behavior = |
606 setFakeboxAndLogoVisibility(false); | 613 wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior; |
607 } | 614 if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX) |
608 | 615 setFakeboxActive(false); |
609 | 616 else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO) |
610 /** | 617 setFakeboxAndLogoVisibility(false); |
611 * Restores the NTP (re-enables the fakebox and unhides the logo.) | 618 } |
612 */ | 619 |
613 function restoreNtp() { | 620 |
614 setFakeboxActive(true); | 621 /** |
615 setFakeboxAndLogoVisibility(true); | 622 * Restores the NTP (re-enables the fakebox and unhides the logo.) |
616 } | 623 */ |
617 | 624 function restoreNtp() { |
618 | 625 setFakeboxActive(true); |
619 /** | 626 setFakeboxAndLogoVisibility(true); |
620 * @param {boolean} focus True to focus the fakebox. | 627 } |
621 */ | 628 |
622 function setFakeboxFocus(focus) { | 629 |
623 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus); | 630 /** |
624 } | 631 * @param {boolean} focus True to focus the fakebox. |
625 | 632 */ |
626 /** | 633 function setFakeboxFocus(focus) { |
627 * @param {boolean} focus True to show a dragging focus to the fakebox. | 634 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus); |
628 */ | 635 } |
629 function setFakeboxDragFocus(focus) { | 636 |
630 document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus); | 637 /** |
631 } | 638 * @param {boolean} focus True to show a dragging focus to the fakebox. |
632 | 639 */ |
633 /** | 640 function setFakeboxDragFocus(focus) { |
| 641 document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus); |
| 642 } |
| 643 |
| 644 /** |
634 * @return {boolean} True if the fakebox has focus. | 645 * @return {boolean} True if the fakebox has focus. |
635 */ | 646 */ |
636 function isFakeboxFocused() { | 647 function isFakeboxFocused() { |
637 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) || | 648 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) || |
638 document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS); | 649 document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS); |
639 } | 650 } |
640 | 651 |
641 | 652 |
642 /** | 653 /** |
643 * @param {boolean} enable True to enable the fakebox. | 654 * @param {boolean} enable True to enable the fakebox. |
644 */ | 655 */ |
645 function setFakeboxActive(enable) { | 656 function setFakeboxActive(enable) { |
646 document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable); | 657 document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable); |
647 } | 658 } |
648 | 659 |
649 | 660 |
650 /** | 661 /** |
651 * @param {!Event} event The click event. | 662 * @param {!Event} event The click event. |
652 * @return {boolean} True if the click occurred in an enabled fakebox. | 663 * @return {boolean} True if the click occurred in an enabled fakebox. |
653 */ | 664 */ |
654 function isFakeboxClick(event) { | 665 function isFakeboxClick(event) { |
655 return fakebox.contains(event.target) && | 666 return fakebox.contains(event.target) && |
656 !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE); | 667 !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE); |
657 } | 668 } |
658 | 669 |
659 | 670 |
660 /** | 671 /** |
661 * @param {boolean} show True to show the fakebox and logo. | 672 * @param {boolean} show True to show the fakebox and logo. |
662 */ | 673 */ |
663 function setFakeboxAndLogoVisibility(show) { | 674 function setFakeboxAndLogoVisibility(show) { |
664 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show); | 675 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show); |
665 } | 676 } |
666 | 677 |
667 | 678 |
668 /** | 679 /** |
669 * Shortcut for document.getElementById. | 680 * Shortcut for document.getElementById. |
670 * @param {string} id of the element. | 681 * @param {string} id of the element. |
671 * @return {HTMLElement} with the id. | 682 * @return {HTMLElement} with the id. |
672 */ | 683 */ |
673 function $(id) { | 684 function $(id) { |
674 return document.getElementById(id); | 685 return document.getElementById(id); |
675 } | 686 } |
676 | 687 |
677 | 688 |
678 /** | 689 /** |
679 * Utility function which creates an element with an optional classname and | 690 * Utility function which creates an element with an optional classname and |
680 * appends it to the specified parent. | 691 * appends it to the specified parent. |
681 * @param {Element} parent The parent to append the new element. | 692 * @param {Element} parent The parent to append the new element. |
682 * @param {string} name The name of the new element. | 693 * @param {string} name The name of the new element. |
683 * @param {string=} opt_class The optional classname of the new element. | 694 * @param {string=} opt_class The optional classname of the new element. |
684 * @return {Element} The new element. | 695 * @return {Element} The new element. |
685 */ | 696 */ |
686 function createAndAppendElement(parent, name, opt_class) { | 697 function createAndAppendElement(parent, name, opt_class) { |
687 var child = document.createElement(name); | 698 var child = document.createElement(name); |
688 if (opt_class) | 699 if (opt_class) |
689 child.classList.add(opt_class); | 700 child.classList.add(opt_class); |
690 parent.appendChild(child); | 701 parent.appendChild(child); |
691 return child; | 702 return child; |
692 } | 703 } |
693 | 704 |
694 | 705 |
695 /** | 706 /** |
696 * @param {!Element} element The element to register the handler for. | 707 * @param {!Element} element The element to register the handler for. |
697 * @param {number} keycode The keycode of the key to register. | 708 * @param {number} keycode The keycode of the key to register. |
698 * @param {!Function} handler The key handler to register. | 709 * @param {!Function} handler The key handler to register. |
699 */ | 710 */ |
700 function registerKeyHandler(element, keycode, handler) { | 711 function registerKeyHandler(element, keycode, handler) { |
701 element.addEventListener('keydown', function(event) { | 712 element.addEventListener('keydown', function(event) { |
702 if (event.keyCode == keycode) | 713 if (event.keyCode == keycode) |
703 handler(event); | 714 handler(event); |
704 }); | 715 }); |
705 } | 716 } |
706 | 717 |
707 | 718 |
708 /** | 719 /** |
709 * @return {Object} the handle to the embeddedSearch API. | 720 * @return {Object} the handle to the embeddedSearch API. |
710 */ | 721 */ |
711 function getEmbeddedSearchApiHandle() { | 722 function getEmbeddedSearchApiHandle() { |
712 if (window.cideb) | 723 if (window.cideb) |
713 return window.cideb; | 724 return window.cideb; |
714 if (window.chrome && window.chrome.embeddedSearch) | 725 if (window.chrome && window.chrome.embeddedSearch) |
715 return window.chrome.embeddedSearch; | 726 return window.chrome.embeddedSearch; |
716 return null; | 727 return null; |
| 728 } |
| 729 |
| 730 |
| 731 /** |
| 732 * Event handler for the focus changed and blacklist messages on link |
| 733 * elements. |
| 734 * Used to toggle visual treatment on the tiles (depending on the message). |
| 735 * @param {Event} event Event received. |
| 736 */ |
| 737 function handlePostMessage(event) { |
| 738 var cmd = event.data.cmd; |
| 739 var args = event.data; |
| 740 if (cmd == 'tileBlacklisted') { |
| 741 showNotification(); |
| 742 lastBlacklistedTile = args.tid; |
| 743 |
| 744 ntpApiHandle.deleteMostVisitedItem(args.tid); |
| 745 } |
| 746 } |
| 747 |
| 748 |
| 749 /** |
| 750 * Prepares the New Tab Page by adding listeners, rendering the current |
| 751 * theme, the most visited pages section, and Google-specific elements for a |
| 752 * Google-provided page. |
| 753 */ |
| 754 function init() { |
| 755 notification = $(IDS.NOTIFICATION); |
| 756 attribution = $(IDS.ATTRIBUTION); |
| 757 ntpContents = $(IDS.NTP_CONTENTS); |
| 758 |
| 759 if (configData.isGooglePage) { |
| 760 var logo = document.createElement('div'); |
| 761 logo.id = IDS.LOGO; |
| 762 logo.title = 'Google'; |
| 763 |
| 764 fakebox = document.createElement('div'); |
| 765 fakebox.id = IDS.FAKEBOX; |
| 766 var fakeboxHtml = []; |
| 767 fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '"></div>'); |
| 768 fakeboxHtml.push( |
| 769 '<input id="' + IDS.FAKEBOX_INPUT + |
| 770 '" autocomplete="off" tabindex="-1" type="url" aria-hidden="true">'); |
| 771 fakeboxHtml.push('<div id="cursor"></div>'); |
| 772 fakebox.innerHTML = fakeboxHtml.join(''); |
| 773 |
| 774 ntpContents.insertBefore(fakebox, ntpContents.firstChild); |
| 775 ntpContents.insertBefore(logo, ntpContents.firstChild); |
| 776 } else { |
| 777 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); |
| 778 } |
| 779 |
| 780 // Modify design for experimental icon NTP, if specified. |
| 781 if (configData.useIcons) |
| 782 modifyNtpDesignForIcons(); |
| 783 document.querySelector('#ntp-contents').classList.add(NTP_DESIGN.mainClass); |
| 784 |
| 785 // Hide notifications after fade out, so we can't focus on links via |
| 786 // keyboard. |
| 787 notification.addEventListener('webkitTransitionEnd', hideNotification); |
| 788 |
| 789 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE); |
| 790 notificationMessage.textContent = |
| 791 configData.translatedStrings.thumbnailRemovedNotification; |
| 792 |
| 793 var undoLink = $(IDS.UNDO_LINK); |
| 794 undoLink.addEventListener('click', onUndo); |
| 795 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo); |
| 796 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove; |
| 797 |
| 798 var restoreAllLink = $(IDS.RESTORE_ALL_LINK); |
| 799 restoreAllLink.addEventListener('click', onRestoreAll); |
| 800 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo); |
| 801 restoreAllLink.textContent = |
| 802 configData.translatedStrings.restoreThumbnailsShort; |
| 803 |
| 804 $(IDS.ATTRIBUTION_TEXT).textContent = |
| 805 configData.translatedStrings.attributionIntro; |
| 806 |
| 807 var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON); |
| 808 createAndAppendElement( |
| 809 notificationCloseButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER); |
| 810 notificationCloseButton.addEventListener('click', hideNotification); |
| 811 |
| 812 window.addEventListener('resize', onResize); |
| 813 updateContentWidth(); |
| 814 |
| 815 var topLevelHandle = getEmbeddedSearchApiHandle(); |
| 816 |
| 817 ntpApiHandle = topLevelHandle.newTabPage; |
| 818 ntpApiHandle.onthemechange = onThemeChange; |
| 819 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; |
| 820 |
| 821 ntpApiHandle.oninputstart = onInputStart; |
| 822 ntpApiHandle.oninputcancel = restoreNtp; |
| 823 |
| 824 if (ntpApiHandle.isInputInProgress) |
| 825 onInputStart(); |
| 826 |
| 827 searchboxApiHandle = topLevelHandle.searchBox; |
| 828 |
| 829 if (fakebox) { |
| 830 // Listener for updating the key capture state. |
| 831 document.body.onmousedown = function(event) { |
| 832 if (isFakeboxClick(event)) |
| 833 searchboxApiHandle.startCapturingKeyStrokes(); |
| 834 else if (isFakeboxFocused()) |
| 835 searchboxApiHandle.stopCapturingKeyStrokes(); |
| 836 }; |
| 837 searchboxApiHandle.onkeycapturechange = function() { |
| 838 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); |
| 839 }; |
| 840 var inputbox = $(IDS.FAKEBOX_INPUT); |
| 841 if (inputbox) { |
| 842 inputbox.onpaste = function(event) { |
| 843 event.preventDefault(); |
| 844 // Send pasted text to Omnibox. |
| 845 var text = event.clipboardData.getData('text/plain'); |
| 846 if (text) |
| 847 searchboxApiHandle.paste(text); |
| 848 }; |
| 849 inputbox.ondrop = function(event) { |
| 850 event.preventDefault(); |
| 851 var text = event.dataTransfer.getData('text/plain'); |
| 852 if (text) { |
| 853 searchboxApiHandle.paste(text); |
| 854 } |
| 855 setFakeboxDragFocus(false); |
| 856 }; |
| 857 inputbox.ondragenter = function() { |
| 858 setFakeboxDragFocus(true); |
| 859 }; |
| 860 inputbox.ondragleave = function() { |
| 861 setFakeboxDragFocus(false); |
| 862 }; |
| 863 } |
| 864 |
| 865 // Update the fakebox style to match the current key capturing state. |
| 866 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); |
| 867 } |
| 868 |
| 869 if (searchboxApiHandle.rtl) { |
| 870 $(IDS.NOTIFICATION).dir = 'rtl'; |
| 871 // Grabbing the root HTML element. |
| 872 document.documentElement.setAttribute('dir', 'rtl'); |
| 873 // Add class for setting alignments based on language directionality. |
| 874 document.documentElement.classList.add(CLASSES.RTL); |
| 875 } |
| 876 |
| 877 var iframe = document.createElement('iframe'); |
| 878 // Change the order of tabbing the page to start with NTP tiles. |
| 879 iframe.setAttribute('tabindex', '1'); |
| 880 iframe.id = 'mv-single'; |
| 881 |
| 882 var args = []; |
| 883 |
| 884 if (searchboxApiHandle.rtl) |
| 885 args.push('rtl=1'); |
| 886 if (window.configData.useIcons) |
| 887 args.push('icons=1'); |
| 888 if (NTP_DESIGN.numTitleLines > 1) |
| 889 args.push('ntl=' + NTP_DESIGN.numTitleLines); |
| 890 |
| 891 args.push( |
| 892 'removeTooltip=' + |
| 893 encodeURIComponent( |
| 894 configData.translatedStrings.removeThumbnailTooltip)); |
| 895 |
| 896 iframe.src = '//most-visited/single.html?' + args.join('&'); |
| 897 $(IDS.TILES).appendChild(iframe); |
| 898 |
| 899 iframe.onload = function() { |
| 900 reloadTiles(); |
| 901 renderTheme(); |
| 902 }; |
| 903 |
| 904 window.addEventListener('message', handlePostMessage); |
| 905 } |
| 906 |
| 907 |
| 908 /** |
| 909 * Binds event listeners. |
| 910 */ |
| 911 function listen() { |
| 912 document.addEventListener('DOMContentLoaded', init); |
| 913 } |
| 914 |
| 915 return {init: init, listen: listen}; |
717 } | 916 } |
718 | 917 |
719 | |
720 /** | |
721 * Event handler for the focus changed and blacklist messages on link elements. | |
722 * Used to toggle visual treatment on the tiles (depending on the message). | |
723 * @param {Event} event Event received. | |
724 */ | |
725 function handlePostMessage(event) { | |
726 var cmd = event.data.cmd; | |
727 var args = event.data; | |
728 if (cmd == 'tileBlacklisted') { | |
729 showNotification(); | |
730 lastBlacklistedTile = args.tid; | |
731 | |
732 ntpApiHandle.deleteMostVisitedItem(args.tid); | |
733 } | |
734 } | |
735 | |
736 | |
737 /** | |
738 * Prepares the New Tab Page by adding listeners, rendering the current | |
739 * theme, the most visited pages section, and Google-specific elements for a | |
740 * Google-provided page. | |
741 */ | |
742 function init() { | |
743 notification = $(IDS.NOTIFICATION); | |
744 attribution = $(IDS.ATTRIBUTION); | |
745 ntpContents = $(IDS.NTP_CONTENTS); | |
746 | |
747 if (configData.isGooglePage) { | |
748 var logo = document.createElement('div'); | |
749 logo.id = IDS.LOGO; | |
750 logo.title = 'Google'; | |
751 | |
752 fakebox = document.createElement('div'); | |
753 fakebox.id = IDS.FAKEBOX; | |
754 var fakeboxHtml = []; | |
755 fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '"></div>'); | |
756 fakeboxHtml.push('<input id="' + IDS.FAKEBOX_INPUT + | |
757 '" autocomplete="off" tabindex="-1" type="url" aria-hidden="true">'); | |
758 fakeboxHtml.push('<div id="cursor"></div>'); | |
759 fakebox.innerHTML = fakeboxHtml.join(''); | |
760 | |
761 ntpContents.insertBefore(fakebox, ntpContents.firstChild); | |
762 ntpContents.insertBefore(logo, ntpContents.firstChild); | |
763 } else { | |
764 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); | |
765 } | |
766 | |
767 // Modify design for experimental icon NTP, if specified. | |
768 if (configData.useIcons) | |
769 modifyNtpDesignForIcons(); | |
770 document.querySelector('#ntp-contents').classList.add(NTP_DESIGN.mainClass); | |
771 | |
772 // Hide notifications after fade out, so we can't focus on links via keyboard. | |
773 notification.addEventListener('webkitTransitionEnd', hideNotification); | |
774 | |
775 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE); | |
776 notificationMessage.textContent = | |
777 configData.translatedStrings.thumbnailRemovedNotification; | |
778 | |
779 var undoLink = $(IDS.UNDO_LINK); | |
780 undoLink.addEventListener('click', onUndo); | |
781 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo); | |
782 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove; | |
783 | |
784 var restoreAllLink = $(IDS.RESTORE_ALL_LINK); | |
785 restoreAllLink.addEventListener('click', onRestoreAll); | |
786 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo); | |
787 restoreAllLink.textContent = | |
788 configData.translatedStrings.restoreThumbnailsShort; | |
789 | |
790 $(IDS.ATTRIBUTION_TEXT).textContent = | |
791 configData.translatedStrings.attributionIntro; | |
792 | |
793 var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON); | |
794 createAndAppendElement( | |
795 notificationCloseButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER); | |
796 notificationCloseButton.addEventListener('click', hideNotification); | |
797 | |
798 window.addEventListener('resize', onResize); | |
799 updateContentWidth(); | |
800 | |
801 var topLevelHandle = getEmbeddedSearchApiHandle(); | |
802 | |
803 ntpApiHandle = topLevelHandle.newTabPage; | |
804 ntpApiHandle.onthemechange = onThemeChange; | |
805 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; | |
806 | |
807 ntpApiHandle.oninputstart = onInputStart; | |
808 ntpApiHandle.oninputcancel = restoreNtp; | |
809 | |
810 if (ntpApiHandle.isInputInProgress) | |
811 onInputStart(); | |
812 | |
813 searchboxApiHandle = topLevelHandle.searchBox; | |
814 | |
815 if (fakebox) { | |
816 // Listener for updating the key capture state. | |
817 document.body.onmousedown = function(event) { | |
818 if (isFakeboxClick(event)) | |
819 searchboxApiHandle.startCapturingKeyStrokes(); | |
820 else if (isFakeboxFocused()) | |
821 searchboxApiHandle.stopCapturingKeyStrokes(); | |
822 }; | |
823 searchboxApiHandle.onkeycapturechange = function() { | |
824 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); | |
825 }; | |
826 var inputbox = $(IDS.FAKEBOX_INPUT); | |
827 if (inputbox) { | |
828 inputbox.onpaste = function(event) { | |
829 event.preventDefault(); | |
830 // Send pasted text to Omnibox. | |
831 var text = event.clipboardData.getData('text/plain'); | |
832 if (text) | |
833 searchboxApiHandle.paste(text); | |
834 }; | |
835 inputbox.ondrop = function(event) { | |
836 event.preventDefault(); | |
837 var text = event.dataTransfer.getData('text/plain'); | |
838 if (text) { | |
839 searchboxApiHandle.paste(text); | |
840 } | |
841 setFakeboxDragFocus(false); | |
842 }; | |
843 inputbox.ondragenter = function() { | |
844 setFakeboxDragFocus(true); | |
845 }; | |
846 inputbox.ondragleave = function() { | |
847 setFakeboxDragFocus(false); | |
848 }; | |
849 } | |
850 | |
851 // Update the fakebox style to match the current key capturing state. | |
852 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); | |
853 } | |
854 | |
855 if (searchboxApiHandle.rtl) { | |
856 $(IDS.NOTIFICATION).dir = 'rtl'; | |
857 // Grabbing the root HTML element. | |
858 document.documentElement.setAttribute('dir', 'rtl'); | |
859 // Add class for setting alignments based on language directionality. | |
860 document.documentElement.classList.add(CLASSES.RTL); | |
861 } | |
862 | |
863 var iframe = document.createElement('iframe'); | |
864 // Change the order of tabbing the page to start with NTP tiles. | |
865 iframe.setAttribute('tabindex', '1'); | |
866 iframe.id = 'mv-single'; | |
867 | |
868 var args = []; | |
869 | |
870 if (searchboxApiHandle.rtl) | |
871 args.push('rtl=1'); | |
872 if (window.configData.useIcons) | |
873 args.push('icons=1'); | |
874 if (NTP_DESIGN.numTitleLines > 1) | |
875 args.push('ntl=' + NTP_DESIGN.numTitleLines); | |
876 | |
877 args.push('removeTooltip=' + | |
878 encodeURIComponent(configData.translatedStrings.removeThumbnailTooltip)); | |
879 | |
880 iframe.src = '//most-visited/single.html?' + args.join('&'); | |
881 $(IDS.TILES).appendChild(iframe); | |
882 | |
883 iframe.onload = function() { | |
884 reloadTiles(); | |
885 renderTheme(); | |
886 }; | |
887 | |
888 window.addEventListener('message', handlePostMessage); | |
889 } | |
890 | |
891 | |
892 /** | |
893 * Binds event listeners. | |
894 */ | |
895 function listen() { | |
896 document.addEventListener('DOMContentLoaded', init); | |
897 } | |
898 | |
899 return { | |
900 init: init, | |
901 listen: listen | |
902 }; | |
903 } | |
904 | |
905 if (!window.localNTPUnitTest) { | 918 if (!window.localNTPUnitTest) { |
906 LocalNTP().listen(); | 919 LocalNTP().listen(); |
907 } | 920 } |
OLD | NEW |