Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(63)

Side by Side Diff: chrome/browser/resources/ntp_search/new_tab.js

Issue 10823052: Refactoring the NTP. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @fileoverview New tab page
7 * This is the main code for the new tab page used by touch-enabled Chrome
8 * browsers. For now this is still a prototype.
9 */
10
11 // Use an anonymous function to enable strict mode just for this file (which
12 // will be concatenated with other files when embedded in Chrome
13 cr.define('ntp', function() {
14 'use strict';
15
16 /**
17 * NewTabView instance.
18 * @type {!Object|undefined}
19 */
20 var newTabView;
21
22 /**
23 * The 'notification-container' element.
24 * @type {!Element|undefined}
25 */
26 var notificationContainer;
27
28 /**
29 * If non-null, an info bubble for showing messages to the user. It points at
30 * the Most Visited label, and is used to draw more attention to the
31 * navigation dot UI.
32 * @type {!Element|undefined}
33 */
34 var infoBubble;
35
36 /**
37 * If non-null, an bubble confirming that the user has signed into sync. It
38 * points at the login status at the top of the page.
39 * @type {!Element|undefined}
40 */
41 var loginBubble;
42
43 /**
44 * true if |loginBubble| should be shown.
45 * @type {Boolean}
46 */
47 var shouldShowLoginBubble = false;
48
49 /**
50 * The 'other-sessions-menu-button' element.
51 * @type {!Element|undefined}
52 */
53 var otherSessionsButton;
54
55 /**
56 * The time in milliseconds for most transitions. This should match what's
57 * in new_tab.css. Unfortunately there's no better way to try to time
58 * something to occur until after a transition has completed.
59 * @type {number}
60 * @const
61 */
62 var DEFAULT_TRANSITION_TIME = 500;
63
64 /**
65 * See description for these values in ntp_stats.h.
66 * @enum {number}
67 */
68 var NtpFollowAction = {
69 CLICKED_TILE: 11,
70 CLICKED_OTHER_NTP_PANE: 12,
71 OTHER: 13
72 };
73
74 /**
75 * Creates a NewTabView object. NewTabView extends PageListView with
76 * new tab UI specific logics.
77 * @constructor
78 * @extends {PageListView}
79 */
80 function NewTabView() {
81 var pageSwitcherStart = null;
82 var pageSwitcherEnd = null;
83 if (loadTimeData.getValue('showApps')) {
84 pageSwitcherStart = getRequiredElement('page-switcher-start');
85 pageSwitcherEnd = getRequiredElement('page-switcher-end');
86 }
87 this.initialize(getRequiredElement('page-list'),
88 getRequiredElement('dot-list'),
89 getRequiredElement('card-slider-frame'),
90 getRequiredElement('trash'),
91 pageSwitcherStart, pageSwitcherEnd);
92 }
93
94 NewTabView.prototype = {
95 __proto__: ntp.PageListView.prototype,
96
97 /** @inheritDoc */
98 appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
99 ntp.PageListView.prototype.appendTilePage.apply(this, arguments);
100
101 if (infoBubble)
102 window.setTimeout(infoBubble.reposition.bind(infoBubble), 0);
103 }
104 };
105
106 /**
107 * Invoked at startup once the DOM is available to initialize the app.
108 */
109 function onLoad() {
110 sectionsToWaitFor = loadTimeData.getBoolean('showApps') ? 2 : 1;
111 if (loadTimeData.getBoolean('isSuggestionsPageEnabled'))
112 sectionsToWaitFor++;
113 measureNavDots();
114
115 // Load the current theme colors.
116 themeChanged();
117
118 newTabView = new NewTabView();
119
120 notificationContainer = getRequiredElement('notification-container');
121 notificationContainer.addEventListener(
122 'webkitTransitionEnd', onNotificationTransitionEnd);
123
124 cr.ui.decorate($('recently-closed-menu-button'), ntp.RecentMenuButton);
125 chrome.send('getRecentlyClosedTabs');
126
127 if (loadTimeData.getBoolean('showOtherSessionsMenu')) {
128 otherSessionsButton = getRequiredElement('other-sessions-menu-button');
129 cr.ui.decorate(otherSessionsButton, ntp.OtherSessionsMenuButton);
130 otherSessionsButton.initialize(loadTimeData.getBoolean('isUserSignedIn'));
131 }
132
133 var mostVisited = new ntp.MostVisitedPage();
134 // Move the footer into the most visited page if we are in "bare minimum"
135 // mode.
136 if (document.body.classList.contains('bare-minimum'))
137 mostVisited.appendFooter(getRequiredElement('footer'));
138 newTabView.appendTilePage(mostVisited,
139 loadTimeData.getString('mostvisited'),
140 false);
141 chrome.send('getMostVisited');
142
143 if (loadTimeData.getBoolean('isSuggestionsPageEnabled')) {
144 var suggestions_script = document.createElement('script');
145 suggestions_script.src = 'suggestions_page.js';
146 suggestions_script.onload = function() {
147 newTabView.appendTilePage(new ntp.SuggestionsPage(),
148 loadTimeData.getString('suggestions'),
149 false,
150 (newTabView.appsPages.length > 0) ?
151 newTabView.appsPages[0] : null);
152 chrome.send('getSuggestions');
153 cr.dispatchSimpleEvent(document, 'sectionready', true, true);
154 };
155 document.querySelector('head').appendChild(suggestions_script);
156 }
157
158 var webStoreLink = loadTimeData.getString('webStoreLink');
159 var url = appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher');
160 $('chrome-web-store-link').href = url;
161 $('chrome-web-store-link').addEventListener('click',
162 onChromeWebStoreButtonClick);
163
164 if (loadTimeData.getString('login_status_message')) {
165 loginBubble = new cr.ui.Bubble;
166 loginBubble.anchorNode = $('login-container');
167 loginBubble.setArrowLocation(cr.ui.ArrowLocation.TOP_END);
168 loginBubble.bubbleAlignment =
169 cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE;
170 loginBubble.deactivateToDismissDelay = 2000;
171 loginBubble.setCloseButtonVisible(false);
172
173 $('login-status-advanced').onclick = function() {
174 chrome.send('showAdvancedLoginUI');
175 };
176 $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble);
177
178 var bubbleContent = $('login-status-bubble-contents');
179 loginBubble.content = bubbleContent;
180
181 // The anchor node won't be updated until updateLogin is called so don't
182 // show the bubble yet.
183 shouldShowLoginBubble = true;
184 }
185
186 var loginContainer = getRequiredElement('login-container');
187 loginContainer.addEventListener('click', showSyncLoginUI);
188 chrome.send('initializeSyncLogin');
189
190 doWhenAllSectionsReady(function() {
191
192 // TODO(xci) new!
193 // TODO(pedrosimonetti): find a better place to put this code. Every,
194 // card needs to call layout the first time is shown.
195 newTabView.cardSlider.currentCardValue.layout_();
196
197 // Tell the slider about the pages.
198 newTabView.updateSliderCards();
199 // Mark the current page.
200 newTabView.cardSlider.currentCardValue.navigationDot.classList.add(
201 'selected');
202
203 if (loadTimeData.valueExists('serverpromo')) {
204 var promo = loadTimeData.getString('serverpromo');
205 var tags = ['IMG'];
206 var attrs = {
207 src: function(node, value) {
208 return node.tagName == 'IMG' &&
209 /^data\:image\/(?:png|gif|jpe?g)/.test(value);
210 },
211 };
212 showNotification(parseHtmlSubset(promo, tags, attrs), [], function() {
213 chrome.send('closeNotificationPromo');
214 }, 60000);
215 chrome.send('notificationPromoViewed');
216 }
217
218 cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true);
219 document.documentElement.classList.remove('starting-up');
220 });
221 }
222
223 /**
224 * Launches the chrome web store app with the chrome-ntp-launcher
225 * source.
226 * @param {Event} e The click event.
227 */
228 function onChromeWebStoreButtonClick(e) {
229 chrome.send('recordAppLaunchByURL',
230 [encodeURIComponent(this.href),
231 ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]);
232 }
233
234 /*
235 * The number of sections to wait on.
236 * @type {number}
237 */
238 var sectionsToWaitFor = -1;
239
240 /**
241 * Queued callbacks which lie in wait for all sections to be ready.
242 * @type {array}
243 */
244 var readyCallbacks = [];
245
246 /**
247 * Fired as each section of pages becomes ready.
248 * @param {Event} e Each page's synthetic DOM event.
249 */
250 document.addEventListener('sectionready', function(e) {
251 if (--sectionsToWaitFor <= 0) {
252 while (readyCallbacks.length) {
253 readyCallbacks.shift()();
254 }
255 }
256 });
257
258 /**
259 * This is used to simulate a fire-once event (i.e. $(document).ready() in
260 * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback
261 * is fired right away. If all pages are not ready yet, the function is queued
262 * for later execution.
263 * @param {function} callback The work to be done when ready.
264 */
265 function doWhenAllSectionsReady(callback) {
266 assert(typeof callback == 'function');
267 if (sectionsToWaitFor > 0)
268 readyCallbacks.push(callback);
269 else
270 window.setTimeout(callback, 0); // Do soon after, but asynchronously.
271 }
272
273 /**
274 * Fills in an invisible div with the 'Most Visited' string so that
275 * its length may be measured and the nav dots sized accordingly.
276 */
277 function measureNavDots() {
278 var measuringDiv = $('fontMeasuringDiv');
279 measuringDiv.textContent = loadTimeData.getString('mostvisited');
280 // The 4 is for border and padding.
281 var pxWidth = Math.max(measuringDiv.clientWidth * 1.15 + 4, 80);
282
283 var styleElement = document.createElement('style');
284 styleElement.type = 'text/css';
285 // max-width is used because if we run out of space, the nav dots will be
286 // shrunk.
287 styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }';
288 document.querySelector('head').appendChild(styleElement);
289 }
290
291 function themeChanged(opt_hasAttribution) {
292 $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now();
293
294 if (typeof opt_hasAttribution != 'undefined') {
295 document.documentElement.setAttribute('hasattribution',
296 opt_hasAttribution);
297 }
298
299 updateAttribution();
300 }
301
302 function setBookmarkBarAttached(attached) {
303 document.documentElement.setAttribute('bookmarkbarattached', attached);
304 }
305
306 /**
307 * Attributes the attribution image at the bottom left.
308 */
309 function updateAttribution() {
310 var attribution = $('attribution');
311 if (document.documentElement.getAttribute('hasattribution') == 'true') {
312 $('attribution-img').src =
313 'chrome://theme/IDR_THEME_NTP_ATTRIBUTION?' + Date.now();
314 attribution.hidden = false;
315 } else {
316 attribution.hidden = true;
317 }
318 }
319
320 /**
321 * Timeout ID.
322 * @type {number}
323 */
324 var notificationTimeout = 0;
325
326 /**
327 * Shows the notification bubble.
328 * @param {string|Node} message The notification message or node to use as
329 * message.
330 * @param {Array.<{text: string, action: function()}>} links An array of
331 * records describing the links in the notification. Each record should
332 * have a 'text' attribute (the display string) and an 'action' attribute
333 * (a function to run when the link is activated).
334 * @param {Function} opt_closeHandler The callback invoked if the user
335 * manually dismisses the notification.
336 */
337 function showNotification(message, links, opt_closeHandler, opt_timeout) {
338 window.clearTimeout(notificationTimeout);
339
340 var span = document.querySelector('#notification > span');
341 if (typeof message == 'string') {
342 span.textContent = message;
343 } else {
344 span.textContent = ''; // Remove all children.
345 span.appendChild(message);
346 }
347
348 var linksBin = $('notificationLinks');
349 linksBin.textContent = '';
350 for (var i = 0; i < links.length; i++) {
351 var link = linksBin.ownerDocument.createElement('div');
352 link.textContent = links[i].text;
353 link.action = links[i].action;
354 link.onclick = function() {
355 this.action();
356 hideNotification();
357 };
358 link.setAttribute('role', 'button');
359 link.setAttribute('tabindex', 0);
360 link.className = 'link-button';
361 linksBin.appendChild(link);
362 }
363
364 function closeFunc(e) {
365 if (opt_closeHandler)
366 opt_closeHandler();
367 hideNotification();
368 }
369
370 document.querySelector('#notification button').onclick = closeFunc;
371 document.addEventListener('dragstart', closeFunc);
372
373 notificationContainer.hidden = false;
374 showNotificationOnCurrentPage();
375
376 newTabView.cardSlider.frame.addEventListener(
377 'cardSlider:card_change_ended', onCardChangeEnded);
378
379 var timeout = opt_timeout || 10000;
380 notificationTimeout = window.setTimeout(hideNotification, timeout);
381 }
382
383 /**
384 * Hide the notification bubble.
385 */
386 function hideNotification() {
387 notificationContainer.classList.add('inactive');
388
389 newTabView.cardSlider.frame.removeEventListener(
390 'cardSlider:card_change_ended', onCardChangeEnded);
391 }
392
393 /**
394 * Happens when 1 or more consecutive card changes end.
395 * @param {Event} e The cardSlider:card_change_ended event.
396 */
397 function onCardChangeEnded(e) {
398 // If we ended on the same page as we started, ignore.
399 if (newTabView.cardSlider.currentCardValue.notification)
400 return;
401
402 // Hide the notification the old page.
403 notificationContainer.classList.add('card-changed');
404
405 showNotificationOnCurrentPage();
406 }
407
408 /**
409 * Move and show the notification on the current page.
410 */
411 function showNotificationOnCurrentPage() {
412 var page = newTabView.cardSlider.currentCardValue;
413 doWhenAllSectionsReady(function() {
414 if (page != newTabView.cardSlider.currentCardValue)
415 return;
416
417 // NOTE: This moves the notification to inside of the current page.
418 page.notification = notificationContainer;
419
420 // Reveal the notification and instruct it to hide itself if ignored.
421 notificationContainer.classList.remove('inactive');
422
423 // Gives the browser time to apply this rule before we remove it (causing
424 // a transition).
425 window.setTimeout(function() {
426 notificationContainer.classList.remove('card-changed');
427 }, 0);
428 });
429 }
430
431 /**
432 * When done fading out, set hidden to true so the notification can't be
433 * tabbed to or clicked.
434 * @param {Event} e The webkitTransitionEnd event.
435 */
436 function onNotificationTransitionEnd(e) {
437 if (notificationContainer.classList.contains('inactive'))
438 notificationContainer.hidden = true;
439 }
440
441 function setRecentlyClosedTabs(dataItems) {
442 $('recently-closed-menu-button').dataItems = dataItems;
443 }
444
445 function setMostVisitedPages(data, hasBlacklistedUrls) {
446 newTabView.mostVisitedPage.data = data;
447 cr.dispatchSimpleEvent(document, 'sectionready', true, true);
448 }
449
450 function setSuggestionsPages(data, hasBlacklistedUrls) {
451 newTabView.suggestionsPage.data = data;
452 }
453
454 function getThumbnailUrl(url) {
455 return 'chrome://thumb/' + url;
456 }
457
458 /**
459 * Set the dominant color for a node. This will be called in response to
460 * getFaviconDominantColor. The node represented by |id| better have a setter
461 * for stripeColor.
462 * @param {string} id The ID of a node.
463 * @param {string} color The color represented as a CSS string.
464 */
465 function setStripeColor(id, color) {
466 var node = $(id);
467 if (node)
468 node.stripeColor = color;
469 }
470
471 /**
472 * Updates the text displayed in the login container. If there is no text then
473 * the login container is hidden.
474 * @param {string} loginHeader The first line of text.
475 * @param {string} loginSubHeader The second line of text.
476 * @param {string} iconURL The url for the login status icon. If this is null
477 then the login status icon is hidden.
478 * @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
479 */
480 function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) {
481 if (loginHeader || loginSubHeader) {
482 $('login-container').hidden = false;
483 $('login-status-header').innerHTML = loginHeader;
484 $('login-status-sub-header').innerHTML = loginSubHeader;
485 $('card-slider-frame').classList.add('showing-login-area');
486
487 if (iconURL) {
488 $('login-status-header-container').style.backgroundImage = url(iconURL);
489 $('login-status-header-container').classList.add('login-status-icon');
490 } else {
491 $('login-status-header-container').style.backgroundImage = 'none';
492 $('login-status-header-container').classList.remove(
493 'login-status-icon');
494 }
495 } else {
496 $('login-container').hidden = true;
497 $('card-slider-frame').classList.remove('showing-login-area');
498 }
499 if (shouldShowLoginBubble) {
500 window.setTimeout(loginBubble.show.bind(loginBubble), 0);
501 chrome.send('loginMessageSeen');
502 shouldShowLoginBubble = false;
503 } else if (loginBubble) {
504 loginBubble.reposition();
505 }
506 if (otherSessionsButton)
507 otherSessionsButton.updateSignInState(isUserSignedIn);
508 }
509
510 /**
511 * Show the sync login UI.
512 * @param {Event} e The click event.
513 */
514 function showSyncLoginUI(e) {
515 var rect = e.currentTarget.getBoundingClientRect();
516 chrome.send('showSyncLoginUI',
517 [rect.left, rect.top, rect.width, rect.height]);
518 }
519
520 /**
521 * Wrappers to forward the callback to corresponding PageListView member.
522 */
523 function appAdded() {
524 return newTabView.appAdded.apply(newTabView, arguments);
525 }
526
527 function appMoved() {
528 return newTabView.appMoved.apply(newTabView, arguments);
529 }
530
531 function appRemoved() {
532 return newTabView.appRemoved.apply(newTabView, arguments);
533 }
534
535 function appsPrefChangeCallback() {
536 return newTabView.appsPrefChangedCallback.apply(newTabView, arguments);
537 }
538
539 function appsReordered() {
540 return newTabView.appsReordered.apply(newTabView, arguments);
541 }
542
543 function enterRearrangeMode() {
544 return newTabView.enterRearrangeMode.apply(newTabView, arguments);
545 }
546
547 function setForeignSessions(sessionList, isTabSyncEnabled) {
548 if (otherSessionsButton)
549 otherSessionsButton.setForeignSessions(sessionList, isTabSyncEnabled);
550 }
551
552 function getAppsCallback() {
553 return newTabView.getAppsCallback.apply(newTabView, arguments);
554 }
555
556 function getAppsPageIndex() {
557 return newTabView.getAppsPageIndex.apply(newTabView, arguments);
558 }
559
560 function getCardSlider() {
561 return newTabView.cardSlider;
562 }
563
564 function leaveRearrangeMode() {
565 return newTabView.leaveRearrangeMode.apply(newTabView, arguments);
566 }
567
568 function saveAppPageName() {
569 return newTabView.saveAppPageName.apply(newTabView, arguments);
570 }
571
572 function setAppToBeHighlighted(appId) {
573 newTabView.highlightAppId = appId;
574 }
575
576 // Return an object with all the exports
577 return {
578 appAdded: appAdded,
579 appMoved: appMoved,
580 appRemoved: appRemoved,
581 appsPrefChangeCallback: appsPrefChangeCallback,
582 enterRearrangeMode: enterRearrangeMode,
583 getAppsCallback: getAppsCallback,
584 getAppsPageIndex: getAppsPageIndex,
585 getCardSlider: getCardSlider,
586 onLoad: onLoad,
587 leaveRearrangeMode: leaveRearrangeMode,
588 NtpFollowAction: NtpFollowAction,
589 saveAppPageName: saveAppPageName,
590 setAppToBeHighlighted: setAppToBeHighlighted,
591 setBookmarkBarAttached: setBookmarkBarAttached,
592 setForeignSessions: setForeignSessions,
593 setMostVisitedPages: setMostVisitedPages,
594 setSuggestionsPages: setSuggestionsPages,
595 setRecentlyClosedTabs: setRecentlyClosedTabs,
596 getThumbnailUrl: getThumbnailUrl,
jeremycho_google 2012/07/31 03:09:16 Alphabetize.
pedrosimonetti2 2012/08/03 18:14:01 Done.
597 setStripeColor: setStripeColor,
598 showNotification: showNotification,
599 themeChanged: themeChanged,
600 updateLogin: updateLogin
601 };
602 });
603
604 document.addEventListener('DOMContentLoaded', ntp.onLoad);
605
606 var toCssPx = cr.ui.toCssPx;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698