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

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

Issue 147226: Make the new new tab page the default new tab page.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 6 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
2 // Helpers
3
4 function $(id) {
5 return document.getElementById(id);
6 }
7
8 // TODO(arv): Remove these when classList is available in HTML5.
9 // https://bugs.webkit.org/show_bug.cgi?id=20709
10 function hasClass(el, name) {
11 return el.nodeType == 1 && el.className.split(/\s+/).indexOf(name) != -1;
12 }
13
14 function addClass(el, name) {
15 el.className += ' ' + name;
16 }
17
18 function removeClass(el, name) {
19 var names = el.className.split(/\s+/);
20 el.className = names.filter(function(n) {
21 return name != n;
22 }).join(' ');
23 }
24
25 function findAncestorByClass(el, className) {
26 return findAncestor(el, function(el) {
27 return hasClass(el, className);
28 });
29 }
30
31 /**
32 * Return the first ancestor for which the {@code predicate} returns true.
33 * @param {Node} node The node to check.
34 * @param {function(Node) : boolean} predicate The function that tests the
35 * nodes.
36 * @return {Node} The found ancestor or null if not found.
37 */
38 function findAncestor(node, predicate) {
39 var last = false;
40 while (node != null && !(last = predicate(node))) {
41 node = node.parentNode;
42 }
43 return last ? node : null;
44 }
45
46 // WebKit does not have Node.prototype.swapNode
47 // https://bugs.webkit.org/show_bug.cgi?id=26525
48 function swapDomNodes(a, b) {
49 var afterA = a.nextSibling;
50 if (afterA == b) {
51 swapDomNodes(b, a);
52 return;
53 }
54 var aParent = a.parentNode;
55 b.parentNode.replaceChild(a, b);
56 aParent.insertBefore(b, afterA);
57 }
58
59 function bind(fn, selfObj, var_args) {
60 var boundArgs = Array.prototype.slice.call(arguments, 2);
61 return function() {
62 var args = Array.prototype.slice.call(arguments);
63 args.unshift.apply(args, boundArgs);
64 return fn.apply(selfObj, args);
65 }
66 }
67
68 var mostVisitedData = [];
69 var gotMostVisited = false;
70 var gotShownSections = false;
71
72 function mostVisitedPages(data) {
73 logEvent('received most visited pages');
74
75 // We append the class name with the "filler" so that we can style fillers
76 // differently.
77 var maxItems = 8;
78 data.length = Math.min(maxItems, data.length);
79 var len = data.length;
80 for (var i = len; i < maxItems; i++) {
81 data[i] = {filler: true};
82 }
83
84 mostVisitedData = data;
85 renderMostVisited(data);
86 layoutMostVisited();
87
88 gotMostVisited = true;
89 onDataLoaded();
90 }
91
92 function downloadsList(data) {
93 logEvent('received downloads');
94 data.length = Math.min(data.length, 5);
95 processData('#download-items', data);
96 }
97
98 function recentlyClosedTabs(data) {
99 logEvent('received recently closed tabs');
100 data.length = Math.min(data.length, 5);
101 processData('#tab-items', data);
102 }
103
104 function onShownSections(m) {
105 logEvent('received shown sections');
106 setShownSections(m);
107 gotShownSections = true;
108 onDataLoaded();
109 }
110
111 function saveShownSections() {
112 chrome.send('setShownSections', [String(shownSections)]);
113 }
114
115 function tips(data) {
116 logEvent('received tips data');
117 data.length = Math.min(data.length, 5);
118 processData('#tip-items', data);
119 }
120
121 function layoutMostVisited() {
122 var d0 = Date.now();
123 var mostVisitedElement = $('most-visited');
124 var thumbnails = mostVisitedElement.querySelectorAll('.thumbnail-container');
125
126 if (thumbnails.length < 8) {
127 return;
128 }
129
130 var small = useSmallGrid();
131
132 var cols = 4;
133 var rows = 2;
134 var marginWidth = 10;
135 var marginHeight = 7;
136 var borderWidth = 4;
137 var thumbWidth = small ? 150 : 212;
138 var thumbHeight = small ? 93 : 132;
139 var w = thumbWidth + 2 * borderWidth + 2 * marginWidth;
140 var h = thumbHeight + 40 + 2 * marginHeight;
141 var sumWidth = cols * w - 2 * marginWidth;
142 var sumHeight = rows * h;
143 var opacity = 1;
144
145 if (shownSections & Section.LIST) {
146 w = (sumWidth + 2 * marginWidth) / 2;
147 h = 45;
148 rows = 4;
149 cols = 2;
150 sumHeight = rows * h;
151 addClass(mostVisitedElement, 'list');
152 } else if (shownSections & Section.THUMB) {
153 removeClass(mostVisitedElement, 'list');
154 } else {
155 sumHeight = 0;
156 opacity = 0;
157 }
158
159 mostVisitedElement.style.height = sumHeight + 'px';
160 mostVisitedElement.style.opacity = opacity;
161 // We set overflow to hidden so that the most visited element does not
162 // "leak" when we hide and show it.
163 mostVisitedElement.style.overflow = 'hidden';
164
165 var rtl = document.documentElement.dir == 'rtl';
166
167 if (shownSections & Section.THUMB || shownSections & Section.LIST) {
168 for (var i = 0; i < thumbnails.length; i++) {
169 var t = thumbnails[i];
170
171 var row, col;
172 if (shownSections & Section.THUMB) {
173 row = Math.floor(i / cols);
174 col = i % cols;
175 } else {
176 col = Math.floor(i / rows);
177 row = i % rows;
178 }
179
180 if (shownSections & Section.THUMB) {
181 t.style.left = (rtl ?
182 sumWidth - col * w - thumbWidth - 2 * borderWidth :
183 col * w) + 'px';
184 } else {
185 t.style.left = (rtl ?
186 sumWidth - col * w - w + 2 * marginWidth :
187 col * w) + 'px';
188 }
189 t.style.top = row * h + 'px';
190
191 if (shownSections & Section.LIST) {
192 t.style.width = w - 2 * marginWidth + 'px';
193 } else {
194 t.style.width = '';
195 }
196 }
197 }
198
199 afterTransition(function() {
200 // Only set overflow to visible if the element is shown.
201 if (opacity) {
202 mostVisitedElement.style.overflow = '';
203 }
204 });
205
206 logEvent('layoutMostVisited: ' + (Date.now() - d0));
207 }
208
209 // This global variable is used to skip parts of the DOM tree for the global
210 // jst processing done by the i18n.
211 var processing = false;
212
213 function processData(selector, data) {
214 var output = document.querySelector(selector);
215
216 // Wait until ready
217 if (typeof JsEvalContext !== 'function' || !output) {
218 logEvent('JsEvalContext is not yet available, ' + selector);
219 document.addEventListener('DOMContentLoaded', function() {
220 processData(selector, data);
221 });
222 } else {
223 var d0 = Date.now();
224 var input = new JsEvalContext(data);
225 processing = true;
226 jstProcess(input, output);
227 processing = false;
228 logEvent('processData: ' + selector + ', ' + (Date.now() - d0));
229 }
230 }
231
232 var thumbnailTemplate;
233
234 function getThumbnailClassName(data) {
235 return 'thumbnail-container' +
236 (data.pinned ? ' pinned' : '') +
237 (data.filler ? ' filler' : '');
238 }
239
240 function renderMostVisited(data) {
241 var parent = $('most-visited');
242 if (!thumbnailTemplate) {
243 thumbnailTemplate = $('thumbnail-template');
244 thumbnailTemplate.parentNode.removeChild(thumbnailTemplate);
245 }
246
247 var children = parent.children;
248 for (var i = 0; i < data.length; i++) {
249 var d = data[i];
250 var reuse = !!children[i];
251 var t = children[i] || thumbnailTemplate.cloneNode(true);
252 t.style.display = '';
253 t.className = getThumbnailClassName(d);
254 t.title = d.title;
255 t.href = d.url;
256 t.querySelector('.pin').title = localStrings.getString(d.pinned ?
257 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
258 t.querySelector('.remove').title =
259 localStrings.getString('removethumbnailtooltip');
260
261 // There was some concern that a malformed malicious URL could cause an XSS
262 // attack but setting style.backgroundImage = 'url(javascript:...)' does
263 // not execute the JavaScript in WebKit.
264 t.querySelector('.thumbnail-wrapper').style.backgroundImage =
265 'url(chrome://thumb/' + d.url + ')';
266 var titleDiv = t.querySelector('.title > div');
267 titleDiv.textContent = d.title;
268 titleDiv.style.backgroundImage = 'url(chrome://favicon/' + d.url + ')';
269 titleDiv.dir = d.direction;
270 if (!reuse) {
271 parent.appendChild(t);
272 }
273 }
274 }
275
276 /**
277 * Calls chrome.send with a callback and restores the original afterwards.
278 */
279 function chromeSend(name, params, callbackName, callback) {
280 var old = global[callbackName];
281 global[callbackName] = function() {
282 // restore
283 global[callbackName] = old;
284
285 var args = Array.prototype.slice.call(arguments);
286 return callback.apply(global, args);
287 };
288 chrome.send(name, params);
289 }
290
291 function useSmallGrid() {
292 return document.body.clientWidth <= 940;
293 }
294
295 function handleWindowResize(e, opt_noUpdate) {
296 var body = document.body;
297 if (!body || body.clientWidth < 10) {
298 // We're probably a background tab, so don't do anything.
299 return;
300 }
301
302 var hasSmallClass = hasClass(body, 'small');
303 if (hasSmallClass && !useSmallGrid()) {
304 removeClass(body, 'small');
305 if (!opt_noUpdate) {
306 layoutMostVisited();
307 layoutLowerSections();
308 }
309 } else if (!hasSmallClass && useSmallGrid()) {
310 addClass(body, 'small');
311 if (!opt_noUpdate) {
312 layoutMostVisited();
313 layoutLowerSections();
314 }
315 }
316 }
317
318 /**
319 * Bitmask for the different UI sections.
320 * This matches the Section enum in ../dom_ui/shown_sections_handler.h
321 * @enum {number}
322 */
323 var Section = {
324 THUMB: 1,
325 LIST: 2,
326 RECENT: 4,
327 TIPS: 8
328 };
329
330 var shownSections = Section.RECENT | Section.TIPS;
331
332 function showSection(section) {
333 if (!(section & shownSections)) {
334 // THUMBS and LIST are mutually exclusive.
335 if (section == Section.THUMB) {
336 hideSection(Section.LIST);
337 } else if (section == Section.LIST) {
338 hideSection(Section.THUMB);
339 }
340
341 shownSections |= section;
342 notifyLowerSectionForChange(section, false);
343
344 mostVisited.updateDisplayMode();
345 layoutMostVisited();
346 updateOptionMenu();
347 layoutLowerSections();
348 }
349 }
350
351 function hideSection(section) {
352 if (section & shownSections) {
353 shownSections &= ~section;
354 notifyLowerSectionForChange(section, true);
355
356 mostVisited.updateDisplayMode();
357 layoutMostVisited();
358 updateOptionMenu();
359 layoutLowerSections();
360 }
361 }
362
363 function notifyLowerSectionForChange(section, large) {
364 // Notify recent and tips if they need to display more data.
365 if (section == Section.RECENT || section == Section.TIPS) {
366 // we are hiding one of them so if the other one is visible it is now
367 // {@code large}.
368 if (shownSections & Section.RECENT) {
369 recentChangedSize(large);
370 } else if (shownSections & Section.TIPS) {
371 tipsChangedSize(large);
372 }
373 }
374 }
375
376 /**
377 * This is called when we get the shown sections pref from the backend.
378 */
379 function setShownSections(mask) {
380 if (mask != shownSections) {
381 shownSections = mask;
382 mostVisited.updateDisplayMode();
383 layoutMostVisited();
384 layoutLowerSections();
385 updateOptionMenu();
386 }
387 }
388
389 var mostVisited = {
390 getItem: function(el) {
391 return findAncestorByClass(el, 'thumbnail-container');
392 },
393
394 getHref: function(el) {
395 return el.href;
396 },
397
398 togglePinned: function(el) {
399 var index = this.getThumbnailIndex(el);
400 var data = mostVisitedData[index];
401 if (data.pinned) {
402 removeClass(el, 'pinned');
403 chrome.send('removePinnedURL', [data.url]);
404 } else {
405 addClass(el, 'pinned');
406 chrome.send('addPinnedURL', [data.url, data.title, String(index)]);
407 }
408 data.pinned = !data.pinned;
409 // Update tooltip
410 el.querySelector('.pin').title = localStrings.getString(data.pinned ?
411 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
412 },
413
414 getThumbnailIndex: function(el) {
415 var nodes = el.parentNode.querySelectorAll('.thumbnail-container');
416 return Array.prototype.indexOf.call(nodes, el);
417 },
418
419 swapPosition: function(source, destination) {
420 var nodes = source.parentNode.querySelectorAll('.thumbnail-container');
421 var sourceIndex = this.getThumbnailIndex(source);
422 var destinationIndex = this.getThumbnailIndex(destination);
423 swapDomNodes(source, destination);
424
425 var sourceData = mostVisitedData[sourceIndex];
426 chrome.send('addPinnedURL', [sourceData.url, sourceData.title,
427 String(destinationIndex)]);
428 sourceData.pinned = true;
429 addClass(source, 'pinned');
430 var destinationData = mostVisitedData[destinationIndex];
431 // Only update the destination if it was pinned before.
432 if (destinationData.pinned) {
433 chrome.send('addPinnedURL', [destinationData.url, destinationData.title,
434 String(sourceIndex)]);
435 }
436 mostVisitedData[destinationIndex] = sourceData;
437 mostVisitedData[sourceIndex] = destinationData;
438 },
439
440 blacklist: function(el) {
441 var self = this;
442 var url = this.getHref(el);
443 chrome.send('blacklistURLFromMostVisited', [url]);
444
445 addClass(el, 'hide');
446
447 // Find the old item.
448 var oldUrls = {};
449 var oldIndex = -1;
450 var oldItem;
451 for (var i = 0; i < mostVisitedData.length; i++) {
452 if (mostVisitedData[i].url == url) {
453 oldItem = mostVisitedData[i];
454 oldIndex = i;
455 }
456 oldUrls[mostVisitedData[i].url] = true;
457 }
458
459 // Send 'getMostVisitedPages' with a callback since we want to find the new
460 // page and add that in the place of the removed page.
461 chromeSend('getMostVisited', [], 'mostVisitedPages', function(data) {
462 // Find new item.
463 var newItem;
464 for (var i = 0; i < data.length; i++) {
465 if (!(data[i].url in oldUrls)) {
466 newItem = data[i];
467 break;
468 }
469 }
470
471 if (!newItem) {
472 newItem = {filler: true};
473 }
474
475 // Replace old item with new item in the mostVisitedData array.
476 if (oldIndex != -1) {
477 mostVisitedData.splice(oldIndex, 1, newItem);
478 mostVisitedPages(mostVisitedData);
479 addClass(el, 'fade-in');
480 }
481
482 var text = localStrings.formatString('thumbnailremovednotification',
483 oldItem.title);
484 var actionText = localStrings.getString('undothumbnailremove');
485
486 // Show notification and add undo callback function.
487 var wasPinned = oldItem.pinned;
488 showNotification(text, actionText, function() {
489 self.removeFromBlackList(url);
490 if (wasPinned) {
491 chromeSend('addPinnedURL', [url, oldItem.title, String(oldIndex)]);
492 }
493 chrome.send('getMostVisited');
494 });
495 });
496 },
497
498 removeFromBlackList: function(url) {
499 chrome.send('removeURLsFromMostVisitedBlacklist', [url]);
500 },
501
502 clearAllBlacklisted: function() {
503 chrome.send('clearMostVisitedURLsBlacklist', []);
504 },
505
506 updateDisplayMode: function() {
507 var thumbCheckbox = $('thumb-checkbox');
508 var listCheckbox = $('list-checkbox');
509 var mostVisitedElement = $('most-visited');
510
511 if (shownSections & Section.THUMB) {
512 thumbCheckbox.checked = true;
513 listCheckbox.checked = false;
514 removeClass(mostVisitedElement, 'list');
515 } else if (shownSections & Section.LIST) {
516 thumbCheckbox.checked = false;
517 listCheckbox.checked = true;
518 addClass(mostVisitedElement, 'list');
519 } else {
520 thumbCheckbox.checked = false;
521 listCheckbox.checked = false;
522 }
523
524 thumbCheckbox.title = localStrings.getString(
525 shownSections & Section.THUMB ? 'hidethumbnails' : 'showthumbnails');
526 listCheckbox.title = localStrings.getString(
527 shownSections & Section.LIST ? 'hidelist' : 'showlist');
528 }
529 };
530
531 function recentChangedSize(large) {
532 // TODO(arv): Implement
533 }
534
535 function tipsChangedSize(large) {
536 // TODO(arv): Implement
537 }
538
539 // Recent activities
540
541 function layoutLowerSections() {
542 // This lower sections are inline blocks so all we need to do is to set the
543 // width and opacity.
544 var lowerSectionsElement = $('lower-sections');
545 var recentElement = $('recent-activities');
546 var tipsElement = $('tips');
547 var spacer = recentElement.nextElementSibling;
548
549 var totalWidth = useSmallGrid() ? 692 : 940;
550 var spacing = 20;
551 var rtl = document.documentElement.dir == 'rtl';
552
553 var recentShown = shownSections & Section.RECENT;
554 var tipsShown = shownSections & Section.TIPS;
555
556 if (recentShown || tipsShown) {
557 lowerSectionsElement.style.height = '198px';
558 lowerSectionsElement.style.opacity = '';
559 } else {
560 lowerSectionsElement.style.height = lowerSectionsElement.style.opacity = 0;
561 }
562
563 if (recentShown && tipsShown) {
564 var w = (totalWidth - spacing) / 2;
565 recentElement.style.width = tipsElement.style.width = w + 'px'
566 recentElement.style.opacity = tipsElement.style.opacity = '';
567 spacer.style.width = spacing + 'px';
568 } else if (recentShown) {
569 recentElement.style.width = totalWidth + 'px';
570 recentElement.style.opacity = '';
571 tipsElement.style.width =
572 tipsElement.style.opacity = 0;
573 spacer.style.width = 0;
574 } else if (tipsShown) {
575 tipsElement.style.width = totalWidth + 'px';
576 tipsElement.style.opacity = '';
577 recentElement.style.width = recentElement.style.opacity = 0;
578 spacer.style.width = 0;
579 }
580 }
581
582 /**
583 * Returns the text used for a recently closed window.
584 * @param {number} numTabs Number of tabs in the window.
585 * @return {string} The text to use.
586 */
587 function formatTabsText(numTabs) {
588 if (numTabs == 1)
589 return localStrings.getString('closedwindowsingle');
590 return localStrings.formatString('closedwindowmultiple', numTabs);
591 }
592
593 /**
594 * We need both most visited and the shown sections to be considered loaded.
595 * @return {boolean}
596 */
597 function onDataLoaded() {
598 if (gotMostVisited && gotShownSections) {
599 // Remove class name in a timeout so that changes done in this JS thread are
600 // not animated.
601 window.setTimeout(function() {
602 removeClass(document.body, 'loading');
603 }, 10);
604 }
605 }
606
607 // Theme related
608
609 function themeChanged() {
610 $('themecss').href = 'chrome://theme/css/newtab.css?' + Date.now();
611 updateAttribution();
612 }
613
614 function updateAttribution() {
615 // TODO(arv): Implement
616 //$('attribution-img').src = 'chrome://theme/theme_ntp_attribution?' +
617 // Date.now();
618 }
619
620 function bookmarkBarAttached() {
621 document.documentElement.setAttribute("bookmarkbarattached", "true");
622 }
623
624 function bookmarkBarDetached() {
625 document.documentElement.setAttribute("bookmarkbarattached", "false");
626 }
627
628 function viewLog() {
629 var lines = [];
630 var start = log[0][1];
631
632 for (var i = 0; i < log.length; i++) {
633 lines.push((log[i][1] - start) + ': ' + log[i][0]);
634 }
635
636 console.log(lines.join('\n'));
637 }
638
639 // Updates the visibility of the menu items.
640 function updateOptionMenu() {
641 var menuItems = $('option-menu').children;
642 for (var i = 0; i < menuItems.length; i++) {
643 var item = menuItems[i];
644 var section = Section[item.getAttribute('section')];
645 var show = item.getAttribute('show') == 'true';
646 // Hide show items if already shown. Hide hide items if already hidden.
647 var hideMenuItem = show == !!(shownSections & section);
648 item.style.display = hideMenuItem ? 'none' : '';
649 }
650 }
651
652 // We apply the size class here so that we don't trigger layout animations
653 // onload.
654
655 handleWindowResize(null, true);
656
657 var localStrings = new LocalStrings();
658
659 ///////////////////////////////////////////////////////////////////////////////
660 // Things we know are not needed at startup go below here
661
662 // Notification
663
664 function afterTransition(f) {
665 // The duration of all transitions are 500ms
666 window.setTimeout(f, 500);
667 }
668
669 function showNotification(text, actionText, f) {
670 var notificationElement = $('notification');
671 var actionLink = notificationElement.querySelector('.link');
672 notificationElement.firstElementChild.textContent = text;
673
674 actionLink.textContent = actionText;
675 actionLink.onclick = function() {
676 f();
677 removeClass(notificationElement, 'show');
678 // Since we have a :hover rule to not hide the notification banner when the
679 // mouse is over we need force it to hide. We remove the hide class after
680 // a short timeout to allow the banner to be shown again.
681 addClass(notificationElement, 'hide');
682 afterTransition(function() {
683 removeClass(notificationElement, 'hide');
684 })
685 };
686 addClass(notificationElement, 'show');
687 window.setTimeout(function() {
688 removeClass(notificationElement, 'show');
689 }, 10000);
690 }
691
692 /**
693 * This handles the option menu.
694 * @param {Element} button The button element.
695 * @param {Element} menu The menu element.
696 * @constructor
697 */
698 function OptionMenu(button, menu) {
699 this.button = button;
700 this.menu = menu;
701 this.button.onmousedown = bind(this.handleMouseDown, this);
702 this.button.onkeydown = bind(this.handleKeyDown, this);
703 this.boundHideMenu_ = bind(this.hideMenu, this);
704 this.boundMaybeHide_ = bind(this.maybeHide_, this);
705 this.menu.onmouseover = bind(this.handleMouseOver, this);
706 this.menu.onmouseout = bind(this.handleMouseOut, this);
707 this.menu.onmouseup = bind(this.handleMouseUp, this);
708 }
709
710 OptionMenu.prototype = {
711 showMenu: function() {
712 this.menu.style.display = 'block';
713 this.button.focus();
714
715 // Listen to document and window events so that we hide the menu when the
716 // user clicks outside the menu or tabs away or the whole window is blurred.
717 document.addEventListener('focus', this.boundMaybeHide_, true);
718 document.addEventListener('mousedown', this.boundMaybeHide_, true);
719 window.addEventListener('blur', this.boundHideMenu_);
720 },
721
722 hideMenu: function() {
723 this.menu.style.display = 'none';
724 this.setSelectedIndex(-1);
725
726 document.removeEventListener('focus', this.boundMaybeHide_, true);
727 document.removeEventListener('mousedown', this.boundMaybeHide_, true);
728 window.removeEventListener('blur', this.boundHide_);
729 },
730
731 isMenuShown: function() {
732 return this.menu.style.display == 'block';
733 },
734
735 /**
736 * Callback for document mousedown and focus. It checks if the user tried to
737 * navigate to a different element on the page and if so hides the menu.
738 * @param {Event} e The mouse or focus event.
739 * @private
740 */
741 maybeHide_: function(e) {
742 if (!this.menu.contains(e.target) && !this.button.contains(e.target)) {
743 this.hideMenu();
744 }
745 },
746
747 handleMouseDown: function(e) {
748 if (this.isMenuShown()) {
749 this.hideMenu();
750 } else {
751 this.showMenu();
752 }
753 },
754
755 handleMouseOver: function(e) {
756 var el = e.target;
757 var index = Array.prototype.indexOf.call(this.menu.children, el);
758 console.log(el, index);
759 this.setSelectedIndex(index);
760 },
761
762 handleMouseOut: function(e) {
763 this.setSelectedIndex(-1);
764 },
765
766 handleMouseUp: function(e) {
767 var item = this.getSelectedItem();
768 if (item) {
769 this.executeItem(item);
770 }
771 },
772
773 handleKeyDown: function(e) {
774 var item = this.getSelectedItem();
775
776 var self = this;
777 function selectNextVisible(m) {
778 var children = self.menu.children;
779 var len = children.length;
780 var i = self.selectedIndex_;
781 if (i == -1 && m == -1) {
782 // Edge case when we need to go the last item fisrt.
783 i = 0;
784 }
785 while (true) {
786 i = (i + m + len) % len;
787 item = children[i];
788 if (item && item.style.display != 'none') {
789 break;
790 }
791 }
792 if (item) {
793 self.setSelectedIndex(i);
794 }
795 }
796
797 switch (e.keyIdentifier) {
798 case 'Down':
799 if (!this.isMenuShown()) {
800 this.showMenu();
801 }
802 selectNextVisible(1);
803 break;
804 case 'Up':
805 if (!this.isMenuShown()) {
806 this.showMenu();
807 }
808 selectNextVisible(-1);
809 break;
810 case 'Esc':
811 case 'U+001B': // Maybe this is remote desktop playing a prank?
812 this.hideMenu();
813 break;
814 case 'Enter':
815 if (this.isMenuShown()) {
816 if (item) {
817 this.executeItem(item);
818 }
819 } else {
820 this.showMenu();
821 }
822 break;
823 }
824 },
825
826 selectedIndex_: -1,
827 setSelectedIndex: function(i) {
828 if (i != this.selectedIndex_) {
829 var items = this.menu.children;
830 var oldItem = items[this.selectedIndex_];
831 if (oldItem) {
832 oldItem.removeAttribute('selected');
833 }
834 var newItem = items[i];
835 if (newItem) {
836 newItem.setAttribute('selected', 'selected');
837 }
838 this.selectedIndex_ = i;
839 }
840 },
841
842 getSelectedItem: function() {
843 return this.menu.children[this.selectedIndex_] || null;
844 },
845
846 executeItem: function(item) {
847 var section = Section[item.getAttribute('section')];
848 var show = item.getAttribute('show') == 'true';
849 if (show) {
850 showSection(section);
851 } else {
852 hideSection(section);
853 }
854
855 this.hideMenu();
856
857 layoutLowerSections();
858 mostVisited.updateDisplayMode();
859 layoutMostVisited();
860
861 saveShownSections();
862 }
863 };
864
865 new OptionMenu($('option-button'), $('option-menu'));
866
867 $('most-visited').addEventListener('click', function(e) {
868 var target = e.target;
869 if (hasClass(target, 'pin')) {
870 mostVisited.togglePinned(mostVisited.getItem(target));
871 e.preventDefault();
872 } else if (hasClass(target, 'remove')) {
873 mostVisited.blacklist(mostVisited.getItem(target));
874 e.preventDefault();
875 }
876 });
877
878 function handleIfEnterKey(f) {
879 return function(e) {
880 if (e.keyIdentifier == 'Enter') {
881 f(e);
882 }
883 };
884 }
885
886 $('downloads').addEventListener('click', maybeOpenFile);
887 $('downloads').addEventListener('keydown', handleIfEnterKey(maybeOpenFile));
888
889 function maybeOpenFile(e) {
890 var el = findAncestor(e.target, function(el) {
891 return el.fileId !== undefined;
892 });
893 if (el) {
894 chrome.send('openFile', [String(el.fileId)]);
895 e.preventDefault();
896 }
897 }
898
899 var recentTabs = $('recent-tabs');
900 recentTabs.addEventListener('click', maybeReopenTab);
901 recentTabs.addEventListener('keydown', handleIfEnterKey(maybeReopenTab));
902
903 function maybeReopenTab(e) {
904 var el = findAncestor(e.target, function(el) {
905 return el.sessionId !== undefined;
906 });
907 if (el) {
908 chrome.send('reopenTab', [String(el.sessionId)]);
909 e.preventDefault();
910 }
911 }
912
913 recentTabs.addEventListener('mouseover', maybeShowWindowMenu);
914 recentTabs.addEventListener('focus', maybeShowWindowMenu, true);
915 recentTabs.addEventListener('mouseout', maybeHideWindowMenu);
916 recentTabs.addEventListener('blur', maybeHideWindowMenu, true);
917
918 function maybeShowWindowMenu(e) {
919 var el = findAncestor(e.target, function(el) {
920 return el.tabItems !== undefined;
921 });
922 if (el) {
923 showWindowMenu(el, el.tabItems);
924 }
925 }
926
927 function maybeHideWindowMenu(e) {
928 var el = findAncestor(e.target, function(el) {
929 return el.tabItems !== undefined;
930 });
931 if (el) {
932 $('window-menu').style.display = 'none';
933 }
934 }
935
936 function showWindowMenu(el, tabs) {
937 var menuEl = $('window-menu');
938 processData('#window-menu', tabs);
939 var rect = el.getBoundingClientRect();
940 var bodyRect = document.body.getBoundingClientRect()
941 var rtl = document.documentElement.dir == 'rtl';
942
943 menuEl.style.display = 'block';
944 menuEl.style.left = (rtl ?
945 rect.left + bodyRect.left + rect.width - menuEl.offsetWidth :
946 rect.left + bodyRect.left) + 'px';
947 menuEl.style.top = rect.top + bodyRect.top + rect.height + 'px';
948
949 }
950
951 $('thumb-checkbox').addEventListener('change', function(e) {
952 if (e.target.checked) {
953 showSection(Section.THUMB);
954 } else {
955 hideSection(Section.THUMB);
956 }
957 mostVisited.updateDisplayMode();
958 layoutMostVisited();
959 saveShownSections();
960 });
961
962 $('list-checkbox').addEventListener('change', function(e) {
963 var newValue = shownSections;
964 if (e.target.checked) {
965 showSection(Section.LIST);
966 } else {
967 hideSection(Section.LIST);
968 }
969 mostVisited.updateDisplayMode();
970 layoutMostVisited();
971 saveShownSections();
972 });
973
974 window.addEventListener('load', bind(logEvent, global, 'onload fired'));
975 window.addEventListener('load', onDataLoaded);
976 window.addEventListener('resize', handleWindowResize);
977 document.addEventListener('DOMContentLoaded', bind(logEvent, global,
978 'domcontentloaded fired'));
979
980 // DnD
981
982 var dnd = {
983 currentOverItem: null,
984 dragItem: null,
985 startX: 0,
986 startY: 0,
987 startScreenX: 0,
988 startScreenY: 0,
989 dragEndTimer: null,
990
991 handleDragStart: function(e) {
992 var thumbnail = mostVisited.getItem(e.target);
993 if (thumbnail) {
994 e.dataTransfer.setData('text/uri-list', mostVisited.getHref(thumbnail));
995 this.dragItem = thumbnail;
996 addClass(this.dragItem, 'dragging');
997 this.dragItem.style.zIndex = 2;
998 }
999 },
1000
1001 handleDragEnter: function(e) {
1002 this.currentOverItem = mostVisited.getItem(e.target);
1003 if (this.canDropOnElement(this.currentOverItem)) {
1004 e.preventDefault();
1005 }
1006 },
1007
1008 handleDragOver: function(e) {
1009 var item = mostVisited.getItem(e.target);
1010 if (this.canDropOnElement(item)) {
1011 e.preventDefault();
1012 }
1013 },
1014
1015 handleDragLeave: function(e) {
1016 var item = mostVisited.getItem(e.target);
1017 if (item) {
1018 e.preventDefault();
1019 }
1020
1021 this.currentOverItem = null;
1022 },
1023
1024 handleDrop: function(e) {
1025 var dropTarget = mostVisited.getItem(e.target);
1026 if (this.canDropOnElement(dropTarget)) {
1027 dropTarget.style.zIndex = 1;
1028 mostVisited.swapPosition(this.dragItem, dropTarget);
1029 layoutMostVisited();
1030 e.preventDefault();
1031 if (this.dragEndTimer) {
1032 window.clearTimeout(this.dragEndTimer);
1033 this.dragEndTimer = null;
1034 }
1035 afterTransition(function() {
1036 dropTarget.style.zIndex = '';
1037 });
1038 }
1039 },
1040
1041 handleDragEnd: function(e) {
1042 // WebKit fires dragend before drop.
1043 var dragItem = this.dragItem;
1044 if (dragItem) {
1045 dragItem.style.pointerEvents = '';
1046 removeClass(dragItem, 'dragging');
1047
1048 afterTransition(function() {
1049 // Delay resetting zIndex to let the animation finish.
1050 dragItem.style.zIndex = '';
1051 // Same for overflow.
1052 dragItem.parentNode.style.overflow = '';
1053 });
1054 var self = this;
1055 this.dragEndTimer = window.setTimeout(function() {
1056 // These things needto happen after the drop event.
1057 layoutMostVisited();
1058 self.dragItem = null;
1059 }, 10);
1060
1061 }
1062 },
1063
1064 handleDrag: function(e) {
1065 var item = mostVisited.getItem(e.target);
1066 var rect = document.querySelector('#most-visited').getBoundingClientRect();
1067 item.style.pointerEvents = 'none';
1068
1069 item.style.left = this.startX + e.screenX - this.startScreenX + 'px';
1070 item.style.top = this.startY + e.screenY - this.startScreenY + 'px';
1071 },
1072
1073 // We listen to mousedown to get the relative position of the cursor for dnd.
1074 handleMouseDown: function(e) {
1075 var item = mostVisited.getItem(e.target);
1076 if (item) {
1077 this.startX = item.offsetLeft;
1078 this.startY = item.offsetTop;
1079 this.startScreenX = e.screenX;
1080 this.startScreenY = e.screenY;
1081 }
1082 },
1083
1084 canDropOnElement: function(el) {
1085 return this.dragItem && el && hasClass(el, 'thumbnail-container') &&
1086 !hasClass(el, 'filler');
1087 },
1088
1089 init: function() {
1090 var el = $('most-visited');
1091 el.addEventListener('dragstart', bind(this.handleDragStart, this));
1092 el.addEventListener('dragenter', bind(this.handleDragEnter, this));
1093 el.addEventListener('dragover', bind(this.handleDragOver, this));
1094 el.addEventListener('dragleave', bind(this.handleDragLeave, this));
1095 el.addEventListener('drop', bind(this.handleDrop, this));
1096 el.addEventListener('dragend', bind(this.handleDragEnd, this));
1097 el.addEventListener('drag', bind(this.handleDrag, this));
1098 el.addEventListener('mousedown', bind(this.handleMouseDown, this));
1099 }
1100 };
1101
1102 dnd.init();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698