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

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

Issue 8036002: ntp: remove ntp3 resources (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 9 years, 3 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 // To avoid creating tons of unnecessary nodes. We assume we cannot fit more
6 // than this many items in the miniview.
7 var MAX_MINIVIEW_ITEMS = 15;
8
9 // Extra spacing at the top of the layout.
10 var LAYOUT_SPACING_TOP = 25;
11
12 // The visible height of the expanded maxiview.
13 var maxiviewVisibleHeight = 0;
14
15 var APP_LAUNCH = {
16 // The histogram buckets (keep in sync with extension_constants.h).
17 NTP_APPS_MAXIMIZED: 0,
18 NTP_APPS_COLLAPSED: 1,
19 NTP_APPS_MENU: 2,
20 NTP_MOST_VISITED: 3,
21 NTP_RECENTLY_CLOSED: 4,
22 NTP_APP_RE_ENABLE: 16
23 };
24
25 var APP_LAUNCH_URL = {
26 // The URL prefix for pings that record app launches by URL.
27 PING_BY_URL: 'record-app-launch-by-url',
28
29 // The URL prefix for pings that record app launches by ID.
30 PING_BY_ID: 'record-app-launch-by-id',
31
32 // The URL prefix used by the webstore link 'ping' attributes.
33 PING_WEBSTORE: 'record-webstore-launch'
34 };
35
36 function getAppPingUrl(prefix, data, bucket) {
37 return [APP_LAUNCH_URL[prefix],
38 encodeURIComponent(data),
39 APP_LAUNCH[bucket]].join('+');
40 }
41
42 function getSectionCloseButton(sectionId) {
43 return document.querySelector('#' + sectionId + ' .section-close-button');
44 }
45
46 function getSectionMenuButton(sectionId) {
47 return $(sectionId + '-button');
48 }
49
50 function getSectionMenuButtonTextId(sectionId) {
51 return sectionId.replace(/-/g, '');
52 }
53
54 function setSectionMenuMode(sectionId, section, menuModeEnabled, menuModeMask) {
55 var el = $(sectionId);
56 if (!menuModeEnabled) {
57 // Because sections are collapsed when they are in menu mode, it is not
58 // necessary to restore the maxiview here. It will happen if the section
59 // header is clicked.
60 // TODO(aa): Sections should maintain their collapse state when minimized.
61 el.classList.remove('menu');
62 shownSections &= ~menuModeMask;
63 } else {
64 if (section) {
65 hideSection(section); // To hide the maxiview.
66 }
67 el.classList.add('menu');
68 shownSections |= menuModeMask;
69 }
70 layoutSections();
71 }
72
73 function clearClosedMenu(menu) {
74 menu.innerHTML = '';
75 }
76
77 function addClosedMenuEntryWithLink(menu, a) {
78 var span = document.createElement('span');
79 a.className += ' item menuitem';
80 span.appendChild(a);
81 menu.appendChild(span);
82 }
83
84 function addClosedMenuEntry(menu, url, title, imageUrl, opt_pingUrl) {
85 var a = document.createElement('a');
86 a.href = url;
87 a.textContent = title;
88 a.style.backgroundImage = 'url(' + imageUrl + ')';
89 if (opt_pingUrl)
90 a.ping = opt_pingUrl;
91 addClosedMenuEntryWithLink(menu, a);
92 }
93
94 function addClosedMenuFooter(menu, sectionId, mask, opt_section) {
95 menu.appendChild(document.createElement('hr'));
96
97 var span = document.createElement('span');
98 var a = span.appendChild(document.createElement('a'));
99 a.href = '';
100 if (cr.isChromeOS) {
101 a.textContent = localStrings.getString('expandMenu');
102 } else {
103 a.textContent =
104 localStrings.getString(getSectionMenuButtonTextId(sectionId));
105 }
106 a.className = 'item';
107 a.addEventListener(
108 'click',
109 function(e) {
110 getSectionMenuButton(sectionId).hideMenu();
111 e.preventDefault();
112 setSectionMenuMode(sectionId, opt_section, false, mask);
113 shownSections &= ~mask;
114 saveShownSections();
115 });
116 menu.appendChild(span);
117 }
118
119 function initializeSection(sectionId, mask, opt_section) {
120 var button = getSectionCloseButton(sectionId);
121 button.addEventListener(
122 'click',
123 function() {
124 setSectionMenuMode(sectionId, opt_section, true, mask);
125 saveShownSections();
126 });
127 }
128
129 function updateSimpleSection(id, section) {
130 var elm = $(id);
131 var maxiview = getSectionMaxiview(elm);
132 var miniview = getSectionMiniview(elm);
133 if (shownSections & section) {
134 // The section is expanded, so the maxiview should be opaque (visible) and
135 // the miniview should be hidden.
136 elm.classList.remove('collapsed');
137 if (maxiview) {
138 maxiview.classList.remove('collapsed');
139 maxiview.classList.add('opaque');
140 }
141 if (miniview)
142 miniview.classList.remove('opaque');
143 } else {
144 // The section is collapsed, so the maxiview should be hidden and the
145 // miniview should be opaque.
146 elm.classList.add('collapsed');
147 if (maxiview) {
148 maxiview.classList.add('collapsed');
149 maxiview.classList.remove('opaque');
150 }
151 if (miniview)
152 miniview.classList.add('opaque');
153 }
154 }
155
156 var sessionItems = [];
157
158 function foreignSessions(data) {
159 logEvent('received foreign sessions');
160 // We need to store the foreign sessions so we can update the layout on a
161 // resize.
162 sessionItems = data;
163 renderForeignSessions();
164 layoutSections();
165 }
166
167 function renderForeignSessions() {
168 // Remove all existing items and create new items.
169 var sessionElement = $('foreign-sessions');
170 var parentSessionElement = sessionElement.lastElementChild;
171 parentSessionElement.textContent = '';
172
173 // For each client, create entries and append the lists together.
174 sessionItems.forEach(function(item, i) {
175 // TODO(zea): Get real client names. See crbug/59672.
176 var name = 'Client ' + i;
177 parentSessionElement.appendChild(createForeignSession(item, name));
178 });
179
180 layoutForeignSessions();
181 }
182
183 function layoutForeignSessions() {
184 var sessionElement = $('foreign-sessions');
185 // We cannot use clientWidth here since the width has a transition.
186 var availWidth = useSmallGrid() ? 692 : 920;
187 var parentSessEl = sessionElement.lastElementChild;
188
189 if (parentSessEl.hasChildNodes()) {
190 sessionElement.classList.remove('disabled');
191 sessionElement.classList.remove('opaque');
192 } else {
193 sessionElement.classList.add('disabled');
194 sessionElement.classList.add('opaque');
195 }
196 }
197
198 function createForeignSession(client, name) {
199 // Vertically stack the windows in a client.
200 var stack = document.createElement('div');
201 stack.className = 'foreign-session-client item link';
202 stack.textContent = name;
203 stack.sessionTag = client[0].sessionTag;
204
205 client.forEach(function(win, i) {
206 // Create a window entry.
207 var winSpan = document.createElement('span');
208 var winEl = document.createElement('p');
209 winEl.className = 'item link window';
210 winEl.tabItems = win.tabs;
211 winEl.tabIndex = 0;
212 winEl.textContent = formatTabsText(win.tabs.length);
213 winEl.xtitle = win.title;
214 winEl.sessionTag = win.sessionTag;
215 winEl.winNum = i;
216 winEl.addEventListener('click', maybeOpenForeignWindow);
217 winEl.addEventListener('keydown',
218 handleIfEnterKey(maybeOpenForeignWindow));
219 winSpan.appendChild(winEl);
220
221 // Sort tabs by MRU order
222 win.tabs.sort(function(a, b) {
223 return a.timestamp < b.timestamp;
224 });
225
226 // Create individual tab information.
227 win.tabs.forEach(function(data) {
228 var tabEl = document.createElement('a');
229 tabEl.className = 'item link tab';
230 tabEl.href = data.timestamp;
231 tabEl.style.backgroundImage = url('chrome://favicon/' + data.url);
232 tabEl.dir = data.direction;
233 tabEl.textContent = data.title;
234 tabEl.sessionTag = win.sessionTag;
235 tabEl.winNum = i;
236 tabEl.sessionId = data.sessionId;
237 tabEl.addEventListener('click', maybeOpenForeignTab);
238 tabEl.addEventListener('keydown',
239 handleIfEnterKey(maybeOpenForeignTab));
240
241 winSpan.appendChild(tabEl);
242 });
243
244 // Append the window.
245 stack.appendChild(winSpan);
246 });
247 return stack;
248 }
249
250 var recentItems = [];
251
252 function recentlyClosedTabs(data) {
253 logEvent('received recently closed tabs');
254 // We need to store the recent items so we can update the layout on a resize.
255 recentItems = data;
256 renderRecentlyClosed();
257 layoutSections();
258 }
259
260 function renderRecentlyClosed() {
261 // Remove all existing items and create new items.
262 var recentElement = $('recently-closed');
263 var parentEl = recentElement.lastElementChild;
264 parentEl.textContent = '';
265 var recentMenu = $('recently-closed-menu');
266 clearClosedMenu(recentMenu);
267
268 recentItems.forEach(function(item) {
269 parentEl.appendChild(createRecentItem(item));
270 addRecentMenuItem(recentMenu, item);
271 });
272 addClosedMenuFooter(recentMenu, 'recently-closed', MENU_RECENT);
273
274 layoutRecentlyClosed();
275 }
276
277 function createRecentItem(data) {
278 var isWindow = data.type == 'window';
279 var el;
280 if (isWindow) {
281 el = document.createElement('span');
282 el.className = 'item link window';
283 el.tabItems = data.tabs;
284 el.tabIndex = 0;
285 el.textContent = formatTabsText(data.tabs.length);
286 } else {
287 el = document.createElement('a');
288 el.className = 'item';
289 el.href = data.url;
290 el.ping = getAppPingUrl(
291 'PING_BY_URL', data.url, 'NTP_RECENTLY_CLOSED');
292 el.style.backgroundImage = url('chrome://favicon/' + data.url);
293 el.dir = data.direction;
294 el.textContent = data.title;
295 }
296 el.sessionId = data.sessionId;
297 el.xtitle = data.title;
298 el.sessionTag = data.sessionTag;
299 var wrapperEl = document.createElement('span');
300 wrapperEl.appendChild(el);
301 return wrapperEl;
302 }
303
304 function addRecentMenuItem(menu, data) {
305 var isWindow = data.type == 'window';
306 var a = document.createElement('a');
307 if (isWindow) {
308 a.textContent = formatTabsText(data.tabs.length);
309 a.className = 'window'; // To get the icon from the CSS .window rule.
310 a.href = ''; // To make underline show up.
311 } else {
312 a.href = data.url;
313 a.ping = getAppPingUrl(
314 'PING_BY_URL', data.url, 'NTP_RECENTLY_CLOSED');
315 a.style.backgroundImage = 'url(chrome://favicon/' + data.url + ')';
316 a.textContent = data.title;
317 }
318 function clickHandler(e) {
319 chrome.send('reopenTab', [String(data.sessionId)]);
320 e.preventDefault();
321 }
322 a.addEventListener('click', clickHandler);
323 addClosedMenuEntryWithLink(menu, a);
324 }
325
326 function saveShownSections() {
327 chrome.send('setShownSections', [shownSections]);
328 }
329
330 var LayoutMode = {
331 SMALL: 1,
332 NORMAL: 2
333 };
334
335 var layoutMode = useSmallGrid() ? LayoutMode.SMALL : LayoutMode.NORMAL;
336
337 function handleWindowResize() {
338 if (window.innerWidth < 10) {
339 // We're probably a background tab, so don't do anything.
340 return;
341 }
342
343 // TODO(jstritar): Remove the small-layout class and revert back to the
344 // @media (max-width) directive once http://crbug.com/70930 is fixed.
345 var oldLayoutMode = layoutMode;
346 var b = useSmallGrid();
347 if (b) {
348 layoutMode = LayoutMode.SMALL;
349 document.body.classList.add('small-layout');
350 } else {
351 layoutMode = LayoutMode.NORMAL;
352 document.body.classList.remove('small-layout');
353 }
354
355 if (layoutMode != oldLayoutMode){
356 mostVisited.useSmallGrid = b;
357 mostVisited.layout();
358 apps.layout({force:true});
359 renderRecentlyClosed();
360 renderForeignSessions();
361 updateAllMiniviewClippings();
362 }
363
364 layoutSections();
365 }
366
367 // Stores some information about each section necessary to layout. A new
368 // instance is constructed for each section on each layout.
369 function SectionLayoutInfo(section) {
370 this.section = section;
371 this.header = section.querySelector('h2');
372 this.miniview = section.querySelector('.miniview');
373 this.maxiview = getSectionMaxiview(section);
374 this.expanded = this.maxiview && !section.classList.contains('collapsed');
375 this.fixedHeight = this.section.offsetHeight;
376 this.scrollingHeight = 0;
377
378 if (this.expanded)
379 this.scrollingHeight = this.maxiview.offsetHeight;
380 }
381
382 // Get all sections to be layed out.
383 SectionLayoutInfo.getAll = function() {
384 var sections = document.querySelectorAll(
385 '.section:not(.disabled):not(.menu)');
386 var result = [];
387 for (var i = 0, section; section = sections[i]; i++) {
388 result.push(new SectionLayoutInfo(section));
389 }
390 return result;
391 };
392
393 // Ensure the miniview sections don't have any clipped items.
394 function updateMiniviewClipping(miniview) {
395 var clipped = false;
396 for (var j = 0, item; item = miniview.children[j]; j++) {
397 item.style.display = '';
398 if (clipped ||
399 (item.offsetLeft + item.offsetWidth) > miniview.offsetWidth) {
400 item.style.display = 'none';
401 clipped = true;
402 } else {
403 item.style.display = '';
404 }
405 }
406 }
407
408 // Ensure none of the miniviews have any clipped items.
409 function updateAllMiniviewClippings() {
410 var miniviews = document.querySelectorAll('.section.collapsed .miniview');
411 for (var i = 0, miniview; miniview = miniviews[i]; i++) {
412 updateMiniviewClipping(miniview);
413 }
414 }
415
416 // Returns whether or not vertical scrollbars are present.
417 function hasScrollBars() {
418 return window.innerHeight != document.body.clientHeight;
419 }
420
421 // Enables scrollbars (they will only show up if needed).
422 function showScrollBars() {
423 document.body.classList.remove('noscroll');
424 }
425
426 // Hides all scrollbars.
427 function hideScrollBars() {
428 document.body.classList.add('noscroll');
429 }
430
431 // Returns whether or not the sections are currently animating due to a
432 // section transition.
433 function isAnimating() {
434 var de = document.documentElement;
435 return de.getAttribute('enable-section-animations') == 'true';
436 }
437
438 // Layout the sections in a modified accordion. The header and miniview, if
439 // visible are fixed within the viewport. If there is an expanded section, its
440 // it scrolls.
441 //
442 // =============================
443 // | collapsed section | <- Any collapsed sections are fixed position.
444 // | and miniview |
445 // |---------------------------|
446 // | expanded section |
447 // | | <- There can be one expanded section and it
448 // | and maxiview | is absolutely positioned so that it can
449 // | | scroll "underneath" the fixed elements.
450 // | |
451 // |---------------------------|
452 // | another collapsed section |
453 // |---------------------------|
454 //
455 // We want the main frame scrollbar to be the one that scrolls the expanded
456 // region. To get this effect, we make the fixed elements position:fixed and the
457 // scrollable element position:absolute. We also artificially increase the
458 // height of the document so that it is possible to scroll down enough to
459 // display the end of the document, even with any fixed elements at the bottom
460 // of the viewport.
461 //
462 // There is a final twist: If the intrinsic height of the expanded section is
463 // less than the available height (because the window is tall), any collapsed
464 // sections sinch up and sit below the expanded section. This is so that we
465 // don't have a bunch of dead whitespace in the case of expanded sections that
466 // aren't very tall.
467 function layoutSections() {
468 // While transitioning sections, we only want scrollbars to appear if they're
469 // already present or the window is being resized (so there's no animation).
470 if (!hasScrollBars() && isAnimating())
471 hideScrollBars();
472
473 var sections = SectionLayoutInfo.getAll();
474 var expandedSection = null;
475 var headerHeight = LAYOUT_SPACING_TOP;
476 var footerHeight = 0;
477
478 // Calculate the height of the fixed elements above the expanded section. Also
479 // take note of the expanded section, if there is one.
480 var i;
481 var section;
482 for (i = 0; section = sections[i]; i++) {
483 headerHeight += section.fixedHeight;
484 if (section.expanded) {
485 expandedSection = section;
486 i++;
487 break;
488 }
489 }
490
491 // Include the height of the sync promo bar.
492 var sync_promo_height = $('sync-promo').offsetHeight;
493 headerHeight += sync_promo_height;
494
495 // Calculate the height of the fixed elements below the expanded section, if
496 // any.
497 for (; section = sections[i]; i++) {
498 footerHeight += section.fixedHeight;
499 }
500 // Leave room for bottom bar if it's visible.
501 footerHeight += $('closed-sections-bar').offsetHeight;
502
503
504 // Determine the height to use for the expanded section. If there isn't enough
505 // space to show the expanded section completely, this will be the available
506 // height. Otherwise, we use the intrinsic height of the expanded section.
507 var expandedSectionHeight;
508 var expandedSectionIsClipped = false;
509 if (expandedSection) {
510 var flexHeight = window.innerHeight - headerHeight - footerHeight;
511 if (flexHeight < expandedSection.scrollingHeight) {
512 expandedSectionHeight = flexHeight;
513
514 // Also, artificially expand the height of the document so that we can see
515 // the entire expanded section.
516 //
517 // TODO(aa): Where does this come from? It is the difference between what
518 // we set document.body.style.height to and what
519 // document.body.scrollHeight measures afterward. I expect them to be the
520 // same if document.body has no margins.
521 var fudge = 44;
522 document.body.style.height =
523 headerHeight +
524 expandedSection.scrollingHeight +
525 footerHeight +
526 fudge +
527 'px';
528 expandedSectionIsClipped = true;
529 } else {
530 expandedSectionHeight = expandedSection.scrollingHeight;
531 document.body.style.height = '';
532 }
533 } else {
534 // We only set the document height when a section is expanded. If
535 // all sections are collapsed, then get rid of the previous height.
536 document.body.style.height = '';
537 }
538
539 maxiviewVisibleHeight = expandedSectionHeight;
540
541 // Now position all the elements.
542 var y = LAYOUT_SPACING_TOP + sync_promo_height;
543 for (i = 0, section; section = sections[i]; i++) {
544 section.section.style.top = y + 'px';
545 y += section.fixedHeight;
546
547 if (section.maxiview) {
548 if (section == expandedSection) {
549 section.maxiview.style.top = y + 'px';
550 } else {
551 // The miniviews fade out gradually, so it may have height at this
552 // point. We position the maxiview as if the miniview was not displayed
553 // by subtracting off the miniview's total height (height + margin).
554 var miniviewFudge = 40; // miniview margin-bottom + margin-top
555 var miniviewHeight = section.miniview.offsetHeight + miniviewFudge;
556 section.maxiview.style.top = y - miniviewHeight + 'px';
557 }
558 }
559
560 if (section.maxiview && section == expandedSection)
561 updateMask(
562 section.maxiview, expandedSectionHeight, expandedSectionIsClipped);
563
564 if (section == expandedSection)
565 y += expandedSectionHeight;
566 }
567 if (cr.isChromeOS)
568 $('closed-sections-bar').style.top = y + 'px';
569
570 // Position the notification container below the sync promo.
571 $('notification-container').style.top = sync_promo_height + 'px';
572
573 updateMenuSections();
574 updateAttributionDisplay(y);
575 }
576
577 function updateMask(maxiview, visibleHeightPx, isClipped) {
578 // If the section isn't actually clipped, then we don't want to use a mask at
579 // all, since enabling one turns off subpixel anti-aliasing.
580 if (!isClipped) {
581 maxiview.style.WebkitMaskImage = 'none';
582 return;
583 }
584
585 // We want to end up with 10px gradients at the top and bottom of
586 // visibleHeight, but webkit-mask only supports expression in terms of
587 // percentages.
588
589 // We might not have enough room to do 10px gradients on each side. To get the
590 // right effect, we don't want to make the gradients smaller, but make them
591 // appear to mush into each other.
592 var gradientHeightPx = Math.min(10, Math.floor(visibleHeightPx / 2));
593 var gradientDestination = 'rgba(0,0,0,' + (gradientHeightPx / 10) + ')';
594
595 var bottomSpacing = 15;
596 var first = parseFloat(maxiview.style.top) / window.innerHeight;
597 var second = first + gradientHeightPx / window.innerHeight;
598 var fourth = first + (visibleHeightPx - bottomSpacing) / window.innerHeight;
599 var third = fourth - gradientHeightPx / window.innerHeight;
600
601 var gradientArguments = [
602 'transparent',
603 getColorStopString(first, 'transparent'),
604 getColorStopString(second, gradientDestination),
605 getColorStopString(third, gradientDestination),
606 getColorStopString(fourth, 'transparent'),
607 'transparent'
608 ];
609
610 var gradient = '-webkit-linear-gradient(' + gradientArguments.join(',') + ')';
611 maxiview.style.WebkitMaskImage = gradient;
612 }
613
614 function getColorStopString(height, color) {
615 // TODO(arv): The CSS3 gradient syntax allows px units so we should simplify
616 // this to use pixels instead.
617 return color + ' ' + height * 100 + '%';
618 }
619
620 // Updates the visibility of the menu buttons for each section, based on
621 // whether they are currently enabled and in menu mode.
622 function updateMenuSections() {
623 var elms = document.getElementsByClassName('section');
624 for (var i = 0, elm; elm = elms[i]; i++) {
625 var button = getSectionMenuButton(elm.id);
626 if (!button)
627 continue;
628
629 if (!elm.classList.contains('disabled') &&
630 elm.classList.contains('menu')) {
631 button.style.display = 'inline-block';
632 } else {
633 button.style.display = 'none';
634 }
635 }
636 }
637
638 window.addEventListener('resize', handleWindowResize);
639
640 var sectionToElementMap;
641 function getSectionElement(section) {
642 if (!sectionToElementMap) {
643 sectionToElementMap = {};
644 for (var key in Section) {
645 sectionToElementMap[Section[key]] =
646 document.querySelector('.section[section=' + key + ']');
647 }
648 }
649 return sectionToElementMap[section];
650 }
651
652 function getSectionMaxiview(section) {
653 return $(section.id + '-maxiview');
654 }
655
656 function getSectionMiniview(section) {
657 return section.querySelector('.miniview');
658 }
659
660 // You usually want to call |showOnlySection()| instead of this.
661 function showSection(section) {
662 if (!(section & shownSections)) {
663 shownSections |= section;
664 var el = getSectionElement(section);
665 if (el) {
666 el.classList.remove('collapsed');
667
668 var maxiview = getSectionMaxiview(el);
669 if (maxiview) {
670 maxiview.classList.remove('collapsing');
671 maxiview.classList.remove('collapsed');
672 // The opacity won't transition if you toggle the display property
673 // at the same time. To get a fade effect, we set the opacity
674 // asynchronously from another function, after the display is toggled.
675 // 1) 'collapsed' (display: none, opacity: 0)
676 // 2) none (display: block, opacity: 0)
677 // 3) 'opaque' (display: block, opacity: 1)
678 setTimeout(function () {
679 maxiview.classList.add('opaque');
680 }, 0);
681 }
682
683 var miniview = getSectionMiniview(el);
684 if (miniview) {
685 // The miniview is hidden immediately (no need to set this async).
686 miniview.classList.remove('opaque');
687 }
688 }
689
690 switch (section) {
691 case Section.THUMB:
692 mostVisited.visible = true;
693 mostVisited.layout();
694 break;
695 case Section.APPS:
696 apps.visible = true;
697 apps.layout({disableAnimations:true});
698 break;
699 }
700 }
701 }
702
703 // Show this section and hide all other sections - at most one section can
704 // be open at one time.
705 function showOnlySection(section) {
706 for (var p in Section) {
707 if (p == section)
708 showSection(Section[p]);
709 else
710 hideSection(Section[p]);
711 }
712 }
713
714 function hideSection(section) {
715 if (section & shownSections) {
716 shownSections &= ~section;
717
718 switch (section) {
719 case Section.THUMB:
720 mostVisited.visible = false;
721 mostVisited.layout();
722 break;
723 case Section.APPS:
724 apps.visible = false;
725 apps.layout();
726 break;
727 }
728
729 var el = getSectionElement(section);
730 if (el) {
731 el.classList.add('collapsed');
732
733 var maxiview = getSectionMaxiview(el);
734 if (maxiview) {
735 maxiview.classList.add((isDoneLoading() && isAnimating()) ?
736 'collapsing' : 'collapsed');
737 maxiview.classList.remove('opaque');
738 }
739
740 var miniview = getSectionMiniview(el);
741 if (miniview) {
742 // We need to set this asynchronously to properly get the fade effect.
743 setTimeout(function() {
744 miniview.classList.add('opaque');
745 }, 0);
746 updateMiniviewClipping(miniview);
747 }
748 }
749 }
750 }
751
752 window.addEventListener('webkitTransitionEnd', function(e) {
753 if (e.target.classList.contains('collapsing')) {
754 e.target.classList.add('collapsed');
755 e.target.classList.remove('collapsing');
756 }
757
758 if (e.target.classList.contains('maxiview') ||
759 e.target.classList.contains('miniview')) {
760 document.documentElement.removeAttribute('enable-section-animations');
761 showScrollBars();
762 }
763 });
764
765 /**
766 * Callback when the shown sections changes in another NTP.
767 * @param {number} newShownSections Bitmask of the shown sections.
768 */
769 function setShownSections(newShownSections) {
770 for (var key in Section) {
771 if (newShownSections & Section[key])
772 showSection(Section[key]);
773 else
774 hideSection(Section[key]);
775 }
776 setSectionMenuMode('apps', Section.APPS, newShownSections & MENU_APPS,
777 MENU_APPS);
778 setSectionMenuMode('most-visited', Section.THUMB,
779 newShownSections & MENU_THUMB, MENU_THUMB);
780 setSectionMenuMode('recently-closed', undefined,
781 newShownSections & MENU_RECENT, MENU_RECENT);
782 layoutSections();
783 }
784
785 // Recently closed
786
787 function layoutRecentlyClosed() {
788 var recentElement = $('recently-closed');
789 var miniview = getSectionMiniview(recentElement);
790
791 updateMiniviewClipping(miniview);
792
793 if (miniview.hasChildNodes()) {
794 recentElement.classList.remove('disabled');
795 miniview.classList.add('opaque');
796 } else {
797 recentElement.classList.add('disabled');
798 miniview.classList.remove('opaque');
799 }
800
801 layoutSections();
802 }
803
804 /**
805 * This function is called by the backend whenever the sync status section
806 * needs to be updated to reflect recent sync state changes. The backend passes
807 * the new status information in the newMessage parameter. The state includes
808 * the following:
809 *
810 * syncsectionisvisible: true if the sync section needs to show up on the new
811 * tab page and false otherwise.
812 * title: the header for the sync status section.
813 * msg: the actual message (e.g. "Synced to foo@gmail.com").
814 * linkisvisible: true if the link element should be visible within the sync
815 * section and false otherwise.
816 * linktext: the text to display as the link in the sync status (only used if
817 * linkisvisible is true).
818 * linkurlisset: true if an URL should be set as the href for the link and false
819 * otherwise. If this field is false, then clicking on the link
820 * will result in sending a message to the backend (see
821 * 'SyncLinkClicked').
822 * linkurl: the URL to use as the element's href (only used if linkurlisset is
823 * true).
824 */
825 function syncMessageChanged(newMessage) {
826 var syncStatusElement = $('sync-status');
827
828 // Hide the section if the message is emtpy.
829 if (!newMessage['syncsectionisvisible']) {
830 syncStatusElement.classList.add('disabled');
831 return;
832 }
833
834 syncStatusElement.classList.remove('disabled');
835
836 var content = syncStatusElement.children[0];
837
838 // Set the sync section background color based on the state.
839 if (newMessage.msgtype == 'error') {
840 content.style.backgroundColor = 'tomato';
841 } else {
842 content.style.backgroundColor = '';
843 }
844
845 // Set the text for the header and sync message.
846 var titleElement = content.firstElementChild;
847 titleElement.textContent = newMessage.title;
848 var messageElement = titleElement.nextElementSibling;
849 messageElement.textContent = newMessage.msg;
850
851 // Remove what comes after the message
852 while (messageElement.nextSibling) {
853 content.removeChild(messageElement.nextSibling);
854 }
855
856 if (newMessage.linkisvisible) {
857 var el;
858 if (newMessage.linkurlisset) {
859 // Use a link
860 el = document.createElement('a');
861 el.href = newMessage.linkurl;
862 } else {
863 el = document.createElement('button');
864 el.className = 'link';
865 el.addEventListener('click', syncSectionLinkClicked);
866 }
867 el.textContent = newMessage.linktext;
868 content.appendChild(el);
869 fixLinkUnderline(el);
870 }
871
872 layoutSections();
873 }
874
875 /**
876 * Invoked when the link in the sync promo or sync status section is clicked.
877 */
878 function syncSectionLinkClicked(e) {
879 chrome.send('SyncLinkClicked');
880 e.preventDefault();
881 }
882
883 /**
884 * Invoked when link to start sync in the promo message is clicked, and Chrome
885 * has already been synced to an account.
886 */
887 function syncAlreadyEnabled(message) {
888 showNotification(message.syncEnabledMessage);
889 }
890
891 /**
892 * Returns the text used for a recently closed window.
893 * @param {number} numTabs Number of tabs in the window.
894 * @return {string} The text to use.
895 */
896 function formatTabsText(numTabs) {
897 if (numTabs == 1)
898 return localStrings.getString('closedwindowsingle');
899 return localStrings.getStringF('closedwindowmultiple', numTabs);
900 }
901
902 // Theme related
903
904 function themeChanged(hasAttribution) {
905 document.documentElement.setAttribute('hasattribution', hasAttribution);
906 $('themecss').href = 'chrome://theme/css/newtab.css?' + Date.now();
907 updateAttribution();
908 }
909
910 function updateAttribution() {
911 // Default value for standard NTP with no theme attribution or custom logo.
912 logEvent('updateAttribution called');
913 var imageId = 'IDR_PRODUCT_LOGO';
914 // Theme attribution always overrides custom logos.
915 if (document.documentElement.getAttribute('hasattribution') == 'true') {
916 logEvent('updateAttribution called with THEME ATTR');
917 imageId = 'IDR_THEME_NTP_ATTRIBUTION';
918 } else if (document.documentElement.getAttribute('customlogo') == 'true') {
919 logEvent('updateAttribution with CUSTOMLOGO');
920 imageId = 'IDR_CUSTOM_PRODUCT_LOGO';
921 }
922
923 $('attribution-img').src = 'chrome://theme/' + imageId + '?' + Date.now();
924 }
925
926 // If the content overlaps with the attribution, we bump its opacity down.
927 function updateAttributionDisplay(contentBottom) {
928 var attribution = $('attribution');
929 var main = $('main');
930 var rtl = document.documentElement.dir == 'rtl';
931 var contentRect = main.getBoundingClientRect();
932 var attributionRect = attribution.getBoundingClientRect();
933
934 // Hack. See comments for '.haslayout' in new_tab.css.
935 if (attributionRect.width == 0)
936 return;
937 else
938 attribution.classList.remove('nolayout');
939
940 if (contentBottom > attribution.offsetTop) {
941 if ((!rtl && contentRect.right > attributionRect.left) ||
942 (rtl && attributionRect.right > contentRect.left)) {
943 attribution.classList.add('obscured');
944 return;
945 }
946 }
947
948 attribution.classList.remove('obscured');
949 }
950
951 function bookmarkBarAttached() {
952 document.documentElement.setAttribute('bookmarkbarattached', 'true');
953 }
954
955 function bookmarkBarDetached() {
956 document.documentElement.setAttribute('bookmarkbarattached', 'false');
957 }
958
959 function viewLog() {
960 var lines = [];
961 var start = log[0][1];
962
963 for (var i = 0; i < log.length; i++) {
964 lines.push((log[i][1] - start) + ': ' + log[i][0]);
965 }
966
967 console.log(lines.join('\n'));
968 }
969
970 // We apply the size class here so that we don't trigger layout animations
971 // onload.
972
973 handleWindowResize();
974
975 var localStrings = new LocalStrings();
976
977 ///////////////////////////////////////////////////////////////////////////////
978 // Things we know are not needed at startup go below here
979
980 function afterTransition(f) {
981 if (!isDoneLoading()) {
982 // Make sure we do not use a timer during load since it slows down the UI.
983 f();
984 } else {
985 // The duration of all transitions are .15s
986 window.setTimeout(f, 150);
987 }
988 }
989
990 // Notification
991
992
993 var notificationTimeout;
994
995 /*
996 * Displays a message (either a string or a document fragment) in the
997 * notification slot at the top of the NTP. A close button ("x") will be
998 * inserted at the end of the message.
999 * @param {string|Node} message String or node to use as message.
1000 * @param {string} actionText The text to show as a link next to the message.
1001 * @param {function=} opt_f Function to call when the user clicks the action
1002 * link.
1003 * @param {number=} opt_delay The time in milliseconds before hiding the
1004 * notification.
1005 */
1006 function showNotification(message, actionText, opt_f, opt_delay) {
1007 // TODO(arv): Create a notification component.
1008 var notificationElement = $('notification');
1009 var f = opt_f || function() {};
1010 var delay = opt_delay || 10000;
1011
1012 function show() {
1013 window.clearTimeout(notificationTimeout);
1014 notificationElement.classList.add('show');
1015 document.body.classList.add('notification-shown');
1016 }
1017
1018 function delayedHide() {
1019 notificationTimeout = window.setTimeout(hideNotification, delay);
1020 }
1021
1022 function doAction() {
1023 f();
1024 closeNotification();
1025 }
1026
1027 function closeNotification() {
1028 if (notification.classList.contains('promo'))
1029 chrome.send('closePromo');
1030 hideNotification();
1031 }
1032
1033 // Remove classList entries from previous notifications.
1034 notification.classList.remove('first-run');
1035 notification.classList.remove('promo');
1036
1037 var messageContainer = notificationElement.firstElementChild;
1038 var actionLink = notificationElement.querySelector('#action-link');
1039 var closeButton = notificationElement.querySelector('#notification-close');
1040
1041 // Remove any previous actionLink entry.
1042 actionLink.textContent = '';
1043
1044 $('notification-close').onclick = closeNotification;
1045
1046 if (typeof message == 'string') {
1047 messageContainer.textContent = message;
1048 } else {
1049 messageContainer.textContent = ''; // Remove all children.
1050 messageContainer.appendChild(message);
1051 }
1052
1053 if (actionText) {
1054 actionLink.style.display = '';
1055 actionLink.textContent = actionText;
1056 } else {
1057 actionLink.style.display = 'none';
1058 }
1059
1060 actionLink.onclick = doAction;
1061 actionLink.onkeydown = handleIfEnterKey(doAction);
1062 notificationElement.onmouseover = show;
1063 notificationElement.onmouseout = delayedHide;
1064 actionLink.onfocus = show;
1065 actionLink.onblur = delayedHide;
1066 // Enable tabbing to the link now that it is shown.
1067 actionLink.tabIndex = 0;
1068
1069 show();
1070 delayedHide();
1071 }
1072
1073 /**
1074 * Hides the notifier.
1075 */
1076 function hideNotification() {
1077 var notificationElement = $('notification');
1078 notificationElement.classList.remove('show');
1079 document.body.classList.remove('notification-shown');
1080 var actionLink = notificationElement.querySelector('#actionlink');
1081 var closeButton = notificationElement.querySelector('#notification-close');
1082 // Prevent tabbing to the hidden link.
1083 // Setting tabIndex to -1 only prevents future tabbing to it. If, however, the
1084 // user switches window or a tab and then moves back to this tab the element
1085 // may gain focus. We therefore make sure that we blur the element so that the
1086 // element focus is not restored when coming back to this window.
1087 if (actionLink) {
1088 actionLink.tabIndex = -1;
1089 actionLink.blur();
1090 }
1091 if (closeButton) {
1092 closeButton.tabIndex = -1;
1093 closeButton.blur();
1094 }
1095 }
1096
1097 function showPromoNotification() {
1098 showNotification(parseHtmlSubset(localStrings.getString('serverpromo')),
1099 localStrings.getString('syncpromotext'),
1100 function () { chrome.send('SyncLinkClicked'); },
1101 60000);
1102 var notificationElement = $('notification');
1103 notification.classList.add('promo');
1104 }
1105
1106 $('main').addEventListener('click', function(e) {
1107 var p = e.target;
1108 while (p && p.tagName != 'H2') {
1109 // In case the user clicks on a button we do not want to expand/collapse a
1110 // section.
1111 if (p.tagName == 'BUTTON')
1112 return;
1113 p = p.parentNode;
1114 }
1115
1116 if (!p)
1117 return;
1118
1119 p = p.parentNode;
1120 if (!getSectionMaxiview(p))
1121 return;
1122
1123 toggleSectionVisibilityAndAnimate(p.getAttribute('section'));
1124 });
1125
1126 $('most-visited-settings').addEventListener('click', function() {
1127 $('clear-all-blacklisted').execute();
1128 });
1129
1130 function toggleSectionVisibilityAndAnimate(section) {
1131 if (!section)
1132 return;
1133
1134 // It looks better to return the scroll to the top when toggling sections.
1135 document.body.scrollTop = 0;
1136
1137 // We set it back in webkitTransitionEnd.
1138 document.documentElement.setAttribute('enable-section-animations', 'true');
1139 if (shownSections & Section[section]) {
1140 hideSection(Section[section]);
1141 } else {
1142 showOnlySection(section);
1143 }
1144 layoutSections();
1145 saveShownSections();
1146 }
1147
1148 function handleIfEnterKey(f) {
1149 return function(e) {
1150 if (e.keyIdentifier == 'Enter')
1151 f(e);
1152 };
1153 }
1154
1155 function maybeReopenTab(e) {
1156 var el = findAncestor(e.target, function(el) {
1157 return el.sessionId !== undefined;
1158 });
1159 if (el) {
1160 chrome.send('reopenTab', [String(el.sessionId)]);
1161 e.preventDefault();
1162
1163 setWindowTooltipTimeout();
1164 }
1165 }
1166
1167 // Note that the openForeignSession calls can fail, resulting this method to
1168 // not have any action (hence the maybe).
1169 function maybeOpenForeignSession(e) {
1170 var el = findAncestor(e.target, function(el) {
1171 return el.sessionTag !== undefined;
1172 });
1173 if (el) {
1174 chrome.send('openForeignSession', [String(el.sessionTag)]);
1175 e.stopPropagation();
1176 e.preventDefault();
1177 setWindowTooltipTimeout();
1178 }
1179 }
1180
1181 function maybeOpenForeignWindow(e) {
1182 var el = findAncestor(e.target, function(el) {
1183 return el.winNum !== undefined;
1184 });
1185 if (el) {
1186 chrome.send('openForeignSession', [String(el.sessionTag),
1187 String(el.winNum)]);
1188 e.stopPropagation();
1189 e.preventDefault();
1190 setWindowTooltipTimeout();
1191 }
1192 }
1193
1194 function maybeOpenForeignTab(e) {
1195 var el = findAncestor(e.target, function(el) {
1196 return el.sessionId !== undefined;
1197 });
1198 if (el) {
1199 chrome.send('openForeignSession', [String(el.sessionTag), String(el.winNum),
1200 String(el.sessionId)]);
1201 e.stopPropagation();
1202 e.preventDefault();
1203 setWindowTooltipTimeout();
1204 }
1205 }
1206
1207 // HACK(arv): After the window onblur event happens we get a mouseover event
1208 // on the next item and we want to make sure that we do not show a tooltip
1209 // for that.
1210 function setWindowTooltipTimeout(e) {
1211 window.setTimeout(function() {
1212 windowTooltip.hide();
1213 }, 2 * WindowTooltip.DELAY);
1214 }
1215
1216 function maybeShowWindowTooltip(e) {
1217 var f = function(el) {
1218 return el.tabItems !== undefined;
1219 };
1220 var el = findAncestor(e.target, f);
1221 var relatedEl = findAncestor(e.relatedTarget, f);
1222 if (el && el != relatedEl) {
1223 windowTooltip.handleMouseOver(e, el, el.tabItems);
1224 }
1225 }
1226
1227
1228 var recentlyClosedElement = $('recently-closed');
1229
1230 recentlyClosedElement.addEventListener('click', maybeReopenTab);
1231 recentlyClosedElement.addEventListener('keydown',
1232 handleIfEnterKey(maybeReopenTab));
1233
1234 recentlyClosedElement.addEventListener('mouseover', maybeShowWindowTooltip);
1235 recentlyClosedElement.addEventListener('focus', maybeShowWindowTooltip, true);
1236
1237 var foreignSessionElement = $('foreign-sessions');
1238
1239 foreignSessionElement.addEventListener('click', maybeOpenForeignSession);
1240 foreignSessionElement.addEventListener('keydown',
1241 handleIfEnterKey(
1242 maybeOpenForeignSession));
1243
1244 foreignSessionElement.addEventListener('mouseover', maybeShowWindowTooltip);
1245 foreignSessionElement.addEventListener('focus', maybeShowWindowTooltip, true);
1246
1247 /**
1248 * This object represents a tooltip representing a closed window. It is
1249 * shown when hovering over a closed window item or when the item is focused. It
1250 * gets hidden when blurred or when mousing out of the menu or the item.
1251 * @param {Element} tooltipEl The element to use as the tooltip.
1252 * @constructor
1253 */
1254 function WindowTooltip(tooltipEl) {
1255 this.tooltipEl = tooltipEl;
1256 this.boundHide_ = this.hide.bind(this);
1257 this.boundHandleMouseOut_ = this.handleMouseOut.bind(this);
1258 }
1259
1260 WindowTooltip.trackMouseMove_ = function(e) {
1261 WindowTooltip.clientX = e.clientX;
1262 WindowTooltip.clientY = e.clientY;
1263 };
1264
1265 /**
1266 * Time in ms to delay before the tooltip is shown.
1267 * @type {number}
1268 */
1269 WindowTooltip.DELAY = 300;
1270
1271 WindowTooltip.prototype = {
1272 timer: 0,
1273 handleMouseOver: function(e, linkEl, tabs) {
1274 this.linkEl_ = linkEl;
1275 if (e.type == 'mouseover') {
1276 this.linkEl_.addEventListener('mousemove', WindowTooltip.trackMouseMove_);
1277 this.linkEl_.addEventListener('mouseout', this.boundHandleMouseOut_);
1278 } else { // focus
1279 this.linkEl_.addEventListener('blur', this.boundHide_);
1280 }
1281 this.timer = window.setTimeout(this.show.bind(this, e.type, linkEl, tabs),
1282 WindowTooltip.DELAY);
1283 },
1284 show: function(type, linkEl, tabs) {
1285 window.addEventListener('blur', this.boundHide_);
1286 this.linkEl_.removeEventListener('mousemove',
1287 WindowTooltip.trackMouseMove_);
1288 window.clearTimeout(this.timer);
1289
1290 this.renderItems(tabs);
1291 var rect = linkEl.getBoundingClientRect();
1292 var bodyRect = document.body.getBoundingClientRect();
1293 var rtl = document.documentElement.dir == 'rtl';
1294
1295 this.tooltipEl.style.display = 'block';
1296 var tooltipRect = this.tooltipEl.getBoundingClientRect();
1297 var x, y;
1298
1299 // When focused show below, like a drop down menu.
1300 if (type == 'focus') {
1301 x = rtl ?
1302 rect.left + bodyRect.left + rect.width - this.tooltipEl.offsetWidth :
1303 rect.left + bodyRect.left;
1304 y = rect.top + bodyRect.top + rect.height;
1305 } else {
1306 x = bodyRect.left + (rtl ?
1307 WindowTooltip.clientX - this.tooltipEl.offsetWidth :
1308 WindowTooltip.clientX);
1309 // Offset like a tooltip
1310 y = 20 + WindowTooltip.clientY + bodyRect.top;
1311 }
1312
1313 // We need to ensure that the tooltip is inside the window viewport.
1314 x = Math.min(x, bodyRect.width - tooltipRect.width);
1315 x = Math.max(x, 0);
1316 y = Math.min(y, bodyRect.height - tooltipRect.height);
1317 y = Math.max(y, 0);
1318
1319 this.tooltipEl.style.left = x + 'px';
1320 this.tooltipEl.style.top = y + 'px';
1321 },
1322 handleMouseOut: function(e) {
1323 // Don't hide when move to another item in the link.
1324 var f = function(el) {
1325 return el.tabItems !== undefined;
1326 };
1327 var el = findAncestor(e.target, f);
1328 var relatedEl = findAncestor(e.relatedTarget, f);
1329 if (el && el != relatedEl) {
1330 this.hide();
1331 }
1332 },
1333 hide: function() {
1334 window.clearTimeout(this.timer);
1335 window.removeEventListener('blur', this.boundHide_);
1336 this.linkEl_.removeEventListener('mousemove',
1337 WindowTooltip.trackMouseMove_);
1338 this.linkEl_.removeEventListener('mouseout', this.boundHandleMouseOut_);
1339 this.linkEl_.removeEventListener('blur', this.boundHide_);
1340 this.linkEl_ = null;
1341
1342 this.tooltipEl.style.display = 'none';
1343 },
1344 renderItems: function(tabs) {
1345 var tooltip = this.tooltipEl;
1346 tooltip.textContent = '';
1347
1348 tabs.forEach(function(tab) {
1349 var span = document.createElement('span');
1350 span.className = 'item';
1351 span.style.backgroundImage = url('chrome://favicon/' + tab.url);
1352 span.dir = tab.direction;
1353 span.textContent = tab.title;
1354 tooltip.appendChild(span);
1355 });
1356 }
1357 };
1358
1359 var windowTooltip = new WindowTooltip($('window-tooltip'));
1360
1361 window.addEventListener('load',
1362 logEvent.bind(global, 'Tab.NewTabOnload', true));
1363
1364 window.addEventListener('resize', handleWindowResize);
1365 document.addEventListener('DOMContentLoaded',
1366 logEvent.bind(global, 'Tab.NewTabDOMContentLoaded', true));
1367
1368 // Whether or not we should send the initial 'GetSyncMessage' to the backend
1369 // depends on the value of the attribue 'syncispresent' which the backend sets
1370 // to indicate if there is code in the backend which is capable of processing
1371 // this message. This attribute is loaded by the JSTemplate and therefore we
1372 // must make sure we check the attribute after the DOM is loaded.
1373 document.addEventListener('DOMContentLoaded',
1374 callGetSyncMessageIfSyncIsPresent);
1375
1376 /**
1377 * The sync code is not yet built by default on all platforms so we have to
1378 * make sure we don't send the initial sync message to the backend unless the
1379 * backend told us that the sync code is present.
1380 */
1381 function callGetSyncMessageIfSyncIsPresent() {
1382 if (document.documentElement.getAttribute('syncispresent') == 'true') {
1383 chrome.send('GetSyncMessage');
1384 }
1385 }
1386
1387 // Tooltip for elements that have text that overflows.
1388 document.addEventListener('mouseover', function(e) {
1389 // We don't want to do this while we are dragging because it makes things very
1390 // janky
1391 if (mostVisited.isDragging()) {
1392 return;
1393 }
1394
1395 var el = findAncestor(e.target, function(el) {
1396 return el.xtitle;
1397 });
1398 if (el && el.xtitle != el.title) {
1399 if (el.scrollWidth > el.clientWidth) {
1400 el.title = el.xtitle;
1401 } else {
1402 el.title = '';
1403 }
1404 }
1405 });
1406
1407 /**
1408 * Makes links and buttons support a different underline color.
1409 * @param {Node} node The node to search for links and buttons in.
1410 */
1411 function fixLinkUnderlines(node) {
1412 var elements = node.querySelectorAll('a,button');
1413 Array.prototype.forEach.call(elements, fixLinkUnderline);
1414 }
1415
1416 /**
1417 * Wraps the content of an element in a a link-color span.
1418 * @param {Element} el The element to wrap.
1419 */
1420 function fixLinkUnderline(el) {
1421 var span = document.createElement('span');
1422 span.className = 'link-color';
1423 while (el.hasChildNodes()) {
1424 span.appendChild(el.firstChild);
1425 }
1426 el.appendChild(span);
1427 }
1428
1429 updateAttribution();
1430
1431 function initializeLogin() {
1432 chrome.send('initializeLogin', []);
1433 }
1434
1435 function updateLogin(login) {
1436 $('login-container').style.display = login ? 'block' : '';
1437 if (login)
1438 $('login-username').textContent = login;
1439 }
1440
1441 var mostVisited = new MostVisited(
1442 $('most-visited-maxiview'),
1443 document.querySelector('#most-visited .miniview'),
1444 $('most-visited-menu'),
1445 useSmallGrid(),
1446 shownSections & Section.THUMB);
1447
1448 function setMostVisitedPages(data, hasBlacklistedUrls) {
1449 logEvent('received most visited pages');
1450
1451 mostVisited.updateSettingsLink(hasBlacklistedUrls);
1452 mostVisited.data = data;
1453 mostVisited.layout();
1454 layoutSections();
1455
1456 // Remove class name in a timeout so that changes done in this JS thread are
1457 // not animated.
1458 window.setTimeout(function() {
1459 mostVisited.ensureSmallGridCorrect();
1460 maybeDoneLoading();
1461 }, 1);
1462
1463 if (localStrings.getString('serverpromo')) {
1464 showPromoNotification();
1465 }
1466
1467 }
1468
1469 function maybeDoneLoading() {
1470 if (mostVisited.data && apps.loaded)
1471 document.body.classList.remove('loading');
1472 }
1473
1474 function isDoneLoading() {
1475 return !document.body.classList.contains('loading');
1476 }
1477
1478 document.addEventListener('DOMContentLoaded', function() {
1479 cr.enablePlatformSpecificCSSRules();
1480
1481 // Initialize the listener for the "hide this" link on the apps promo. We do
1482 // this outside of getAppsCallback because it only needs to be done once per
1483 // NTP load.
1484 $('apps-promo-hide').addEventListener('click', function() {
1485 chrome.send('hideAppsPromo', []);
1486 document.documentElement.classList.remove('apps-promo-visible');
1487 layoutSections();
1488 });
1489 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698