home *** CD-ROM | disk | FTP | other *** search
/ Freelog 115 / FreelogNo115-MaiJuin2013.iso / Internet / AvantBrowser / asetup.exe / _data / webkit / chrome.dll / 0 / BINDATA / 765 < prev    next >
Encoding:
Text File  |  2013-04-03  |  314.4 KB  |  10,278 lines

  1. <!DOCTYPE html>
  2. <html i18n-values="
  3.     dir:textdirection;
  4.     hasattribution:hasattribution;
  5.     themegravity:themegravity;
  6.     bookmarkbarattached:bookmarkbarattached;"
  7.   class="starting-up">
  8. <head>
  9. <meta charset="utf-8">
  10. <title i18n-content="title"></title>
  11. <!-- Don't scale the viewport in either portrait or landscape mode.
  12.      Note that this means apps will be reflowed when rotated (like iPad).
  13.      If we wanted to maintain position we could remove 'maximum-scale' so
  14.      that we'd zoom out in portrait mode, but then there would be a bunch
  15.      of unusable space at the bottom.
  16. -->
  17. <meta name="viewport"
  18.       content="user-scalable=no, width=device-width, maximum-scale=1.0">
  19.  
  20. <!-- It's important that this be the first script loaded. -->
  21. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  22. // Use of this source code is governed by a BSD-style license that can be
  23. // found in the LICENSE file.
  24.  
  25. /**
  26.  * @fileoverview
  27.  * Logging info for benchmarking purposes. Should be the first js file included.
  28.  */
  29.  
  30. /* Stack of events that has been logged. */
  31. var eventLog = [];
  32.  
  33. /**
  34.  * Logs an event.
  35.  * @param {String} name The name of the event (can be any string).
  36.  * @param {boolean} shouldLogTime If true, the event is used for benchmarking
  37.  *     and the time is logged. Otherwise, just push the event on the event
  38.  *     stack.
  39.  */
  40. function logEvent(name, shouldLogTime) {
  41.   if (shouldLogTime)
  42.     chrome.send('metricsHandler:logEventTime', [name]);
  43.   eventLog.push([name, Date.now()]);
  44. }
  45.  
  46. logEvent('Tab.NewTabScriptStart', true);
  47. window.addEventListener('load', function(e) {
  48.   logEvent('Tab.NewTabOnload', true);
  49. });
  50. document.addEventListener('DOMContentLoaded', function(e) {
  51.   logEvent('Tab.NewTabDOMContentLoaded', true);
  52. });
  53. </script>
  54.  
  55. <!-- TODO(jeremycho): This is only used for notifications.  Discuss with UX. -->
  56. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  57.  * Use of this source code is governed by a BSD-style license that can be
  58.  * found in the LICENSE file. */
  59.  
  60. .bubble {
  61.   position: absolute;
  62.   white-space: normal;
  63.   /* Height is dynamic, width fixed. */
  64.   width: 300px;
  65.   z-index: 9999;
  66. }
  67.  
  68. .bubble-content {
  69.   color: black;
  70.   left: 1px;
  71.   line-height: 150%;
  72.   padding: 8px 11px 12px;
  73.   position: relative;
  74.   right: 1px;
  75.   top: 1px;
  76.   width: 298px;
  77.   z-index: 3;
  78. }
  79.  
  80. /* When the close button is there, we need more padding on the right of the
  81.  * bubble. */
  82. .bubble-close:not([hidden]) ~ .bubble-content {
  83.   -webkit-padding-end: 22px;
  84. }
  85.  
  86. .bubble-close {
  87.   background-image: no-repeat 50% 50%;
  88.   height: 16px;
  89.   position: absolute;
  90.   right: 6px;
  91.   top: 6px;
  92.   width: 16px;
  93.   z-index: 4;
  94. }
  95.  
  96. html[dir='rtl'] .bubble-close {
  97.   left: 6px;
  98.   right: auto;
  99. }
  100.  
  101. .bubble-close {
  102.   background-image: -webkit-image-set(
  103.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAiElEQVR42r2RsQrDMAxEBRdl8SDcX8lQPGg1GBI6lvz/h7QyRRXV0qUULwfvwZ1tenw5PxToRPWMC52eA9+WDnlh3HFQ/xBQl86NFYJqeGflkiogrOvVlIFhqURFVho3x1moGAa3deMs+LS30CAhBN5nNxeT5hbJ1zwmji2k+aF6NENIPf/hs54f0sZFUVAMigAAAABJRU5ErkJggg==') 1x);
  104. }
  105.  
  106. .bubble-close:hover {
  107.   background-image: -webkit-image-set(
  108.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAqklEQVR4XqWRMQ6DMAxF/1Fyilyj2SmIBUG5QcTCyJA5Z8jGhlBPgRi4TmoDraVmKFJlWYrlp/g5QfwRlwEVNWVa4WzfH9jK6kCkEkBjwxOhLghheMWMELUAqqwQ4OCbnE4LJnhr5IYdqQt4DJQjhe9u4vBBmnxHHNzRFkDGjHDo0VuTAqy2vAG4NkvXXDHxbGsIGlj3e835VFNtdugma/Jk0eXq0lP//5svi4PtO01oFfYAAAAASUVORK5CYII=')
  109.           1x,
  110.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAB4UlEQVR42u2VsWoCQRBAh+MUFP0C1V9QD4NEOxs9xBQHQVCwSJFWVBAtBNXCxk6wTkBJYUTwEwQLC61E8QP0NzZzt5g5726DkC7EYWHZ8T3WndkV2C/jLwn4hwVYBIdLn9vkLp79QcBCTDMiy3w2gQ9XeTYkEHA8vqj2rworXu3HF1YFfSWgp5QFnKVLvYvzDEKEZ5hW70oXOCtcEbQLIkx7+IQtfMBSOjU6XEF4oyOdYInZbXyOuajjDlpNeQgleIUJKUz4BDMledhqOu/AzVSmzZ49CUjCC0yvim98iqtJT2L2jKsqczsdok9XrHNexaww415lnTNwn6CM/KxJIR8bnUZHPhLO6yMoIyk2pNjLewFuE5AiY1KMMQx8Q7hQYFek4AkjxXFe1rsF84I/BTFQMGL+1Lxwl4DwdtM1gjwKohgxyLtG7SYpxALqugOMcfOKN+bFXeBsLB1uulNcRqq7/tt36k41zoL6QlxGjtd6lrahiqCi1iOFYyvXuxY8yzK33VnvUivbLlOlj/jktm0s3YnXrNIXXufHNxuOGasi8S68zkwrlnV8ZcJJsTIUxbLgQcFZWE8N0gau2p40VVcM0gYeFpSRK6445UhBuKiRgiyKw+34rLt59nb1/7+RwReVkaFtqvNBuwAAAABJRU5ErkJggg==')
  111.           2x);
  112. }
  113.  
  114. .bubble-close:active {
  115.   background-image: -webkit-image-set(
  116.     url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARElEQVQoz2P4z4AfMlBLAYMdwxkghgEwD1XBGTC0g0sDIaYJECVwFqoChBK4WegKkJWArSJZAQErCDqSKG/iCyhaRhYA9LDIbULDzlIAAAAASUVORK5CYII=')
  117.         1x,
  118.     url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAA/ElEQVR4Xu3UsWrCUBiG4efGlIBoIMFbcnYolYJ3pg4iKGrGYFTRwaUFhYAekiDt0EG++X2W83N8/3J/DbwBMJJSsdQItcDY1VlCOImzq3Ed8OmicHASB3ns5KBw8VUNpDJrW7uAiJ3sbK1l0mqArpmFTUlQ5jYWZrrUAUSmT0SZm4qoA56JvVhs/5g3A7RLolA85A1ASOTye65NMxASK6syfxGITMzvMxG9CvRkliWwlOm9AsSOcitzU1NzK7mjuBkQvHtLK7iLBiB5PhttJSGpB8I8vM6kDuiHeUjoVwMfYR4SRtUAw1veIZzOjRhSBzCoyKFjgH/3K7+BHzg+Cgw0eSW3AAAAAElFTkSuQmCC')
  119.         2x);
  120. }
  121.  
  122. .bubble-shadow {
  123.   bottom: -2px;
  124.   box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
  125.   left: 0;
  126.   position: absolute;
  127.   right: 0;
  128.   top: 0;
  129.   z-index: 1;
  130. }
  131.  
  132. .bubble-arrow {
  133.   -webkit-transform: rotate(45deg);
  134.   box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.15);
  135.   height: 15px;
  136.   position: absolute;
  137.   width: 15px;
  138.   z-index: 2;
  139. }
  140.  
  141. .bubble-content,
  142. .bubble-arrow {
  143.   background: white;
  144. }
  145.  
  146. .bubble-shadow,
  147. .bubble-arrow {
  148.   border: 1px solid rgba(0, 0, 0, 0.3);
  149. }
  150.  
  151. .bubble-shadow,
  152. .bubble-content {
  153.   border-radius: 6px;
  154.   box-sizing: border-box;
  155. }
  156. </style>
  157. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  158.  * Use of this source code is governed by a BSD-style license that can be
  159.  * found in the LICENSE file. */
  160.  
  161. .expandable-bubble {
  162.   -webkit-border-image: url('chrome://theme/IDR_APP_NOTIFICATION_SMALL_BUBBLE')
  163.                         5 5 7 6 stretch;
  164.   -webkit-box-sizing: border-box;
  165.   -webkit-user-select: none;
  166.   border-width: 5px 5px 7px 6px;
  167.   color: #444;
  168.   cursor: pointer;
  169.   display: inline-block;
  170.   font-size: 12px;
  171.   position: absolute;
  172.   z-index: 1;
  173. }
  174.  
  175. .expandable-bubble::after {
  176.   bottom: -1px;
  177.   content: url('chrome://theme/IDR_APP_NOTIFICATION_NUB');
  178.   display: block;
  179.   height: 7px;
  180.   position: absolute;
  181.   right: 5px;  /* TODO(finnur): Need to handle RTL properly. */
  182.   width: 9px;
  183. }
  184.  
  185. .expandable-bubble > .expandable-bubble-contents > .expandable-bubble-title {
  186.   display: inline-block;
  187.   margin-left: 1px;
  188.   margin-top : -3px;
  189.   overflow: hidden;
  190.   white-space: nowrap;
  191. }
  192.  
  193. .expandable-bubble[masked] > .expandable-bubble-contents >
  194.     .expandable-bubble-title::after {
  195.   content: url('chrome://theme/IDR_APP_NOTIFICATION_NUB_MASK');
  196.   display: block;
  197.   height: 15px;
  198.   overflow: hidden;
  199.   position: absolute;
  200.   right: 0;
  201.   top: 0;
  202.   width: 12px;
  203. }
  204.  
  205. .expandable-bubble[expanded] > .expandable-bubble-contents >
  206.     .expandable-bubble-title {
  207.   font-size: 13px;
  208.   margin-bottom: 3px;
  209.   margin-left: 0;
  210. }
  211.  
  212. .expandable-bubble-close {
  213.   background-image: no-repeat 50% 50%;
  214.   height: 16px;
  215.   position: absolute;
  216.   right: 0;
  217.   top: 0;
  218.   width: 16px;
  219.   z-index: 2;
  220. }
  221.  
  222. .expandable-bubble[expanded] {
  223.   padding: 3px;
  224.   z-index: 3;  /* One higher then the close button on an unexpanded bubble. */
  225. }
  226.  
  227. .expandable-bubble[expanded] > .expandable-bubble-close {
  228.   z-index: 4;
  229. }
  230.  
  231. .expandable-bubble-close {
  232.   background-image: -webkit-image-set(
  233.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAiElEQVR42r2RsQrDMAxEBRdl8SDcX8lQPGg1GBI6lvz/h7QyRRXV0qUULwfvwZ1tenw5PxToRPWMC52eA9+WDnlh3HFQ/xBQl86NFYJqeGflkiogrOvVlIFhqURFVho3x1moGAa3deMs+LS30CAhBN5nNxeT5hbJ1zwmji2k+aF6NENIPf/hs54f0sZFUVAMigAAAABJRU5ErkJggg==') 1x);
  234. }
  235.  
  236. .expandable-bubble-close:hover {
  237.   background-image: -webkit-image-set(
  238.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAqklEQVR4XqWRMQ6DMAxF/1Fyilyj2SmIBUG5QcTCyJA5Z8jGhlBPgRi4TmoDraVmKFJlWYrlp/g5QfwRlwEVNWVa4WzfH9jK6kCkEkBjwxOhLghheMWMELUAqqwQ4OCbnE4LJnhr5IYdqQt4DJQjhe9u4vBBmnxHHNzRFkDGjHDo0VuTAqy2vAG4NkvXXDHxbGsIGlj3e835VFNtdugma/Jk0eXq0lP//5svi4PtO01oFfYAAAAASUVORK5CYII=')
  239.           1x,
  240.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAB4UlEQVR42u2VsWoCQRBAh+MUFP0C1V9QD4NEOxs9xBQHQVCwSJFWVBAtBNXCxk6wTkBJYUTwEwQLC61E8QP0NzZzt5g5726DkC7EYWHZ8T3WndkV2C/jLwn4hwVYBIdLn9vkLp79QcBCTDMiy3w2gQ9XeTYkEHA8vqj2rworXu3HF1YFfSWgp5QFnKVLvYvzDEKEZ5hW70oXOCtcEbQLIkx7+IQtfMBSOjU6XEF4oyOdYInZbXyOuajjDlpNeQgleIUJKUz4BDMledhqOu/AzVSmzZ49CUjCC0yvim98iqtJT2L2jKsqczsdok9XrHNexaww415lnTNwn6CM/KxJIR8bnUZHPhLO6yMoIyk2pNjLewFuE5AiY1KMMQx8Q7hQYFek4AkjxXFe1rsF84I/BTFQMGL+1Lxwl4DwdtM1gjwKohgxyLtG7SYpxALqugOMcfOKN+bFXeBsLB1uulNcRqq7/tt36k41zoL6QlxGjtd6lrahiqCi1iOFYyvXuxY8yzK33VnvUivbLlOlj/jktm0s3YnXrNIXXufHNxuOGasi8S68zkwrlnV8ZcJJsTIUxbLgQcFZWE8N0gau2p40VVcM0gYeFpSRK6445UhBuKiRgiyKw+34rLt59nb1/7+RwReVkaFtqvNBuwAAAABJRU5ErkJggg==')
  241.           2x);
  242. }
  243.  
  244. .expandable-bubble-close:active {
  245.   background-image: -webkit-image-set(
  246.     url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARElEQVQoz2P4z4AfMlBLAYMdwxkghgEwD1XBGTC0g0sDIaYJECVwFqoChBK4WegKkJWArSJZAQErCDqSKG/iCyhaRhYA9LDIbULDzlIAAAAASUVORK5CYII=')
  247.         1x,
  248.     url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAA/ElEQVR4Xu3UsWrCUBiG4efGlIBoIMFbcnYolYJ3pg4iKGrGYFTRwaUFhYAekiDt0EG++X2W83N8/3J/DbwBMJJSsdQItcDY1VlCOImzq3Ed8OmicHASB3ns5KBw8VUNpDJrW7uAiJ3sbK1l0mqArpmFTUlQ5jYWZrrUAUSmT0SZm4qoA56JvVhs/5g3A7RLolA85A1ASOTye65NMxASK6syfxGITMzvMxG9CvRkliWwlOm9AsSOcitzU1NzK7mjuBkQvHtLK7iLBiB5PhttJSGpB8I8vM6kDuiHeUjoVwMfYR4SRtUAw1veIZzOjRhSBzCoyKFjgH/3K7+BHzg+Cgw0eSW3AAAAAElFTkSuQmCC')
  249.         2x);
  250. }
  251. </style>
  252. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  253.  * Use of this source code is governed by a BSD-style license that can be
  254.  * found in the LICENSE file. */
  255.  
  256. menu {
  257.   -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, .50);
  258.   background: white;
  259.   color: black;
  260.   cursor: default;
  261.   left: 0;
  262.   margin: 0;
  263.   outline: 1px solid rgba(0, 0, 0, 0.2);
  264.   padding: 8px 0;
  265.   position: fixed;
  266.   white-space: nowrap;
  267.   z-index: 3;
  268. }
  269.  
  270. menu:not(.decorated) {
  271.   display: none;
  272. }
  273.  
  274. menu > * {
  275.   box-sizing: border-box;
  276.   display: block;
  277.   margin: 0;
  278.   text-align: start;
  279.   width: 100%;
  280. }
  281.  
  282. menu > :not(hr) {
  283.   -webkit-appearance: none;
  284.   background: transparent;
  285.   border: 0;
  286.   font: inherit;
  287.   line-height: 18px;
  288.   overflow: hidden;
  289.   padding: 0 19px;
  290.   text-overflow: ellipsis;
  291. }
  292.  
  293. menu > hr {
  294.   background: -webkit-linear-gradient(left,
  295.                                       rgba(0, 0, 0, .10),
  296.                                       rgba(0, 0, 0, .02) 96%);
  297.   border: 0;
  298.   height: 1px;
  299.   margin: 8px 0;
  300. }
  301.  
  302. menu > [disabled] {
  303.   color: rgba(0, 0, 0, .3);
  304. }
  305.  
  306. menu > [hidden] {
  307.   display: none;
  308. }
  309.  
  310. menu > :not(hr)[selected] {
  311.   background-color: rgb(220, 229, 250);
  312. }
  313.  
  314. menu > :not(hr)[selected]:active {
  315.   background-color: rgb(66, 109, 201);
  316.   color: #fff;
  317. }
  318.  
  319. menu > [checked]::before {
  320.   content: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAARklEQVQY02NgwA+MgViQkIJ3QKzEAFVpjEPBf5giJaiAMRYF72DWKSEJlKMpgNsgiCTxH5sCBhxWGOPzzV2sCv7//08QAwAUfjKK4sDXvQAAAABJRU5ErkJggg==');
  321.   display: inline-block;
  322.   height: 9px;
  323.   margin: 0 5px;
  324.   vertical-align: 50%;
  325.   width: 9px;
  326. }
  327.  
  328. menu > [checked] {
  329.   -webkit-padding-start: 0;
  330. }
  331.  
  332. menu > [selected][checked]:active::before {
  333.   content: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAATUlEQVQYV2P4//8/Ax5sDMSChBS8A2IlEEcQKoBNwX+YIiWoAEwhsgIQLQhTBBMoR1MA1gizDiYBA8gmM2BzA4oCZEUwhXfRFaArwokBBR4JwRMmHQoAAAAASUVORK5CYII=');
  334. }
  335.  
  336. /* TODO(zvorygin) menu > [shortcutText]::after - this selector is much better,
  337.  * but it's buggy in current webkit revision, so I have to use [showShortcuts].
  338.  */
  339. menu[showShortcuts] > ::after {
  340.   -webkit-padding-start: 30px;
  341.   color: #999;
  342.   content: attr(shortcutText);
  343.   float: right;
  344. }
  345. </style>
  346. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  347.  * Use of this source code is governed by a BSD-style license that can be
  348.  * found in the LICENSE file. */
  349.  
  350. /* NOTE: If you are using the drop-down style, you must first call
  351.  * MenuButton.createDropDownArrows() to initialize the CSS canvases that
  352.  * contain the arrow images. */
  353.  
  354. button.menu-button.drop-down {
  355.   background: white -webkit-canvas(drop-down-arrow) no-repeat center 4px;
  356.   border: 1px solid rgb(192, 195, 198);
  357.   border-radius: 2px;
  358.   height: 12px;
  359.   margin: 0 5px;
  360.   padding: 0;
  361.   position: relative;
  362.   top: 1px;
  363.   width: 12px;
  364. }
  365.  
  366. button.menu-button.drop-down:hover {
  367.   background-image: -webkit-canvas(drop-down-arrow-hover);
  368.   border-color: rgb(48, 57, 66);
  369. }
  370.  
  371. button.menu-button.drop-down[menu-shown],
  372. button.menu-button.drop-down:focus {
  373.   background-color: rgb(48, 57, 66);
  374.   background-image: -webkit-canvas(drop-down-arrow-active);
  375.   border-color: rgb(48, 57, 66);
  376. }
  377. </style>
  378. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  379.  * Use of this source code is governed by a BSD-style license that can be
  380.  * found in the LICENSE file. */
  381.  
  382. /* This file defines styles for form controls. The order of rule blocks is
  383.  * important as there are some rules with equal specificity that rely on order
  384.  * as a tiebreaker. These are marked with OVERRIDE. */
  385.  
  386. /* Default state **************************************************************/
  387.  
  388. :-webkit-any(button,
  389.              input[type='button'],
  390.              input[type='submit']):not(.custom-appearance):not(.link-button),
  391. select,
  392. input[type='checkbox'],
  393. input[type='radio'] {
  394.   -webkit-appearance: none;
  395.   -webkit-user-select: none;
  396.   background-image: -webkit-linear-gradient(#ededed, #ededed 38%, #dedede);
  397.   border: 1px solid rgba(0, 0, 0, 0.25);
  398.   border-radius: 2px;
  399.   box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
  400.       inset 0 1px 2px rgba(255, 255, 255, 0.75);
  401.   color: #444;
  402.   font: inherit;
  403.   margin: 0 1px 0 0;
  404.   text-shadow: 0 1px 0 rgb(240, 240, 240);
  405. }
  406.  
  407. :-webkit-any(button,
  408.              input[type='button'],
  409.              input[type='submit']):not(.custom-appearance):not(.link-button),
  410. select {
  411.   min-height: 2em;
  412.   min-width: 4em;
  413. /* The following platform-specific rule is necessary to get adjacent
  414.    * buttons, text inputs, and so forth to align on their borders while also
  415.    * aligning on the text's baselines. */
  416.   padding-bottom: 1px;
  417. }
  418.  
  419. :-webkit-any(button,
  420.              input[type='button'],
  421.              input[type='submit']):not(.custom-appearance):not(.link-button) {
  422.   -webkit-padding-end: 10px;
  423.   -webkit-padding-start: 10px;
  424. }
  425.  
  426. select {
  427.   -webkit-appearance: none;
  428.   -webkit-padding-end: 20px;
  429.   -webkit-padding-start: 6px;
  430.   /* OVERRIDE */
  431.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAaUlEQVQoz2P4//8/A7UwdkEGhiggTsODo4g2LBEImJmZvwE1/UfHIHGQPNGGAbHCggULFrKxsf1ENgjEB4mD5EnxJoaByAZB5Yk3DNlAPj6+L8gGkWUYzMC3b982IRtEtmFQjaxYxDAwAGi4TwMYKNLfAAAAAElFTkSuQmCC'),
  432.       -webkit-linear-gradient(#ededed, #ededed 38%, #dedede);
  433.   background-position: right center;
  434.   background-repeat: no-repeat;
  435. }
  436.  
  437. html[dir='rtl'] select {
  438.   background-position: center left;
  439. }
  440.  
  441. input[type='checkbox'] {
  442.   bottom: 2px;
  443.   height: 13px;
  444.   position: relative;
  445.   vertical-align: middle;
  446.   width: 13px;
  447. }
  448.  
  449. input[type='radio'] {
  450.   /* OVERRIDE */
  451.   border-radius: 100%;
  452.   bottom: 3px;
  453.   height: 15px;
  454.   position: relative;
  455.   vertical-align: middle;
  456.   width: 15px;
  457. }
  458.  
  459. /* TODO(estade): add more types here? */
  460. input[type='password'],
  461. input[type='search'],
  462. input[type='text'],
  463. input[type='url'],
  464. input:not([type]),
  465. textarea {
  466.   border: 1px solid #bfbfbf;
  467.   border-radius: 2px;
  468.   box-sizing: border-box;
  469.   color: #444;
  470.   font: inherit;
  471.   margin: 0;
  472.   /* Use min-height to accommodate addditional padding for touch as needed. */
  473.   min-height: 2em;
  474.   padding: 3px;
  475. /* For better alignment between adjacent buttons and inputs. */
  476.   padding-bottom: 4px;
  477. }
  478.  
  479. input[type='search'] {
  480.   -webkit-appearance: textfield;
  481.   /* NOTE: Keep a relatively high min-width for this so we don't obscure the end
  482.    * of the default text in relatively spacious languages (i.e. German). */
  483.   min-width: 160px;
  484. }
  485.  
  486. /* Remove when https://bugs.webkit.org/show_bug.cgi?id=51499 is fixed.
  487.  * TODO(dbeam): are there more types that would benefit from this? */
  488. input[type='search']::-webkit-textfield-decoration-container {
  489.   direction: inherit;
  490. }
  491.  
  492. /* Checked ********************************************************************/
  493.  
  494. input[type='checkbox']:checked::before {
  495.   -webkit-user-select: none;
  496.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAcklEQVQY02NgwA/YoJgoEA/Es4DYgJBCJSBeD8SboRinBiYg7kZS2IosyQ/Eakh8LySFq4FYHFlxGRBvBOJYqMRqJMU+yApNkSRAeC0Sux3dfSCTetE0wKyXxOWhMKhTYIr9CAUXyJMzgLgBagBBgDPGAI2LGdNt0T1AAAAAAElFTkSuQmCC');
  497.   background-size: 100% 100%;
  498.   content: '';
  499.   display: block;
  500.   height: 100%;
  501.   width: 100%;
  502. }
  503.  
  504. html[dir='rtl'] input[type='checkbox']:checked::before {
  505.   -webkit-transform: scaleX(-1);
  506. }
  507.  
  508. input[type='radio']:checked::before {
  509.   background-color: #666;
  510.   border-radius: 100%;
  511.   bottom: 3px;
  512.   content: '';
  513.   display: block;
  514.   left: 3px;
  515.   position: absolute;
  516.   right: 3px;
  517.   top: 3px;
  518. }
  519.  
  520. /* Hover **********************************************************************/
  521.  
  522. :enabled:hover:-webkit-any(
  523.     select,
  524.     input[type='checkbox'],
  525.     input[type='radio'],
  526.     :-webkit-any(
  527.         button,
  528.         input[type='button'],
  529.         input[type='submit']):not(.custom-appearance):not(.link-button)) {
  530.   background-image: -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
  531.   border-color: rgba(0, 0, 0, 0.3);
  532.   box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12),
  533.       inset 0 1px 2px rgba(255, 255, 255, 0.95);
  534.   color: black;
  535. }
  536.  
  537. :enabled:hover:-webkit-any(select) {
  538.   /* OVERRIDE */
  539.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAaUlEQVQoz2P4//8/A7UwdkEGhiggTsODo4g2LBEImJmZvwE1/UfHIHGQPNGGAbHCggULFrKxsf1ENgjEB4mD5EnxJoaByAZB5Yk3DNlAPj6+L8gGkWUYzMC3b982IRtEtmFQjaxYxDAwAGi4TwMYKNLfAAAAAElFTkSuQmCC'),
  540.       -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
  541. }
  542.  
  543. /* Active *********************************************************************/
  544.  
  545. :enabled:active:-webkit-any(
  546.     select,
  547.     input[type='checkbox'],
  548.     input[type='radio'],
  549.     :-webkit-any(
  550.         button,
  551.         input[type='button'],
  552.         input[type='submit']):not(.custom-appearance):not(.link-button)) {
  553.   background-image: -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
  554.   box-shadow: none;
  555.   text-shadow: none;
  556. }
  557.  
  558. :enabled:active:-webkit-any(select) {
  559.   /* OVERRIDE */
  560.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAaUlEQVQoz2P4//8/A7UwdkEGhiggTsODo4g2LBEImJmZvwE1/UfHIHGQPNGGAbHCggULFrKxsf1ENgjEB4mD5EnxJoaByAZB5Yk3DNlAPj6+L8gGkWUYzMC3b982IRtEtmFQjaxYxDAwAGi4TwMYKNLfAAAAAElFTkSuQmCC'),
  561.       -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
  562. }
  563.  
  564. /* Disabled *******************************************************************/
  565.  
  566. :disabled:-webkit-any(
  567.     button,
  568.     input[type='button'],
  569.     input[type='submit']):not(.custom-appearance):not(.link-button),
  570. select:disabled {
  571.   background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
  572.   border-color: rgba(80, 80, 80, 0.2);
  573.   box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08),
  574.       inset 0 1px 2px rgba(255, 255, 255, 0.75);
  575.   color: #aaa;
  576. }
  577.  
  578. select:disabled {
  579.   /* OVERRIDE */
  580.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAWklEQVQoz2P4//8/A7UwdkEGhiggTsODo4g2LBEIGhoa/uPCIHmiDQNihQULFizEZhBIHCRPijexGggzCCpPvGHoBiIbRJZhMAPfvn3bhGwQ2YZBNbJiEcPAAIgGZrTRc1ZLAAAAAElFTkSuQmCC'),
  581.       -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
  582. }
  583.  
  584. input:disabled:-webkit-any([type='checkbox'],
  585.                            [type='radio']) {
  586.   opacity: .75;
  587. }
  588.  
  589. input:disabled:-webkit-any([type='password'],
  590.                            [type='search'],
  591.                            [type='text'],
  592.                            [type='url'],
  593.                            :not([type])) {
  594.   color: #999;
  595. }
  596.  
  597. /* Focus **********************************************************************/
  598.  
  599. :enabled:focus:-webkit-any(
  600.     select,
  601.     input[type='checkbox'],
  602.     input[type='password'],
  603.     input[type='radio'],
  604.     input[type='search'],
  605.     input[type='text'],
  606.     input[type='url'],
  607.     input:not([type]),
  608.     :-webkit-any(
  609.          button,
  610.          input[type='button'],
  611.          input[type='submit']):not(.custom-appearance):not(.link-button)) {
  612.   /* OVERRIDE */
  613.   -webkit-transition: border-color 200ms;
  614.   /* We use border color because it follows the border radius (unlike outline).
  615.    * This is particularly noticeable on mac. */
  616.   border-color: rgb(77, 144, 254);
  617.   outline: none;
  618. }
  619.  
  620. /* Link buttons ***************************************************************/
  621.  
  622. .link-button {
  623.   -webkit-box-shadow: none;
  624.   background: transparent none;
  625.   border: none;
  626.   color: rgb(17, 85, 204);
  627.   cursor: pointer;
  628.   /* Input elements have -webkit-small-control which can override the body font.
  629.    * Resolve this by using 'inherit'. */
  630.   font: inherit;
  631.   margin: 0;
  632.   padding: 0 4px;
  633. }
  634.  
  635. .link-button:hover {
  636.   text-decoration: underline;
  637. }
  638.  
  639. .link-button:active {
  640.   color: rgb(5, 37, 119);
  641.   text-decoration: underline;
  642. }
  643.  
  644. .link-button[disabled] {
  645.   color: #999;
  646.   cursor: default;
  647.   text-decoration: none;
  648. }
  649.  
  650. /* Checkbox/radio helpers ******************************************************
  651.  *
  652.  * .checkbox and .radio classes wrap labels. Checkboxes and radios should use
  653.  * these classes with the markup structure:
  654.  *
  655.  *   <div class="checkbox">
  656.  *     <label>
  657.  *       <input type="checkbox"></input>
  658.  *       <span>
  659.  *     </label>
  660.  *   </div>
  661.  */
  662.  
  663. :-webkit-any(.checkbox, .radio) label {
  664.   /* Don't expand horizontally: <http://crbug.com/112091>. */
  665.   display: -webkit-inline-box;
  666.   padding-bottom: 7px;
  667.   padding-top: 7px;
  668. }
  669.  
  670. :-webkit-any(.checkbox, .radio) label input ~ span {
  671.   -webkit-margin-start: 0.6em;
  672.   /* Make sure long spans wrap at the same horizontal position they start. */
  673.   display: block;
  674. }
  675.  
  676. :-webkit-any(.checkbox, .radio) label:hover {
  677.   color: black;
  678. }
  679.  
  680. label > input:disabled:-webkit-any([type='checkbox'], [type='radio']) ~ span {
  681.   color: #999;
  682. }
  683. </style>
  684.  
  685. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  686.  * Use of this source code is governed by a BSD-style license that can be
  687.  * found in the LICENSE file. */
  688.  
  689. .close-button {
  690.   background: no-repeat;
  691.   background-color: transparent;
  692.   /* TODO(estade): this should animate between states. */
  693.   background-image: -webkit-image-set(
  694.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAiElEQVR42r2RsQrDMAxEBRdl8SDcX8lQPGg1GBI6lvz/h7QyRRXV0qUULwfvwZ1tenw5PxToRPWMC52eA9+WDnlh3HFQ/xBQl86NFYJqeGflkiogrOvVlIFhqURFVho3x1moGAa3deMs+LS30CAhBN5nNxeT5hbJ1zwmji2k+aF6NENIPf/hs54f0sZFUVAMigAAAABJRU5ErkJggg==') 1x);
  695.   border: 0;
  696.   cursor: default;
  697.   display: inline-block;
  698.   height: 16px;
  699.   padding: 0;
  700.   width: 16px;
  701.   z-index: 999;
  702. }
  703.  
  704. .close-button:hover,
  705. .close-button:focus {
  706.   background-image: -webkit-image-set(
  707.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAqklEQVR4XqWRMQ6DMAxF/1Fyilyj2SmIBUG5QcTCyJA5Z8jGhlBPgRi4TmoDraVmKFJlWYrlp/g5QfwRlwEVNWVa4WzfH9jK6kCkEkBjwxOhLghheMWMELUAqqwQ4OCbnE4LJnhr5IYdqQt4DJQjhe9u4vBBmnxHHNzRFkDGjHDo0VuTAqy2vAG4NkvXXDHxbGsIGlj3e835VFNtdugma/Jk0eXq0lP//5svi4PtO01oFfYAAAAASUVORK5CYII=')
  708.           1x,
  709.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAB4UlEQVR42u2VsWoCQRBAh+MUFP0C1V9QD4NEOxs9xBQHQVCwSJFWVBAtBNXCxk6wTkBJYUTwEwQLC61E8QP0NzZzt5g5726DkC7EYWHZ8T3WndkV2C/jLwn4hwVYBIdLn9vkLp79QcBCTDMiy3w2gQ9XeTYkEHA8vqj2rworXu3HF1YFfSWgp5QFnKVLvYvzDEKEZ5hW70oXOCtcEbQLIkx7+IQtfMBSOjU6XEF4oyOdYInZbXyOuajjDlpNeQgleIUJKUz4BDMledhqOu/AzVSmzZ49CUjCC0yvim98iqtJT2L2jKsqczsdok9XrHNexaww415lnTNwn6CM/KxJIR8bnUZHPhLO6yMoIyk2pNjLewFuE5AiY1KMMQx8Q7hQYFek4AkjxXFe1rsF84I/BTFQMGL+1Lxwl4DwdtM1gjwKohgxyLtG7SYpxALqugOMcfOKN+bFXeBsLB1uulNcRqq7/tt36k41zoL6QlxGjtd6lrahiqCi1iOFYyvXuxY8yzK33VnvUivbLlOlj/jktm0s3YnXrNIXXufHNxuOGasi8S68zkwrlnV8ZcJJsTIUxbLgQcFZWE8N0gau2p40VVcM0gYeFpSRK6445UhBuKiRgiyKw+34rLt59nb1/7+RwReVkaFtqvNBuwAAAABJRU5ErkJggg==')
  710.           2x);
  711. }
  712.  
  713. .close-button:active {
  714.   background-image: -webkit-image-set(
  715.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARElEQVQoz2P4z4AfMlBLAYMdwxkghgEwD1XBGTC0g0sDIaYJECVwFqoChBK4WegKkJWArSJZAQErCDqSKG/iCyhaRhYA9LDIbULDzlIAAAAASUVORK5CYII=')
  716.           1x,
  717.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAA/ElEQVR4Xu3UsWrCUBiG4efGlIBoIMFbcnYolYJ3pg4iKGrGYFTRwaUFhYAekiDt0EG++X2W83N8/3J/DbwBMJJSsdQItcDY1VlCOImzq3Ed8OmicHASB3ns5KBw8VUNpDJrW7uAiJ3sbK1l0mqArpmFTUlQ5jYWZrrUAUSmT0SZm4qoA56JvVhs/5g3A7RLolA85A1ASOTye65NMxASK6syfxGITMzvMxG9CvRkliWwlOm9AsSOcitzU1NzK7mjuBkQvHtLK7iLBiB5PhttJSGpB8I8vM6kDuiHeUjoVwMfYR4SRtUAw1veIZzOjRhSBzCoyKFjgH/3K7+BHzg+Cgw0eSW3AAAAAElFTkSuQmCC')
  718.           2x);
  719. }
  720.  
  721. /* TODO(pedrosimonetti): Organize these rules. */
  722. .thumbnail .close-button {
  723.   -webkit-transition: opacity 500ms ease-in-out;
  724.   opacity: 0;
  725.   position: absolute;
  726.   right: 2px;
  727.   top: 2px;
  728. }
  729.  
  730. html[dir=rtl] .thumbnail .close-button {
  731.   left: 0;
  732.   right: auto;
  733. }
  734.  
  735. .thumbnail:hover .close-button {
  736.   -webkit-transition-delay: 500ms;
  737.   opacity: 1;
  738. }
  739.  
  740. .thumbnail .close-button:hover {
  741.   -webkit-transition: none;
  742. }
  743. </style>
  744. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  745.  * Use of this source code is governed by a BSD-style license that can be
  746.  * found in the LICENSE file. */
  747.  
  748. #dot-list {
  749.   height: 15px;
  750.   margin: 0;
  751.   overflow: hidden;
  752.   padding: 0;
  753. }
  754.  
  755. .dot {
  756.   -webkit-transition: color 200ms;
  757.   color: #666;
  758.   cursor: pointer;
  759.   display: inline-block;
  760.   font-size: 1.0833em;
  761.   margin: 0 1em;
  762.   min-width: 55px;
  763.   outline: none;
  764.   text-shadow: 0 1px 0 rgba(255, 255, 255, .7);
  765.   white-space: nowrap;
  766. }
  767.  
  768. .dot.selected {
  769.   color: rgb(221, 75, 57);
  770. }
  771. </style>
  772. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  773.  * Use of this source code is governed by a BSD-style license that can be
  774.  * found in the LICENSE file. */
  775.  
  776. /* -----------------------------------------------------------------------------
  777.    General Styles
  778. ----------------------------------------------------------------------------- */
  779.  
  780. html {
  781.   /* It's necessary to put this here instead of in body in order to get the
  782.      background-size of 100% to work properly */
  783.   height: 100%;
  784.   overflow: hidden;
  785. }
  786.  
  787. body {
  788.   /* Don't highlight links when they're tapped. Safari has bugs here that
  789.      show up as flicker when dragging in some situations */
  790.   -webkit-tap-highlight-color: transparent;
  791.   /* Don't allow selecting text - can occur when dragging */
  792.   -webkit-user-select: none;
  793.   background-size: auto 100%;
  794.   /* TODO(pedrosimonetti): Confirm with designers/engineers what do we want
  795.    * to do regarding font family, once AFAIK we want to use Arial always. */
  796.   font-family: Arial;
  797.   margin: 0;
  798.   overflow: hidden;
  799.   padding: 0;
  800. }
  801.  
  802. /* [hidden] does display:none, but its priority is too low in some cases. */
  803. [hidden] {
  804.   display: none !important;
  805. }
  806.  
  807. #notification-container {
  808.   height: 18px;
  809.   padding: 10px 0;
  810. }
  811.  
  812. #notification {
  813.   display: inline-block;
  814.   font-weight: bold;
  815.   white-space: nowrap;
  816. }
  817.  
  818. #notification > div > div,
  819. #notification > div {
  820.   display: inline-block;
  821. }
  822.  
  823. /* NOTE: This is in the probable case that we start stuffing 16x16 data URI'd
  824.  * icons in the promo notification responses. */
  825. #notification > span > img {
  826.   margin-bottom: -3px;
  827. }
  828.  
  829. #notification .close-button {
  830.   -webkit-margin-start: 8px;
  831.   vertical-align: top;
  832. }
  833.  
  834. .link-button {
  835.   -webkit-margin-start: 0.5em;
  836. }
  837.  
  838. #bottom-panel {
  839.   -webkit-transition: opacity 200ms;
  840.   position: absolute;
  841.   top: 300px;
  842.   visibility: hidden;
  843.   width: 100%;
  844. }
  845.  
  846. .hide-bottom-panel {
  847.   opacity: 0;
  848. }
  849.  
  850. #card-slider-frame {
  851.   height: 100px;
  852.   overflow: hidden;
  853. }
  854.  
  855. #page-list {
  856.   display: -webkit-box;
  857.   height: 100%;
  858.   position: static;
  859. }
  860.  
  861. #bottom-panel-header,
  862. #bottom-panel-footer,
  863. #bottom-panel-toolbar {
  864.   margin: 0 auto;
  865.   text-align: center;
  866. }
  867.  
  868. #bottom-panel-header {
  869.   padding: 10px 0 20px;
  870. }
  871.  
  872. #bookmark-bar-spacer {
  873.   height: 48px;
  874. }
  875.  
  876. #promo-bubble-anchor {
  877.   bottom: 50px;
  878.   height: 10px;
  879.   left: 0;
  880.   position: absolute;
  881.   width: 90px;
  882. }
  883.  
  884. .bubble .bubble-arrow {
  885.   display: none;
  886. }
  887.  
  888. /* TODO(jeremycho): Figure out if we need this. */
  889. #attribution {
  890.   bottom: 0;
  891.   left: auto;
  892.   margin-left: 8px;
  893.   /* Leave room for the scrollbar. */
  894.   margin-right: 13px;
  895.   position: absolute;
  896.   right: 0;
  897.   text-align: left;
  898.   z-index: -5;
  899. }
  900.  
  901. /* For themes that right-align their images, we flip the attribution to the
  902.  * left to avoid conflicts. We also do this for bare-minimum mode since there
  903.  * can be conflicts with the recently closed menu. */
  904. html[themegravity='right'] #attribution,
  905. body.bare-minimum #attribution,
  906. html[dir='rtl'] #attribution {
  907.   left: 0;
  908.   right: auto;
  909.   text-align: right;
  910. }
  911.  
  912. #attribution > span {
  913.   display: block;
  914. }
  915.  
  916. .starting-up * {
  917.   -webkit-transition: none !important;
  918. }
  919. </style>
  920. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  921.  * Use of this source code is governed by a BSD-style license that can be
  922.  * found in the LICENSE file. */
  923.  
  924. /* -----------------------------------------------------------------------------
  925.   Thumbnail Cell and Tile
  926. ----------------------------------------------------------------------------- */
  927.  
  928. .thumbnail-page .tile-cell {
  929.   -webkit-margin-start: 12px;
  930.   height: 68px;
  931.   margin-bottom: 12px;
  932.   width: 110px;
  933. }
  934.  
  935. .thumbnail-page .tile {
  936.   background: #fff;
  937.   border: 1px solid rgb(192, 192, 192);
  938.   border-radius: 2px;
  939.   box-shadow: 0 1px 0 rgba(255, 255, 255, .7);
  940.   display: block;
  941.   height: 66px;
  942.   outline: none;  /* Avoids outline glitch when blacklisting a thumbnail. */
  943.   position: absolute;
  944.   width: 108px;
  945. }
  946.  
  947. .thumbnail-page .tile:hover,
  948. .thumbnail-page .tile:hover .thumbnail-card {
  949.   /* TODO(pedrosimonetti): Confirm value with Marcin. */
  950.   border-color: rgb(127, 127, 127);
  951. }
  952.  
  953. .thumbnail-page .tile-cell.filler .tile  {
  954.   background: -webkit-linear-gradient(rgb(242, 242, 242), rgb(232, 232, 232));
  955.   border-color: rgb(224, 224, 224);
  956.   border-radius: 3px;
  957.   box-shadow: inset 0 2px 3px rgba(0, 0, 0, .09);
  958. }
  959.  
  960. /* -----------------------------------------------------------------------------
  961.   Thumbnail
  962. ----------------------------------------------------------------------------- */
  963.  
  964. .thumbnail .thumbnail-image {
  965.   /* These values are equivalent to background-size: 100%.
  966.      TODO(jeremycho): Resolve the discrepancy with the cell dimensions above. */
  967.   background-size: 110px 68px;
  968. }
  969.  
  970. .thumbnail .title,
  971. .thumbnail-banner {
  972.   color: #777;
  973.   font-size: 0.9167em;
  974.   overflow: hidden;
  975.   position: absolute;
  976.   text-align: center;
  977.   text-overflow: ellipsis;
  978.   white-space: nowrap;
  979.   width: 100%;
  980. }
  981.  
  982. .thumbnail .title {
  983.   bottom: -26px;
  984. }
  985.  
  986. .thumbnail-banner {
  987.   bottom: 25px;
  988.   display: block;
  989.   font-size: 1.14em;
  990.   margin: 0 7px;
  991.   width: 88%;
  992. }
  993.  
  994. .thumbnail,
  995. .thumbnail-wrapper,
  996. .thumbnail-card {
  997.   -webkit-background-clip: padding-box;
  998. }
  999.  
  1000. .thumbnail-wrapper {
  1001.   display: block;
  1002.   height: 100%;
  1003.   overflow: hidden;
  1004.   width: 100%;
  1005. }
  1006.  
  1007. .thumbnail-favicon {
  1008.   height: 16px;
  1009.   margin: 0 auto;
  1010.   position: relative;
  1011.   top: -8px;
  1012.   width: 16px;
  1013. }
  1014.  
  1015. .thumbnail-card {
  1016.   /* This gives a 3px offset between consecutive thumbnails on the stack and
  1017.      should be kept in sync with RecentlyClosed's STACK_OFFSET. */
  1018.   -webkit-margin-start: -129px;
  1019.   border: 1px solid silver;
  1020.   border-radius: 2px;
  1021.   display: inline-block;
  1022.   height: 100%;
  1023.   margin-top: -1px;
  1024.   position: relative;
  1025.   width: 100%;
  1026. }
  1027.  
  1028. .thumbnail-card:first-child {
  1029.   -webkit-margin-start: -1px;
  1030. }
  1031. </style>
  1032. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1033.  * Use of this source code is governed by a BSD-style license that can be
  1034.  * found in the LICENSE file. */
  1035.  
  1036. /* -----------------------------------------------------------------------------
  1037.   Tile Page
  1038. ----------------------------------------------------------------------------- */
  1039.  
  1040. .tile-page {
  1041.   overflow: hidden;
  1042.   position: relative;
  1043. }
  1044.  
  1045. .tile-page-frame {
  1046.   margin: 0 auto;
  1047.   overflow: hidden;
  1048.   position: relative;
  1049.   width: 748px;
  1050. }
  1051.  
  1052. .tile-page-content {
  1053.   overflow: hidden;
  1054. }
  1055.  
  1056. .tile-grid {
  1057.   display: block;
  1058.   margin: 0 auto;
  1059.   width: 732px;
  1060. }
  1061.  
  1062. .tile-grid-content {
  1063.   -webkit-transform: translate3d(0, 0, 0);
  1064.   -webkit-transition: -webkit-transform 200ms;
  1065. }
  1066.  
  1067. .tile-row {
  1068.   -webkit-transition: opacity 200ms;
  1069.   height: 100px;
  1070.   text-align: start;
  1071.   white-space: nowrap;
  1072. }
  1073.  
  1074. /* -----------------------------------------------------------------------------
  1075.   Tile Grid Animation
  1076. ----------------------------------------------------------------------------- */
  1077.  
  1078. .animate-grid-width {
  1079.   -webkit-transform: translate3d(0, 0, 0);
  1080.   -webkit-transition: width 200ms;
  1081. }
  1082.  
  1083. /* -----------------------------------------------------------------------------
  1084.   Tile Cell
  1085. ----------------------------------------------------------------------------- */
  1086.  
  1087. .tile-cell {
  1088.   -webkit-transform: translate3d(0, 0, 0);
  1089.   display: inline-block;
  1090.   position: relative;
  1091. }
  1092.  
  1093. .tile-cell:first-child {
  1094.   -webkit-margin-start: 0;
  1095. }
  1096.  
  1097. /* -----------------------------------------------------------------------------
  1098.    Tile Cell Animation
  1099. ----------------------------------------------------------------------------- */
  1100.  
  1101. .animate-grid-width .tile-cell {
  1102.   -webkit-transition: margin 200ms;
  1103.   -webkit-transition-property: margin, opacity, width;
  1104. }
  1105.  
  1106. /* Animates entire columns of Tiles at once.*/
  1107. .hide-col-0 .tile-cell:nth-child(1),
  1108. .hide-col-1 .tile-cell:nth-child(2),
  1109. .hide-col-2 .tile-cell:nth-child(3),
  1110. .hide-col-3 .tile-cell:nth-child(4),
  1111. .hide-col-4 .tile-cell:nth-child(5),
  1112. .hide-col-5 .tile-cell:nth-child(6),
  1113. .hide-col-6 .tile-cell:nth-child(7),
  1114. .hide-col-7 .tile-cell:nth-child(8),
  1115. .hide-col-8 .tile-cell:nth-child(9),
  1116. .hide-col-9 .tile-cell:nth-child(10) {
  1117.   -webkit-margin-end: -10px;
  1118.   opacity: 0;
  1119.   width: 10px !important;
  1120. }
  1121.  
  1122. /* -----------------------------------------------------------------------------
  1123.    Tile Position Animation
  1124. ----------------------------------------------------------------------------- */
  1125.  
  1126. .animate-tile-repositioning .tile {
  1127.   -webkit-transition-duration: 200ms;
  1128.   -webkit-transition-property: -webkit-transform, left, opacity, top;
  1129.   position: absolute;
  1130. }
  1131.  
  1132. .animate-tile-repositioning .tile:not(.target-tile) {
  1133.   -webkit-transition-duration: 400ms;
  1134. }
  1135.  
  1136. .animate-tile-repositioning.undo-removal .target-tile {
  1137.   -webkit-transition-delay: 200ms;
  1138. }
  1139.  
  1140. .animate-tile-repositioning .animate-hide-tile {
  1141.   opacity: 0;
  1142. }
  1143.  
  1144. .animate-tile-repositioning .animate-hide-tile.target-tile {
  1145.   -webkit-transform: scale(0.5);
  1146. }
  1147.  
  1148. /* -----------------------------------------------------------------------------
  1149.   Scroll Bars
  1150. ----------------------------------------------------------------------------- */
  1151.  
  1152. .scrollable {
  1153.   overflow-y: auto;
  1154. }
  1155.  
  1156. .scrollable .shadow-top {
  1157.   -webkit-margin-end: 0;
  1158.   -webkit-mask-box-image: -webkit-linear-gradient(left,
  1159.                                                   rgba(0, 0, 0, 0.1),
  1160.                                                   rgba(0, 0, 0, 0.8),
  1161.                                                   rgba(0, 0, 0, 0.1));
  1162.   background: -webkit-linear-gradient(top,
  1163.                                       rgba(0, 0, 0, 0.2),
  1164.                                       transparent);
  1165.   height: 6px;
  1166.   left: 0;
  1167.   opacity: 0;
  1168.   position: absolute;
  1169.   top: 0;
  1170.   width: 100%;
  1171.   z-index: 1000;
  1172. }
  1173.  
  1174. .scrollable .shadow-top::after {
  1175.   border-top: 1px solid rgba(0, 0, 0, 0.3);
  1176.   content: '';
  1177.   display: block;
  1178.   height: 0;
  1179.   left: 0;
  1180.   position: absolute;
  1181.   top: 0;
  1182.   width: 100%;
  1183. }
  1184.  
  1185. .scrollable .shadow-bottom {
  1186.   -webkit-margin-end: 0;
  1187.   -webkit-mask-box-image: -webkit-linear-gradient(left,
  1188.                                                   rgba(0, 0, 0, 0.1),
  1189.                                                   rgba(0, 0, 0, 0.8),
  1190.                                                   rgba(0, 0, 0, 0.1));
  1191.   background: -webkit-linear-gradient(bottom,
  1192.                                       rgba(0, 0, 0, 0.2),
  1193.                                       transparent);
  1194.   bottom: 0;
  1195.   height: 4px;
  1196.   left: 0;
  1197.   opacity: 1;
  1198.   position: absolute;
  1199.   width: 100%;
  1200.   z-index: 1000;
  1201. }
  1202.  
  1203. .scrollable .shadow-bottom::after {
  1204.   border-bottom: 1px solid rgba(0, 0, 0, 0.3);
  1205.   bottom: 0;
  1206.   content: '';
  1207.   display: block;
  1208.   height: 0;
  1209.   left: 0;
  1210.   position: absolute;
  1211.   width: 100%;
  1212. }
  1213.  
  1214. ::-webkit-scrollbar {
  1215.   height: 12px;
  1216.   width: 12px;
  1217. }
  1218.  
  1219. ::-webkit-scrollbar-button {
  1220.   height: 0;
  1221.   width: 0;
  1222. }
  1223.  
  1224. ::-webkit-scrollbar-button:start:decrement,
  1225. ::-webkit-scrollbar-button:end:increment {
  1226.   display: block;
  1227. }
  1228.  
  1229. ::-webkit-scrollbar-button:vertical:start:increment,
  1230. ::-webkit-scrollbar-button:vertical:end:decrement {
  1231.   display: none;
  1232. }
  1233.  
  1234. ::-webkit-scrollbar-track:vertical {
  1235.   -webkit-border-end: none;
  1236.   -webkit-border-start: 5px solid transparent;
  1237.   background-clip: padding-box;
  1238.   background-color: white;
  1239. }
  1240.  
  1241. ::-webkit-scrollbar-track:horizontal {
  1242.   background-clip: padding-box;
  1243.   background-color: white;
  1244.   border-bottom: none;
  1245.   border-top: 5px solid transparent;
  1246. }
  1247.  
  1248. ::-webkit-scrollbar-thumb {
  1249.   -webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, 0.10),
  1250.                       inset 0 -1px 0 rgba(0, 0, 0, 0.07);
  1251.   background-clip: padding-box;
  1252.   background-color: rgba(0, 0, 0, 0.2);
  1253.   min-height: 28px;
  1254.   padding-top: 100px;
  1255. }
  1256.  
  1257. ::-webkit-scrollbar-thumb:hover {
  1258.   -webkit-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.25);
  1259.   background-color: rgba(0, 0, 0, 0.4);
  1260. }
  1261.  
  1262. ::-webkit-scrollbar-thumb:active {
  1263.   -webkit-box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.35);
  1264.   background-color: rgba(0, 0, 0, 0.5);
  1265. }
  1266.  
  1267. ::-webkit-scrollbar-thumb:vertical {
  1268.   -webkit-border-end: none;
  1269.   -webkit-border-start: 5px solid transparent;
  1270.   border-bottom: none;
  1271.   border-top: none;
  1272. }
  1273.  
  1274. ::-webkit-scrollbar-thumb:horizontal {
  1275.   border: none;
  1276.   border-top: 5px solid transparent;
  1277. }
  1278.  
  1279. ::-webkit-scrollbar-track:hover {
  1280.   -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.10);
  1281.   background-color: rgba(0, 0, 0, 0.05);
  1282. }
  1283.  
  1284. ::-webkit-scrollbar-track:active {
  1285.   -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.14),
  1286.                       inset -1px -1px 0 rgba(0, 0, 0, 0.07);
  1287.   background-color: rgba(0, 0, 0, 0.05);
  1288. }
  1289. </style>
  1290. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1291.  * Use of this source code is governed by a BSD-style license that can be
  1292.  * found in the LICENSE file. */
  1293.  
  1294. .apps-page .tile-cell {
  1295.   -webkit-margin-start: 20px;
  1296.   height: 70px;
  1297.   margin-bottom: 12px;
  1298.   width: 70px;
  1299. }
  1300.  
  1301. .apps-page .tile-cell:first-child {
  1302.   -webkit-margin-start: 0;
  1303. }
  1304.  
  1305. .apps-page .app {
  1306.   display: block;
  1307.   height: 70px;
  1308.   margin-top: 8px;
  1309.   outline: none;
  1310.   text-align: center;
  1311.   width: 70px;
  1312. }
  1313.  
  1314. .apps-page .app .title {
  1315.   color: #777;
  1316.   font-size: 0.9166em;
  1317.   left: -7px;
  1318.   margin-top: 4px;
  1319.   position: relative;
  1320.   width: 84px;
  1321. }
  1322.  
  1323. .app-contents {
  1324.   -webkit-transition: -webkit-transform 100ms;
  1325. }
  1326.  
  1327. .app-contents:active:not(.suppress-active),
  1328. .app:not(.click-focus):focus .app-contents:not(.suppress-active),
  1329. .drag-representation:not(.placing) .app-contents {
  1330.   -webkit-transform: scale(1.1);
  1331. }
  1332.  
  1333. /* Don't animate the initial scaling.  */
  1334. .app-contents:active:not(.suppress-active),
  1335. /* Active gets applied right before .suppress-active, so to avoid flicker
  1336.  * we need to make the scale go back to normal without an animation. */
  1337. .app-contents.suppress-active {
  1338.   -webkit-transition-duration: 0;
  1339. }
  1340.  
  1341. .app-contents > span {
  1342.   display: block;
  1343.   overflow: hidden;
  1344.   text-overflow: ellipsis;
  1345.   white-space: nowrap;
  1346. }
  1347.  
  1348. .app-img-container {
  1349.   /* -webkit-mask-image set by JavaScript to the image source. */
  1350.   -webkit-mask-size: 100% 100%;
  1351.   margin-left: auto;
  1352.   margin-right: auto;
  1353. }
  1354.  
  1355. .app-img-container > * {
  1356.   height: 100%;
  1357.   width: 100%;
  1358. }
  1359.  
  1360. .app-icon-div {
  1361.   -webkit-box-align: center;
  1362.   -webkit-box-pack: center;
  1363.   background-color: white;
  1364.   border: 1px solid #d5d5d5;
  1365.   border-radius: 5px;
  1366.   display: -webkit-box;
  1367.   margin-left: auto;
  1368.   margin-right: auto;
  1369.   position: relative;
  1370.   vertical-align: middle;
  1371.   z-index: 0;
  1372. }
  1373.  
  1374. .animate-tile-repositioning .app.small-icon {
  1375.   margin-top: 0;
  1376. }
  1377.  
  1378. .small-icon .app-icon-div {
  1379.   height: 50px;
  1380.   margin: 18px 9px 12px;
  1381.   width: 50px;
  1382. }
  1383.  
  1384. .small-icon .app-img-container {
  1385.   bottom: 7px;
  1386.   height: 16px;
  1387.   left: 5px;
  1388.   position: absolute;
  1389.   width: 16px;
  1390. }
  1391.  
  1392. .small-icon .color-stripe {
  1393.   border-bottom-left-radius: 5px 5px;
  1394.   border-bottom-right-radius: 5px 5px;
  1395.   bottom: 0;
  1396.   height: 3px;
  1397.   opacity: 1.0;
  1398.   position: absolute;
  1399.   width: 100%;
  1400.   z-index: 100;
  1401. }
  1402.  
  1403. .app-context-menu > button:first-child {
  1404.   font-weight: bold;
  1405. }
  1406.  
  1407. .app-context-menu {
  1408.   z-index: 1000;
  1409. }
  1410.  
  1411. .app-context-menu > [checked]::before {
  1412.   height: 5px;
  1413. }
  1414.  
  1415. .launch-click-target {
  1416.   cursor: pointer;
  1417. }
  1418.  
  1419. /* Notifications */
  1420.  
  1421. .app-notification {
  1422.   -webkit-transition: color 150ms linear;
  1423.   color: #999;
  1424.   display: block;
  1425.   font-size: 0.9em;
  1426.   white-space: nowrap;
  1427. }
  1428.  
  1429. .app-notification:hover {
  1430.   text-decoration: underline;
  1431. }
  1432.  
  1433. .app-img-container > img:first-child {
  1434.   display: block;
  1435. }
  1436.  
  1437. .app .invisible {
  1438.   visibility: hidden;
  1439. }
  1440.  
  1441. /* Move the notification lower on apps pages to account for the 16px of
  1442.  * transparency each app icon should have. */
  1443. .apps-page #notification-container {
  1444.   bottom: 15px;
  1445. }
  1446. </style>
  1447.  
  1448. <link id="themecss" rel="stylesheet">
  1449.  
  1450. <script>// Copyright (c) 2011 The Chromium Authors. All rights reserved.
  1451. // Use of this source code is governed by a BSD-style license that can be
  1452. // found in the LICENSE file.
  1453.  
  1454. /** @fileoverview EventTracker is a simple class that manages the addition and
  1455.  *  removal of DOM event listeners. In particular, it keeps track of all
  1456.  *  listeners that have been added and makes it easy to remove some or all of
  1457.  *  them without requiring all the information again. This is particularly
  1458.  *  handy when the listener is a generated function such as a lambda or the
  1459.  *  result of calling Function.bind.
  1460.  */
  1461.  
  1462. // Use an anonymous function to enable strict mode just for this file (which
  1463. // will be concatenated with other files when embedded in Chrome)
  1464. var EventTracker = (function() {
  1465.   'use strict';
  1466.  
  1467.   /**
  1468.    *  Create an EventTracker to track a set of events.
  1469.    *  EventTracker instances are typically tied 1:1 with other objects or
  1470.    *  DOM elements whose listeners should be removed when the object is disposed
  1471.    *  or the corresponding elements are removed from the DOM.
  1472.    *  @constructor
  1473.    */
  1474.   function EventTracker() {
  1475.     /**
  1476.      *  @type {Array.<EventTracker.Entry>}
  1477.      *  @private
  1478.      */
  1479.     this.listeners_ = [];
  1480.   }
  1481.  
  1482.   /**
  1483.    * The type of the internal tracking entry.
  1484.    *  @typedef {{node: !Node,
  1485.    *            eventType: string,
  1486.    *            listener: Function,
  1487.    *            capture: boolean}}
  1488.    */
  1489.   EventTracker.Entry;
  1490.  
  1491.   EventTracker.prototype = {
  1492.     /**
  1493.      * Add an event listener - replacement for Node.addEventListener.
  1494.      * @param {!Node} node The DOM node to add a listener to.
  1495.      * @param {string} eventType The type of event to subscribe to.
  1496.      * @param {Function} listener The listener to add.
  1497.      * @param {boolean} capture Whether to invoke during the capture phase.
  1498.      */
  1499.     add: function(node, eventType, listener, capture) {
  1500.       var h = {
  1501.         node: node,
  1502.         eventType: eventType,
  1503.         listener: listener,
  1504.         capture: capture
  1505.       };
  1506.       this.listeners_.push(h);
  1507.       node.addEventListener(eventType, listener, capture);
  1508.     },
  1509.  
  1510.     /**
  1511.      * Remove any specified event listeners added with this EventTracker.
  1512.      * @param {!Node} node The DOM node to remove a listener from.
  1513.      * @param {?string} eventType The type of event to remove.
  1514.      */
  1515.     remove: function(node, eventType) {
  1516.       this.listeners_ = this.listeners_.filter(function(h) {
  1517.         if (h.node == node && (!eventType || (h.eventType == eventType))) {
  1518.           EventTracker.removeEventListener_(h);
  1519.           return false;
  1520.         }
  1521.         return true;
  1522.       });
  1523.     },
  1524.  
  1525.     /**
  1526.      * Remove all event listeners added with this EventTracker.
  1527.      */
  1528.     removeAll: function() {
  1529.       this.listeners_.forEach(EventTracker.removeEventListener_);
  1530.       this.listeners_ = [];
  1531.     }
  1532.   };
  1533.  
  1534.   /**
  1535.    * Remove a single event listener given it's tracker entry.  It's up to the
  1536.    * caller to ensure the entry is removed from listeners_.
  1537.    * @param {EventTracker.Entry} h The entry describing the listener to remove.
  1538.    * @private
  1539.    */
  1540.   EventTracker.removeEventListener_ = function(h) {
  1541.     h.node.removeEventListener(h.eventType, h.listener, h.capture);
  1542.   };
  1543.  
  1544.   return EventTracker;
  1545. })();
  1546.  
  1547. </script>
  1548. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1549. // Use of this source code is governed by a BSD-style license that can be
  1550. // found in the LICENSE file.
  1551.  
  1552. /**
  1553.  * @fileoverview This file defines a singleton which provides access to all data
  1554.  * that is available as soon as the page's resources are loaded (before DOM
  1555.  * content has finished loading). This data includes both localized strings and
  1556.  * any data that is important to have ready from a very early stage (e.g. things
  1557.  * that must be displayed right away).
  1558.  */
  1559.  
  1560. var loadTimeData;
  1561.  
  1562. (function() {
  1563.   'use strict';
  1564.  
  1565.   function LoadTimeData() {
  1566.   }
  1567.  
  1568.   LoadTimeData.prototype = {
  1569.     /**
  1570.      * Sets the backing object.
  1571.      * @param {Object} value The de-serialized page data.
  1572.      */
  1573.     set data(value) {
  1574.       expect(!this.data_, 'Re-setting data.');
  1575.       this.data_ = value;
  1576.     },
  1577.  
  1578.     /**
  1579.      * @return {boolean} True if |id| is a key in the dictionary.
  1580.      */
  1581.     valueExists: function(id) {
  1582.       return id in this.data_;
  1583.     },
  1584.  
  1585.     /**
  1586.      * Fetches a value, expecting that it exists.
  1587.      * @param {string} id The key that identifies the desired value.
  1588.      * @return {*} The corresponding value.
  1589.      */
  1590.     getValue: function(id) {
  1591.       expect(this.data_, 'No data. Did you remember to include strings.js?');
  1592.       var value = this.data_[id];
  1593.       expect(typeof value != 'undefined', 'Could not find value for ' + id);
  1594.       return value;
  1595.     },
  1596.  
  1597.     /**
  1598.      * As above, but also makes sure that the value is a string.
  1599.      * @param {string} id The key that identifies the desired string.
  1600.      * @return {string} The corresponding string value.
  1601.      */
  1602.     getString: function(id) {
  1603.       var value = this.getValue(id);
  1604.       expectIsType(id, value, 'string');
  1605.       return value;
  1606.     },
  1607.  
  1608.     /**
  1609.      * Returns a formatted localized string where $1 to $9 are replaced by the
  1610.      * second to the tenth argument.
  1611.      * @param {string} id The ID of the string we want.
  1612.      * @param {...string} The extra values to include in the formatted output.
  1613.      * @return {string} The formatted string.
  1614.      */
  1615.     getStringF: function(id) {
  1616.       var value = this.getString(id);
  1617.       if (!value)
  1618.         return;
  1619.  
  1620.       var varArgs = arguments;
  1621.       return value.replace(/\$[$1-9]/g, function(m) {
  1622.         return m == '$$' ? '$' : varArgs[m[1]];
  1623.       });
  1624.     },
  1625.  
  1626.     /**
  1627.      * As above, but also makes sure that the value is a boolean.
  1628.      * @param {string} id The key that identifies the desired boolean.
  1629.      * @return {boolean} The corresponding boolean value.
  1630.      */
  1631.     getBoolean: function(id) {
  1632.       var value = this.getValue(id);
  1633.       expectIsType(id, value, 'boolean');
  1634.       return value;
  1635.     },
  1636.  
  1637.     /**
  1638.      * As above, but also makes sure that the value is an integer.
  1639.      * @param {string} id The key that identifies the desired number.
  1640.      * @return {number} The corresponding number value.
  1641.      */
  1642.     getInteger: function(id) {
  1643.       var value = this.getValue(id);
  1644.       expectIsType(id, value, 'number');
  1645.       expect(value == Math.floor(value), 'Number isn\'t integer: ' + value);
  1646.       return value;
  1647.     },
  1648.  
  1649.     /**
  1650.      * Override values in loadTimeData with the values found in |replacements|.
  1651.      * @param {Object} replacements The dictionary object of keys to replace.
  1652.      */
  1653.     overrideValues: function(replacements) {
  1654.       expect(typeof replacements == 'object',
  1655.              'Replacements must be a dictionary object.');
  1656.       for (var key in replacements) {
  1657.         this.data_[key] = replacements[key];
  1658.       }
  1659.     }
  1660.   };
  1661.  
  1662.   /**
  1663.    * Checks condition, displays error message if expectation fails.
  1664.    * @param {*} condition The condition to check for truthiness.
  1665.    * @param {string} message The message to display if the check fails.
  1666.    */
  1667.   function expect(condition, message) {
  1668.     if (!condition)
  1669.       console.error(message);
  1670.   }
  1671.  
  1672.   /**
  1673.    * Checks that the given value has the given type.
  1674.    * @param {string} id The id of the value (only used for error message).
  1675.    * @param {*} value The value to check the type on.
  1676.    * @param {string} type The type we expect |value| to be.
  1677.    */
  1678.   function expectIsType(id, value, type) {
  1679.     expect(typeof value == type, '[' + value + '] (' + id +
  1680.                                  ') is not a ' + type);
  1681.   }
  1682.  
  1683.   expect(!loadTimeData, 'should only include this file once');
  1684.   loadTimeData = new LoadTimeData;
  1685. })();
  1686. </script>
  1687. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1688. // Use of this source code is governed by a BSD-style license that can be
  1689. // found in the LICENSE file.
  1690.  
  1691. /**
  1692.  * Parse a very small subset of HTML.  This ensures that insecure HTML /
  1693.  * javascript cannot be injected into the new tab page.
  1694.  * @param {string} s The string to parse.
  1695.  * @param {Array.<string>=} opt_extraTags Optional extra allowed tags.
  1696.  * @param {Object.<string, function(Node, string):boolean>=} opt_extraAttrs
  1697.  *     Optional extra allowed attributes (all tags are run through these).
  1698.  * @throws {Error} In case of non supported markup.
  1699.  * @return {DocumentFragment} A document fragment containing the DOM tree.
  1700.  */
  1701. var parseHtmlSubset = (function() {
  1702.   'use strict';
  1703.  
  1704.   var allowedAttributes = {
  1705.     'href': function(node, value) {
  1706.       // Only allow a[href] starting with chrome:// and https://
  1707.       return node.tagName == 'A' && (value.indexOf('chrome://') == 0 ||
  1708.           value.indexOf('https://') == 0);
  1709.     },
  1710.     'target': function(node, value) {
  1711.       // Allow a[target] but reset the value to "".
  1712.       if (node.tagName != 'A')
  1713.         return false;
  1714.       node.setAttribute('target', '');
  1715.       return true;
  1716.     }
  1717.   };
  1718.  
  1719.   /**
  1720.    * Whitelist of tag names allowed in parseHtmlSubset.
  1721.    * @type {!Array.<string>}
  1722.    * @const
  1723.    */
  1724.   var allowedTags = ['A', 'B', 'STRONG'];
  1725.  
  1726.   function merge() {
  1727.     var clone = {};
  1728.     for (var i = 0; i < arguments.length; ++i) {
  1729.       if (typeof arguments[i] == 'object') {
  1730.         for (var key in arguments[i]) {
  1731.           if (arguments[i].hasOwnProperty(key))
  1732.             clone[key] = arguments[i][key];
  1733.         }
  1734.       }
  1735.     }
  1736.     return clone;
  1737.   }
  1738.  
  1739.   function walk(n, f) {
  1740.     f(n);
  1741.     for (var i = 0; i < n.childNodes.length; i++) {
  1742.       walk(n.childNodes[i], f);
  1743.     }
  1744.   }
  1745.  
  1746.   function assertElement(tags, node) {
  1747.     if (tags.indexOf(node.tagName) == -1)
  1748.       throw Error(node.tagName + ' is not supported');
  1749.   }
  1750.  
  1751.   function assertAttribute(attrs, attrNode, node) {
  1752.     var n = attrNode.nodeName;
  1753.     var v = attrNode.nodeValue;
  1754.     if (!attrs.hasOwnProperty(n) || !attrs[n](node, v))
  1755.       throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported');
  1756.   }
  1757.  
  1758.   return function(s, opt_extraTags, opt_extraAttrs) {
  1759.     var extraTags =
  1760.         (opt_extraTags || []).map(function(str) { return str.toUpperCase(); });
  1761.     var tags = allowedTags.concat(extraTags);
  1762.     var attrs = merge(allowedAttributes, opt_extraAttrs || {});
  1763.  
  1764.     var r = document.createRange();
  1765.     r.selectNode(document.body);
  1766.     // This does not execute any scripts.
  1767.     var df = r.createContextualFragment(s);
  1768.     walk(df, function(node) {
  1769.       switch (node.nodeType) {
  1770.         case Node.ELEMENT_NODE:
  1771.           assertElement(tags, node);
  1772.           var nodeAttrs = node.attributes;
  1773.           for (var i = 0; i < nodeAttrs.length; ++i) {
  1774.             assertAttribute(attrs, nodeAttrs[i], node);
  1775.           }
  1776.           break;
  1777.  
  1778.         case Node.COMMENT_NODE:
  1779.         case Node.DOCUMENT_FRAGMENT_NODE:
  1780.         case Node.TEXT_NODE:
  1781.           break;
  1782.  
  1783.         default:
  1784.           throw Error('Node type ' + node.nodeType + ' is not supported');
  1785.       }
  1786.     });
  1787.     return df;
  1788.   };
  1789. })();
  1790. </script>
  1791. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1792. // Use of this source code is governed by a BSD-style license that can be
  1793. // found in the LICENSE file.
  1794.  
  1795. /**
  1796.  * The global object.
  1797.  * @type {!Object}
  1798.  * @const
  1799.  */
  1800. var global = this;
  1801.  
  1802. /**
  1803.  * Alias for document.getElementById.
  1804.  * @param {string} id The ID of the element to find.
  1805.  * @return {HTMLElement} The found element or null if not found.
  1806.  */
  1807. function $(id) {
  1808.   return document.getElementById(id);
  1809. }
  1810.  
  1811. /**
  1812.  * Calls chrome.send with a callback and restores the original afterwards.
  1813.  * @param {string} name The name of the message to send.
  1814.  * @param {!Array} params The parameters to send.
  1815.  * @param {string} callbackName The name of the function that the backend calls.
  1816.  * @param {!Function} callback The function to call.
  1817.  */
  1818. function chromeSend(name, params, callbackName, callback) {
  1819.   var old = global[callbackName];
  1820.   global[callbackName] = function() {
  1821.     // restore
  1822.     global[callbackName] = old;
  1823.  
  1824.     var args = Array.prototype.slice.call(arguments);
  1825.     return callback.apply(global, args);
  1826.   };
  1827.   chrome.send(name, params);
  1828. }
  1829.  
  1830. /**
  1831.  * Generates a CSS url string.
  1832.  * @param {string} s The URL to generate the CSS url for.
  1833.  * @return {string} The CSS url string.
  1834.  */
  1835. function url(s) {
  1836.   // http://www.w3.org/TR/css3-values/#uris
  1837.   // Parentheses, commas, whitespace characters, single quotes (') and double
  1838.   // quotes (") appearing in a URI must be escaped with a backslash
  1839.   var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
  1840.   // WebKit has a bug when it comes to URLs that end with \
  1841.   // https://bugs.webkit.org/show_bug.cgi?id=28885
  1842.   if (/\\\\$/.test(s2)) {
  1843.     // Add a space to work around the WebKit bug.
  1844.     s2 += ' ';
  1845.   }
  1846.   return 'url("' + s2 + '")';
  1847. }
  1848.  
  1849. /**
  1850.  * Parses query parameters from Location.
  1851.  * @param {string} location The URL to generate the CSS url for.
  1852.  * @return {object} Dictionary containing name value pairs for URL
  1853.  */
  1854. function parseQueryParams(location) {
  1855.   var params = {};
  1856.   var query = unescape(location.search.substring(1));
  1857.   var vars = query.split('&');
  1858.   for (var i = 0; i < vars.length; i++) {
  1859.     var pair = vars[i].split('=');
  1860.     params[pair[0]] = pair[1];
  1861.   }
  1862.   return params;
  1863. }
  1864.  
  1865. function findAncestorByClass(el, className) {
  1866.   return findAncestor(el, function(el) {
  1867.     if (el.classList)
  1868.       return el.classList.contains(className);
  1869.     return null;
  1870.   });
  1871. }
  1872.  
  1873. /**
  1874.  * Return the first ancestor for which the {@code predicate} returns true.
  1875.  * @param {Node} node The node to check.
  1876.  * @param {function(Node) : boolean} predicate The function that tests the
  1877.  *     nodes.
  1878.  * @return {Node} The found ancestor or null if not found.
  1879.  */
  1880. function findAncestor(node, predicate) {
  1881.   var last = false;
  1882.   while (node != null && !(last = predicate(node))) {
  1883.     node = node.parentNode;
  1884.   }
  1885.   return last ? node : null;
  1886. }
  1887.  
  1888. function swapDomNodes(a, b) {
  1889.   var afterA = a.nextSibling;
  1890.   if (afterA == b) {
  1891.     swapDomNodes(b, a);
  1892.     return;
  1893.   }
  1894.   var aParent = a.parentNode;
  1895.   b.parentNode.replaceChild(a, b);
  1896.   aParent.insertBefore(b, afterA);
  1897. }
  1898.  
  1899. /**
  1900.  * Disables text selection and dragging, with optional whitelist callbacks.
  1901.  * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
  1902.  *    is defined and returns true, the onselectionstart event will be
  1903.  *    surpressed.
  1904.  * @param {function(Event):boolean=} opt_allowDragStart Unless this function
  1905.  *    is defined and returns true, the ondragstart event will be surpressed.
  1906.  */
  1907. function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
  1908.   // Disable text selection.
  1909.   document.onselectstart = function(e) {
  1910.     if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
  1911.       e.preventDefault();
  1912.   };
  1913.  
  1914.   // Disable dragging.
  1915.   document.ondragstart = function(e) {
  1916.     if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
  1917.       e.preventDefault();
  1918.   };
  1919. }
  1920.  
  1921. /**
  1922.  * Call this to stop clicks on <a href="#"> links from scrolling to the top of
  1923.  * the page (and possibly showing a # in the link).
  1924.  */
  1925. function preventDefaultOnPoundLinkClicks() {
  1926.   document.addEventListener('click', function(e) {
  1927.     var anchor = findAncestor(e.target, function(el) {
  1928.       return el.tagName == 'A';
  1929.     });
  1930.     // Use getAttribute() to prevent URL normalization.
  1931.     if (anchor && anchor.getAttribute('href') == '#')
  1932.       e.preventDefault();
  1933.   });
  1934. }
  1935.  
  1936. /**
  1937.  * Check the directionality of the page.
  1938.  * @return {boolean} True if Chrome is running an RTL UI.
  1939.  */
  1940. function isRTL() {
  1941.   return document.documentElement.dir == 'rtl';
  1942. }
  1943.  
  1944. /**
  1945.  * Simple common assertion API
  1946.  * @param {*} condition The condition to test.  Note that this may be used to
  1947.  *     test whether a value is defined or not, and we don't want to force a
  1948.  *     cast to Boolean.
  1949.  * @param {string=} opt_message A message to use in any error.
  1950.  */
  1951. function assert(condition, opt_message) {
  1952.   'use strict';
  1953.   if (!condition) {
  1954.     var msg = 'Assertion failed';
  1955.     if (opt_message)
  1956.       msg = msg + ': ' + opt_message;
  1957.     throw new Error(msg);
  1958.   }
  1959. }
  1960.  
  1961. /**
  1962.  * Get an element that's known to exist by its ID. We use this instead of just
  1963.  * calling getElementById and not checking the result because this lets us
  1964.  * satisfy the JSCompiler type system.
  1965.  * @param {string} id The identifier name.
  1966.  * @return {!Element} the Element.
  1967.  */
  1968. function getRequiredElement(id) {
  1969.   var element = $(id);
  1970.   assert(element, 'Missing required element: ' + id);
  1971.   return element;
  1972. }
  1973.  
  1974. // Handle click on a link. If the link points to a chrome: or file: url, then
  1975. // call into the browser to do the navigation.
  1976. document.addEventListener('click', function(e) {
  1977.   // Allow preventDefault to work.
  1978.   if (!e.returnValue)
  1979.     return;
  1980.  
  1981.   var el = e.target;
  1982.   if (el.nodeType == Node.ELEMENT_NODE &&
  1983.       el.webkitMatchesSelector('A, A *')) {
  1984.     while (el.tagName != 'A') {
  1985.       el = el.parentElement;
  1986.     }
  1987.  
  1988.     if ((el.protocol == 'file:' || el.protocol == 'about:') &&
  1989.         (e.button == 0 || e.button == 1)) {
  1990.       chrome.send('navigateToUrl', [
  1991.         el.href,
  1992.         el.target,
  1993.         e.button,
  1994.         e.altKey,
  1995.         e.ctrlKey,
  1996.         e.metaKey,
  1997.         e.shiftKey
  1998.       ]);
  1999.       e.preventDefault();
  2000.     }
  2001.   }
  2002. });
  2003.  
  2004. /**
  2005.  * Creates a new URL which is the old URL with a GET param of key=value.
  2006.  * @param {string} url The base URL. There is not sanity checking on the URL so
  2007.  *     it must be passed in a proper format.
  2008.  * @param {string} key The key of the param.
  2009.  * @param {string} value The value of the param.
  2010.  * @return {string} The new URL.
  2011.  */
  2012. function appendParam(url, key, value) {
  2013.   var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
  2014.  
  2015.   if (url.indexOf('?') == -1)
  2016.     return url + '?' + param;
  2017.   return url + '&' + param;
  2018. }
  2019.  
  2020. /**
  2021.  * Creates a new URL for a favicon request.
  2022.  * @param {string} url The url for the favicon.
  2023.  * @param {number=} opt_size Optional preferred size of the favicon.
  2024.  * @return {string} Updated URL for the favicon.
  2025.  */
  2026. function getFaviconURL(url, opt_size) {
  2027.   var size = opt_size || 16;
  2028.   return 'chrome://favicon/size/' + size + '@' +
  2029.       window.devicePixelRatio + 'x/' + url;
  2030. }
  2031. </script>
  2032. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2033. // Use of this source code is governed by a BSD-style license that can be
  2034. // found in the LICENSE file.
  2035.  
  2036. /**
  2037.  * The global object.
  2038.  * @type {!Object}
  2039.  * @const
  2040.  */
  2041. var global = this;
  2042.  
  2043. /** Platform, package, object property, and Event support. **/
  2044. this.cr = (function() {
  2045.   'use strict';
  2046.  
  2047.   /**
  2048.    * Builds an object structure for the provided namespace path,
  2049.    * ensuring that names that already exist are not overwritten. For
  2050.    * example:
  2051.    * "a.b.c" -> a = {};a.b={};a.b.c={};
  2052.    * @param {string} name Name of the object that this file defines.
  2053.    * @param {*=} opt_object The object to expose at the end of the path.
  2054.    * @param {Object=} opt_objectToExportTo The object to add the path to;
  2055.    *     default is {@code global}.
  2056.    * @private
  2057.    */
  2058.   function exportPath(name, opt_object, opt_objectToExportTo) {
  2059.     var parts = name.split('.');
  2060.     var cur = opt_objectToExportTo || global;
  2061.  
  2062.     for (var part; parts.length && (part = parts.shift());) {
  2063.       if (!parts.length && opt_object !== undefined) {
  2064.         // last part and we have an object; use it
  2065.         cur[part] = opt_object;
  2066.       } else if (part in cur) {
  2067.         cur = cur[part];
  2068.       } else {
  2069.         cur = cur[part] = {};
  2070.       }
  2071.     }
  2072.     return cur;
  2073.   };
  2074.  
  2075.   /**
  2076.    * Fires a property change event on the target.
  2077.    * @param {EventTarget} target The target to dispatch the event on.
  2078.    * @param {string} propertyName The name of the property that changed.
  2079.    * @param {*} newValue The new value for the property.
  2080.    * @param {*} oldValue The old value for the property.
  2081.    */
  2082.   function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
  2083.     var e = new cr.Event(propertyName + 'Change');
  2084.     e.propertyName = propertyName;
  2085.     e.newValue = newValue;
  2086.     e.oldValue = oldValue;
  2087.     target.dispatchEvent(e);
  2088.   }
  2089.  
  2090.   /**
  2091.    * Converts a camelCase javascript property name to a hyphenated-lower-case
  2092.    * attribute name.
  2093.    * @param {string} jsName The javascript camelCase property name.
  2094.    * @return {string} The equivalent hyphenated-lower-case attribute name.
  2095.    */
  2096.   function getAttributeName(jsName) {
  2097.     return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
  2098.   }
  2099.  
  2100.   /**
  2101.    * The kind of property to define in {@code defineProperty}.
  2102.    * @enum {number}
  2103.    * @const
  2104.    */
  2105.   var PropertyKind = {
  2106.     /**
  2107.      * Plain old JS property where the backing data is stored as a "private"
  2108.      * field on the object.
  2109.      */
  2110.     JS: 'js',
  2111.  
  2112.     /**
  2113.      * The property backing data is stored as an attribute on an element.
  2114.      */
  2115.     ATTR: 'attr',
  2116.  
  2117.     /**
  2118.      * The property backing data is stored as an attribute on an element. If the
  2119.      * element has the attribute then the value is true.
  2120.      */
  2121.     BOOL_ATTR: 'boolAttr'
  2122.   };
  2123.  
  2124.   /**
  2125.    * Helper function for defineProperty that returns the getter to use for the
  2126.    * property.
  2127.    * @param {string} name The name of the property.
  2128.    * @param {cr.PropertyKind} kind The kind of the property.
  2129.    * @return {function():*} The getter for the property.
  2130.    */
  2131.   function getGetter(name, kind) {
  2132.     switch (kind) {
  2133.       case PropertyKind.JS:
  2134.         var privateName = name + '_';
  2135.         return function() {
  2136.           return this[privateName];
  2137.         };
  2138.       case PropertyKind.ATTR:
  2139.         var attributeName = getAttributeName(name);
  2140.         return function() {
  2141.           return this.getAttribute(attributeName);
  2142.         };
  2143.       case PropertyKind.BOOL_ATTR:
  2144.         var attributeName = getAttributeName(name);
  2145.         return function() {
  2146.           return this.hasAttribute(attributeName);
  2147.         };
  2148.     }
  2149.   }
  2150.  
  2151.   /**
  2152.    * Helper function for defineProperty that returns the setter of the right
  2153.    * kind.
  2154.    * @param {string} name The name of the property we are defining the setter
  2155.    *     for.
  2156.    * @param {cr.PropertyKind} kind The kind of property we are getting the
  2157.    *     setter for.
  2158.    * @param {function(*):void} opt_setHook A function to run after the property
  2159.    *     is set, but before the propertyChange event is fired.
  2160.    * @return {function(*):void} The function to use as a setter.
  2161.    */
  2162.   function getSetter(name, kind, opt_setHook) {
  2163.     switch (kind) {
  2164.       case PropertyKind.JS:
  2165.         var privateName = name + '_';
  2166.         return function(value) {
  2167.           var oldValue = this[name];
  2168.           if (value !== oldValue) {
  2169.             this[privateName] = value;
  2170.             if (opt_setHook)
  2171.               opt_setHook.call(this, value, oldValue);
  2172.             dispatchPropertyChange(this, name, value, oldValue);
  2173.           }
  2174.         };
  2175.  
  2176.       case PropertyKind.ATTR:
  2177.         var attributeName = getAttributeName(name);
  2178.         return function(value) {
  2179.           var oldValue = this[name];
  2180.           if (value !== oldValue) {
  2181.             if (value == undefined)
  2182.               this.removeAttribute(attributeName);
  2183.             else
  2184.               this.setAttribute(attributeName, value);
  2185.             if (opt_setHook)
  2186.               opt_setHook.call(this, value, oldValue);
  2187.             dispatchPropertyChange(this, name, value, oldValue);
  2188.           }
  2189.         };
  2190.  
  2191.       case PropertyKind.BOOL_ATTR:
  2192.         var attributeName = getAttributeName(name);
  2193.         return function(value) {
  2194.           var oldValue = this[name];
  2195.           if (value !== oldValue) {
  2196.             if (value)
  2197.               this.setAttribute(attributeName, name);
  2198.             else
  2199.               this.removeAttribute(attributeName);
  2200.             if (opt_setHook)
  2201.               opt_setHook.call(this, value, oldValue);
  2202.             dispatchPropertyChange(this, name, value, oldValue);
  2203.           }
  2204.         };
  2205.     }
  2206.   }
  2207.  
  2208.   /**
  2209.    * Defines a property on an object. When the setter changes the value a
  2210.    * property change event with the type {@code name + 'Change'} is fired.
  2211.    * @param {!Object} obj The object to define the property for.
  2212.    * @param {string} name The name of the property.
  2213.    * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use.
  2214.    * @param {function(*):void} opt_setHook A function to run after the
  2215.    *     property is set, but before the propertyChange event is fired.
  2216.    */
  2217.   function defineProperty(obj, name, opt_kind, opt_setHook) {
  2218.     if (typeof obj == 'function')
  2219.       obj = obj.prototype;
  2220.  
  2221.     var kind = opt_kind || PropertyKind.JS;
  2222.  
  2223.     if (!obj.__lookupGetter__(name))
  2224.       obj.__defineGetter__(name, getGetter(name, kind));
  2225.  
  2226.     if (!obj.__lookupSetter__(name))
  2227.       obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
  2228.   }
  2229.  
  2230.   /**
  2231.    * Counter for use with createUid
  2232.    */
  2233.   var uidCounter = 1;
  2234.  
  2235.   /**
  2236.    * @return {number} A new unique ID.
  2237.    */
  2238.   function createUid() {
  2239.     return uidCounter++;
  2240.   }
  2241.  
  2242.   /**
  2243.    * Returns a unique ID for the item. This mutates the item so it needs to be
  2244.    * an object
  2245.    * @param {!Object} item The item to get the unique ID for.
  2246.    * @return {number} The unique ID for the item.
  2247.    */
  2248.   function getUid(item) {
  2249.     if (item.hasOwnProperty('uid'))
  2250.       return item.uid;
  2251.     return item.uid = createUid();
  2252.   }
  2253.  
  2254.   /**
  2255.    * Dispatches a simple event on an event target.
  2256.    * @param {!EventTarget} target The event target to dispatch the event on.
  2257.    * @param {string} type The type of the event.
  2258.    * @param {boolean=} opt_bubbles Whether the event bubbles or not.
  2259.    * @param {boolean=} opt_cancelable Whether the default action of the event
  2260.    *     can be prevented.
  2261.    * @return {boolean} If any of the listeners called {@code preventDefault}
  2262.    *     during the dispatch this will return false.
  2263.    */
  2264.   function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
  2265.     var e = new cr.Event(type, opt_bubbles, opt_cancelable);
  2266.     return target.dispatchEvent(e);
  2267.   }
  2268.  
  2269.   /**
  2270.    * Calls |fun| and adds all the fields of the returned object to the object
  2271.    * named by |name|. For example, cr.define('cr.ui', function() {
  2272.    *   function List() {
  2273.    *     ...
  2274.    *   }
  2275.    *   function ListItem() {
  2276.    *     ...
  2277.    *   }
  2278.    *   return {
  2279.    *     List: List,
  2280.    *     ListItem: ListItem,
  2281.    *   };
  2282.    * });
  2283.    * defines the functions cr.ui.List and cr.ui.ListItem.
  2284.    * @param {string} name The name of the object that we are adding fields to.
  2285.    * @param {!Function} fun The function that will return an object containing
  2286.    *     the names and values of the new fields.
  2287.    */
  2288.   function define(name, fun) {
  2289.     var obj = exportPath(name);
  2290.     var exports = fun();
  2291.     for (var propertyName in exports) {
  2292.       // Maybe we should check the prototype chain here? The current usage
  2293.       // pattern is always using an object literal so we only care about own
  2294.       // properties.
  2295.       var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
  2296.                                                                propertyName);
  2297.       if (propertyDescriptor)
  2298.         Object.defineProperty(obj, propertyName, propertyDescriptor);
  2299.     }
  2300.   }
  2301.  
  2302.   /**
  2303.    * Adds a {@code getInstance} static method that always return the same
  2304.    * instance object.
  2305.    * @param {!Function} ctor The constructor for the class to add the static
  2306.    *     method to.
  2307.    */
  2308.   function addSingletonGetter(ctor) {
  2309.     ctor.getInstance = function() {
  2310.       return ctor.instance_ || (ctor.instance_ = new ctor());
  2311.     };
  2312.   }
  2313.  
  2314.   /**
  2315.    * Creates a new event to be used with cr.EventTarget or DOM EventTarget
  2316.    * objects.
  2317.    * @param {string} type The name of the event.
  2318.    * @param {boolean=} opt_bubbles Whether the event bubbles.
  2319.    *     Default is false.
  2320.    * @param {boolean=} opt_preventable Whether the default action of the event
  2321.    *     can be prevented.
  2322.    * @constructor
  2323.    * @extends {Event}
  2324.    */
  2325.   function Event(type, opt_bubbles, opt_preventable) {
  2326.     var e = cr.doc.createEvent('Event');
  2327.     e.initEvent(type, !!opt_bubbles, !!opt_preventable);
  2328.     e.__proto__ = global.Event.prototype;
  2329.     return e;
  2330.   };
  2331.  
  2332.   /**
  2333.    * Initialization which must be deferred until run-time.
  2334.    */
  2335.   function initialize() {
  2336.     // If 'document' isn't defined, then we must be being pre-compiled,
  2337.     // so set a trap so that we're initialized on first access at run-time.
  2338.     if (!global.document) {
  2339.       var originalCr = cr;
  2340.  
  2341.       Object.defineProperty(global, 'cr', {
  2342.         get: function() {
  2343.           Object.defineProperty(global, 'cr', {value: originalCr});
  2344.           originalCr.initialize();
  2345.           return originalCr;
  2346.         },
  2347.         configurable: true
  2348.       });
  2349.  
  2350.       return;
  2351.     }
  2352.  
  2353.     Event.prototype = {__proto__: global.Event.prototype};
  2354.  
  2355.     cr.doc = document;
  2356.  
  2357.     /**
  2358.      * Whether we are using a Mac or not.
  2359.      */
  2360.     cr.isMac = /Mac/.test(navigator.platform);
  2361.  
  2362.     /**
  2363.      * Whether this is on the Windows platform or not.
  2364.      */
  2365.     cr.isWindows = /Win/.test(navigator.platform);
  2366.  
  2367.     /**
  2368.      * Whether this is on chromeOS or not.
  2369.      */
  2370.     cr.isChromeOS = /CrOS/.test(navigator.userAgent);
  2371.  
  2372.     /**
  2373.      * Whether this is on vanilla Linux (not chromeOS).
  2374.      */
  2375.     cr.isLinux = /Linux/.test(navigator.userAgent);
  2376.  
  2377.     /**
  2378.      * Whether this uses GTK or not.
  2379.      */
  2380.     cr.isGTK = typeof chrome.getVariableValue == 'function' &&
  2381.         /GTK/.test(chrome.getVariableValue('toolkit'));
  2382.  
  2383.     /**
  2384.      * Whether this uses the views toolkit or not.
  2385.      */
  2386.     cr.isViews = typeof chrome.getVariableValue == 'function' &&
  2387.         /views/.test(chrome.getVariableValue('toolkit'));
  2388.   }
  2389.  
  2390.   return {
  2391.     addSingletonGetter: addSingletonGetter,
  2392.     createUid: createUid,
  2393.     define: define,
  2394.     defineProperty: defineProperty,
  2395.     dispatchPropertyChange: dispatchPropertyChange,
  2396.     dispatchSimpleEvent: dispatchSimpleEvent,
  2397.     Event: Event,
  2398.     getUid: getUid,
  2399.     initialize: initialize,
  2400.     PropertyKind: PropertyKind
  2401.   };
  2402. })();
  2403.  
  2404.  
  2405. /**
  2406.  * TODO(kgr): Move this to another file which is to be loaded last.
  2407.  * This will be done as part of future work to make this code pre-compilable.
  2408.  */
  2409. cr.initialize();
  2410. </script>
  2411. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2412. // Use of this source code is governed by a BSD-style license that can be
  2413. // found in the LICENSE file.
  2414.  
  2415. cr.define('cr.ui', function() {
  2416.  
  2417.   /**
  2418.    * Decorates elements as an instance of a class.
  2419.    * @param {string|!Element} source The way to find the element(s) to decorate.
  2420.    *     If this is a string then {@code querySeletorAll} is used to find the
  2421.    *     elements to decorate.
  2422.    * @param {!Function} constr The constructor to decorate with. The constr
  2423.    *     needs to have a {@code decorate} function.
  2424.    */
  2425.   function decorate(source, constr) {
  2426.     var elements;
  2427.     if (typeof source == 'string')
  2428.       elements = cr.doc.querySelectorAll(source);
  2429.     else
  2430.       elements = [source];
  2431.  
  2432.     for (var i = 0, el; el = elements[i]; i++) {
  2433.       if (!(el instanceof constr))
  2434.         constr.decorate(el);
  2435.     }
  2436.   }
  2437.  
  2438.   /**
  2439.    * Helper function for creating new element for define.
  2440.    */
  2441.   function createElementHelper(tagName, opt_bag) {
  2442.     // Allow passing in ownerDocument to create in a different document.
  2443.     var doc;
  2444.     if (opt_bag && opt_bag.ownerDocument)
  2445.       doc = opt_bag.ownerDocument;
  2446.     else
  2447.       doc = cr.doc;
  2448.     return doc.createElement(tagName);
  2449.   }
  2450.  
  2451.   /**
  2452.    * Creates the constructor for a UI element class.
  2453.    *
  2454.    * Usage:
  2455.    * <pre>
  2456.    * var List = cr.ui.define('list');
  2457.    * List.prototype = {
  2458.    *   __proto__: HTMLUListElement.prototype,
  2459.    *   decorate: function() {
  2460.    *     ...
  2461.    *   },
  2462.    *   ...
  2463.    * };
  2464.    * </pre>
  2465.    *
  2466.    * @param {string|Function} tagNameOrFunction The tagName or
  2467.    *     function to use for newly created elements. If this is a function it
  2468.    *     needs to return a new element when called.
  2469.    * @return {function(Object=):Element} The constructor function which takes
  2470.    *     an optional property bag. The function also has a static
  2471.    *     {@code decorate} method added to it.
  2472.    */
  2473.   function define(tagNameOrFunction) {
  2474.     var createFunction, tagName;
  2475.     if (typeof tagNameOrFunction == 'function') {
  2476.       createFunction = tagNameOrFunction;
  2477.       tagName = '';
  2478.     } else {
  2479.       createFunction = createElementHelper;
  2480.       tagName = tagNameOrFunction;
  2481.     }
  2482.  
  2483.     /**
  2484.      * Creates a new UI element constructor.
  2485.      * @param {Object=} opt_propertyBag Optional bag of properties to set on the
  2486.      *     object after created. The property {@code ownerDocument} is special
  2487.      *     cased and it allows you to create the element in a different
  2488.      *     document than the default.
  2489.      * @constructor
  2490.      */
  2491.     function f(opt_propertyBag) {
  2492.       var el = createFunction(tagName, opt_propertyBag);
  2493.       f.decorate(el);
  2494.       for (var propertyName in opt_propertyBag) {
  2495.         el[propertyName] = opt_propertyBag[propertyName];
  2496.       }
  2497.       return el;
  2498.     }
  2499.  
  2500.     /**
  2501.      * Decorates an element as a UI element class.
  2502.      * @param {!Element} el The element to decorate.
  2503.      */
  2504.     f.decorate = function(el) {
  2505.       el.__proto__ = f.prototype;
  2506.       el.decorate();
  2507.     };
  2508.  
  2509.     return f;
  2510.   }
  2511.  
  2512.   /**
  2513.    * Input elements do not grow and shrink with their content. This is a simple
  2514.    * (and not very efficient) way of handling shrinking to content with support
  2515.    * for min width and limited by the width of the parent element.
  2516.    * @param {HTMLElement} el The element to limit the width for.
  2517.    * @param {number} parentEl The parent element that should limit the size.
  2518.    * @param {number} min The minimum width.
  2519.    * @param {number} opt_scale Optional scale factor to apply to the width.
  2520.    */
  2521.   function limitInputWidth(el, parentEl, min, opt_scale) {
  2522.     // Needs a size larger than borders
  2523.     el.style.width = '10px';
  2524.     var doc = el.ownerDocument;
  2525.     var win = doc.defaultView;
  2526.     var computedStyle = win.getComputedStyle(el);
  2527.     var parentComputedStyle = win.getComputedStyle(parentEl);
  2528.     var rtl = computedStyle.direction == 'rtl';
  2529.  
  2530.     // To get the max width we get the width of the treeItem minus the position
  2531.     // of the input.
  2532.     var inputRect = el.getBoundingClientRect();  // box-sizing
  2533.     var parentRect = parentEl.getBoundingClientRect();
  2534.     var startPos = rtl ? parentRect.right - inputRect.right :
  2535.         inputRect.left - parentRect.left;
  2536.  
  2537.     // Add up border and padding of the input.
  2538.     var inner = parseInt(computedStyle.borderLeftWidth, 10) +
  2539.         parseInt(computedStyle.paddingLeft, 10) +
  2540.         parseInt(computedStyle.paddingRight, 10) +
  2541.         parseInt(computedStyle.borderRightWidth, 10);
  2542.  
  2543.     // We also need to subtract the padding of parent to prevent it to overflow.
  2544.     var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
  2545.         parseInt(parentComputedStyle.paddingRight, 10);
  2546.  
  2547.     var max = parentEl.clientWidth - startPos - inner - parentPadding;
  2548.     if (opt_scale)
  2549.       max *= opt_scale;
  2550.  
  2551.     function limit() {
  2552.       if (el.scrollWidth > max) {
  2553.         el.style.width = max + 'px';
  2554.       } else {
  2555.         el.style.width = 0;
  2556.         var sw = el.scrollWidth;
  2557.         if (sw < min) {
  2558.           el.style.width = min + 'px';
  2559.         } else {
  2560.           el.style.width = sw + 'px';
  2561.         }
  2562.       }
  2563.     }
  2564.  
  2565.     el.addEventListener('input', limit);
  2566.     limit();
  2567.   }
  2568.  
  2569.   /**
  2570.    * Takes a number and spits out a value CSS will be happy with. To avoid
  2571.    * subpixel layout issues, the value is rounded to the nearest integral value.
  2572.    * @param {number} pixels The number of pixels.
  2573.    * @return {string} e.g. '16px'.
  2574.    */
  2575.   function toCssPx(pixels) {
  2576.     if (!window.isFinite(pixels))
  2577.       console.error('Pixel value is not a number: ' + pixels);
  2578.     return Math.round(pixels) + 'px';
  2579.   }
  2580.  
  2581.   /**
  2582.    * Users complain they occasionaly use doubleclicks instead of clicks
  2583.    * (http://crbug.com/140364). To fix it we freeze click handling for
  2584.    * the doubleclick time interval.
  2585.    * @param {MouseEvent} e Initial click event.
  2586.    */
  2587.   function swallowDoubleClick(e) {
  2588.     var doc = e.target.ownerDocument;
  2589.     var counter = e.type == 'click' ? e.detail : 0;
  2590.     function swallow(e) {
  2591.       e.stopPropagation();
  2592.       e.preventDefault();
  2593.     }
  2594.     function onclick(e) {
  2595.       if (e.detail > counter) {
  2596.         counter = e.detail;
  2597.         // Swallow the click since it's a click inside the doubleclick timeout.
  2598.         swallow(e);
  2599.       } else {
  2600.         // Stop tracking clicks and let regular handling.
  2601.         doc.removeEventListener('dblclick', swallow, true);
  2602.         doc.removeEventListener('click', onclick, true);
  2603.       }
  2604.     }
  2605.     doc.addEventListener('click', onclick, true);
  2606.     doc.addEventListener('dblclick', swallow, true);
  2607.   }
  2608.  
  2609.   return {
  2610.     decorate: decorate,
  2611.     define: define,
  2612.     limitInputWidth: limitInputWidth,
  2613.     toCssPx: toCssPx,
  2614.     swallowDoubleClick: swallowDoubleClick
  2615.   };
  2616. });
  2617. </script>
  2618. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2619. // Use of this source code is governed by a BSD-style license that can be
  2620. // found in the LICENSE file.
  2621.  
  2622. // require: event_tracker.js
  2623.  
  2624. cr.define('cr.ui', function() {
  2625.  
  2626.   /**
  2627.    * The arrow location specifies how the arrow and bubble are positioned in
  2628.    * relation to the anchor node.
  2629.    * @enum
  2630.    */
  2631.   var ArrowLocation = {
  2632.     // The arrow is positioned at the top and the start of the bubble. In left
  2633.     // to right mode this is the top left. The entire bubble is positioned below
  2634.     // the anchor node.
  2635.     TOP_START: 'top-start',
  2636.     // The arrow is positioned at the top and the end of the bubble. In left to
  2637.     // right mode this is the top right. The entire bubble is positioned below
  2638.     // the anchor node.
  2639.     TOP_END: 'top-end',
  2640.     // The arrow is positioned at the bottom and the start of the bubble. In
  2641.     // left to right mode this is the bottom left. The entire bubble is
  2642.     // positioned above the anchor node.
  2643.     BOTTOM_START: 'bottom-start',
  2644.     // The arrow is positioned at the bottom and the end of the bubble. In
  2645.     // left to right mode this is the bottom right. The entire bubble is
  2646.     // positioned above the anchor node.
  2647.     BOTTOM_END: 'bottom-end'
  2648.   };
  2649.  
  2650.   /**
  2651.    * The bubble alignment specifies the position of the bubble in relation to
  2652.    * the anchor node.
  2653.    * @enum
  2654.    */
  2655.   var BubbleAlignment = {
  2656.     // The bubble is positioned just above or below the anchor node (as
  2657.     // specified by the arrow location) so that the arrow points at the midpoint
  2658.     // of the anchor.
  2659.     ARROW_TO_MID_ANCHOR: 'arrow-to-mid-anchor',
  2660.     // The bubble is positioned just above or below the anchor node (as
  2661.     // specified by the arrow location) so that its reference edge lines up with
  2662.     // the edge of the anchor.
  2663.     BUBBLE_EDGE_TO_ANCHOR_EDGE: 'bubble-edge-anchor-edge',
  2664.     // The bubble is positioned so that it is entirely within view and does not
  2665.     // obstruct the anchor element, if possible. The specified arrow location is
  2666.     // taken into account as the preferred alignment but may be overruled if
  2667.     // there is insufficient space (see BubbleBase.reposition for the exact
  2668.     // placement algorithm).
  2669.     ENTIRELY_VISIBLE: 'entirely-visible'
  2670.   };
  2671.  
  2672.   /**
  2673.    * Abstract base class that provides common functionality for implementing
  2674.    * free-floating informational bubbles with a triangular arrow pointing at an
  2675.    * anchor node.
  2676.    */
  2677.   var BubbleBase = cr.ui.define('div');
  2678.  
  2679.   /**
  2680.    * The horizontal distance between the tip of the arrow and the reference edge
  2681.    * of the bubble (as specified by the arrow location). In pixels.
  2682.    * @type {number}
  2683.    * @const
  2684.    */
  2685.   BubbleBase.ARROW_OFFSET = 30;
  2686.  
  2687.   /**
  2688.    * Minimum horizontal spacing between edge of bubble and edge of viewport
  2689.    * (when using the ENTIRELY_VISIBLE alignment). In pixels.
  2690.    * @type {number}
  2691.    * @const
  2692.    */
  2693.   BubbleBase.MIN_VIEWPORT_EDGE_MARGIN = 2;
  2694.  
  2695.   BubbleBase.prototype = {
  2696.     // Set up the prototype chain.
  2697.     __proto__: HTMLDivElement.prototype,
  2698.  
  2699.     /**
  2700.      * Initialization function for the cr.ui framework.
  2701.      */
  2702.     decorate: function() {
  2703.       this.className = 'bubble';
  2704.       this.innerHTML =
  2705.           '<div class="bubble-content"></div>' +
  2706.           '<div class="bubble-shadow"></div>' +
  2707.           '<div class="bubble-arrow"></div>';
  2708.       this.hidden = true;
  2709.       this.bubbleAlignment = BubbleAlignment.ENTIRELY_VISIBLE;
  2710.     },
  2711.  
  2712.     /**
  2713.      * Set the anchor node, i.e. the node that this bubble points at. Only
  2714.      * available when the bubble is not being shown.
  2715.      * @param {HTMLElement} node The new anchor node.
  2716.      */
  2717.     set anchorNode(node) {
  2718.       if (!this.hidden)
  2719.         return;
  2720.  
  2721.       this.anchorNode_ = node;
  2722.     },
  2723.  
  2724.     /**
  2725.      * Set the conent of the bubble. Only available when the bubble is not being
  2726.      * shown.
  2727.      * @param {HTMLElement} node The root node of the new content.
  2728.      */
  2729.     set content(node) {
  2730.       if (!this.hidden)
  2731.         return;
  2732.  
  2733.       var bubbleContent = this.querySelector('.bubble-content');
  2734.       bubbleContent.innerHTML = '';
  2735.       bubbleContent.appendChild(node);
  2736.     },
  2737.  
  2738.     /**
  2739.      * Set the arrow location. Only available when the bubble is not being
  2740.      * shown.
  2741.      * @param {cr.ui.ArrowLocation} location The new arrow location.
  2742.      */
  2743.     set arrowLocation(location) {
  2744.       if (!this.hidden)
  2745.         return;
  2746.  
  2747.       this.arrowAtRight_ = location == ArrowLocation.TOP_END ||
  2748.                            location == ArrowLocation.BOTTOM_END;
  2749.       if (document.documentElement.dir == 'rtl')
  2750.         this.arrowAtRight_ = !this.arrowAtRight_;
  2751.       this.arrowAtTop_ = location == ArrowLocation.TOP_START ||
  2752.                          location == ArrowLocation.TOP_END;
  2753.     },
  2754.  
  2755.     /**
  2756.      * Set the bubble alignment. Only available when the bubble is not being
  2757.      * shown.
  2758.      * @param {cr.ui.BubbleAlignment} alignment The new bubble alignment.
  2759.      */
  2760.     set bubbleAlignment(alignment) {
  2761.       if (!this.hidden)
  2762.         return;
  2763.  
  2764.       this.bubbleAlignment_ = alignment;
  2765.     },
  2766.  
  2767.     /**
  2768.      * Update the position of the bubble. Whenever the layout may have changed,
  2769.      * the bubble should either be repositioned by calling this function or
  2770.      * hidden so that it does not point to a nonsensical location on the page.
  2771.      */
  2772.     reposition: function() {
  2773.       var documentWidth = document.documentElement.clientWidth;
  2774.       var documentHeight = document.documentElement.clientHeight;
  2775.       var anchor = this.anchorNode_.getBoundingClientRect();
  2776.       var anchorMid = (anchor.left + anchor.right) / 2;
  2777.       var bubble = this.getBoundingClientRect();
  2778.       var arrow = this.querySelector('.bubble-arrow').getBoundingClientRect();
  2779.  
  2780.       if (this.bubbleAlignment_ == BubbleAlignment.ENTIRELY_VISIBLE) {
  2781.         // Work out horizontal placement. The bubble is initially positioned so
  2782.         // that the arrow tip points toward the midpoint of the anchor and is
  2783.         // BubbleBase.ARROW_OFFSET pixels from the reference edge and (as
  2784.         // specified by the arrow location). If the bubble is not entirely
  2785.         // within view, it is then shifted, preserving the arrow tip position.
  2786.         var left = this.arrowAtRight_ ?
  2787.            anchorMid + BubbleBase.ARROW_OFFSET - bubble.width :
  2788.            anchorMid - BubbleBase.ARROW_OFFSET;
  2789.         var max_left_pos =
  2790.             documentWidth - bubble.width - BubbleBase.MIN_VIEWPORT_EDGE_MARGIN;
  2791.         var min_left_pos = BubbleBase.MIN_VIEWPORT_EDGE_MARGIN;
  2792.         if (document.documentElement.dir == 'rtl')
  2793.           left = Math.min(Math.max(left, min_left_pos), max_left_pos);
  2794.         else
  2795.           left = Math.max(Math.min(left, max_left_pos), min_left_pos);
  2796.         var arrowTip = Math.min(
  2797.             Math.max(arrow.width / 2,
  2798.                      this.arrowAtRight_ ? left + bubble.width - anchorMid :
  2799.                                           anchorMid - left),
  2800.             bubble.width - arrow.width / 2);
  2801.  
  2802.         // Work out the vertical placement, attempting to fit the bubble
  2803.         // entirely into view. The following placements are considered in
  2804.         // decreasing order of preference:
  2805.         // * Outside the anchor, arrow tip touching the anchor (arrow at
  2806.         //   top/bottom as specified by the arrow location).
  2807.         // * Outside the anchor, arrow tip touching the anchor (arrow at
  2808.         //   bottom/top, opposite the specified arrow location).
  2809.         // * Outside the anchor, arrow tip overlapping the anchor (arrow at
  2810.         //   top/bottom as specified by the arrow location).
  2811.         // * Outside the anchor, arrow tip overlapping the anchor (arrow at
  2812.         //   bottom/top, opposite the specified arrow location).
  2813.         // * Overlapping the anchor.
  2814.         var offsetTop = Math.min(documentHeight - anchor.bottom - bubble.height,
  2815.                                  arrow.height / 2);
  2816.         var offsetBottom = Math.min(anchor.top - bubble.height,
  2817.                                     arrow.height / 2);
  2818.         if (offsetTop < 0 && offsetBottom < 0) {
  2819.           var top = 0;
  2820.           this.updateArrowPosition_(false, false, arrowTip);
  2821.         } else if (offsetTop > offsetBottom ||
  2822.                    offsetTop == offsetBottom && this.arrowAtTop_) {
  2823.           var top = anchor.bottom + offsetTop;
  2824.           this.updateArrowPosition_(true, true, arrowTip);
  2825.         } else {
  2826.           var top = anchor.top - bubble.height - offsetBottom;
  2827.           this.updateArrowPosition_(true, false, arrowTip);
  2828.         }
  2829.       } else {
  2830.         if (this.bubbleAlignment_ ==
  2831.             BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE) {
  2832.           var left = this.arrowAtRight_ ? anchor.right - bubble.width :
  2833.               anchor.left;
  2834.         } else {
  2835.           var left = this.arrowAtRight_ ?
  2836.               anchorMid - this.clientWidth + BubbleBase.ARROW_OFFSET :
  2837.               anchorMid - BubbleBase.ARROW_OFFSET;
  2838.         }
  2839.         var top = this.arrowAtTop_ ? anchor.bottom + arrow.height / 2 :
  2840.             anchor.top - this.clientHeight - arrow.height / 2;
  2841.         this.updateArrowPosition_(true, this.arrowAtTop_,
  2842.                                   BubbleBase.ARROW_OFFSET);
  2843.       }
  2844.  
  2845.       this.style.left = left + 'px';
  2846.       this.style.top = top + 'px';
  2847.     },
  2848.  
  2849.     /**
  2850.      * Show the bubble.
  2851.      */
  2852.     show: function() {
  2853.       if (!this.hidden)
  2854.         return;
  2855.  
  2856.       this.attachToDOM_();
  2857.       this.hidden = false;
  2858.       this.reposition();
  2859.  
  2860.       var doc = this.ownerDocument;
  2861.       this.eventTracker_ = new EventTracker;
  2862.       this.eventTracker_.add(doc, 'keydown', this, true);
  2863.       this.eventTracker_.add(doc, 'mousedown', this, true);
  2864.     },
  2865.  
  2866.     /**
  2867.      * Hide the bubble.
  2868.      */
  2869.     hide: function() {
  2870.       if (this.hidden)
  2871.         return;
  2872.  
  2873.       this.eventTracker_.removeAll();
  2874.       this.hidden = true;
  2875.       this.parentNode.removeChild(this);
  2876.     },
  2877.  
  2878.     /**
  2879.      * Handle keyboard events, dismissing the bubble if necessary.
  2880.      * @param {Event} event The event.
  2881.      */
  2882.     handleEvent: function(event) {
  2883.       // Close the bubble when the user presses <Esc>.
  2884.       if (event.type == 'keydown' && event.keyCode == 27) {
  2885.         this.hide();
  2886.         event.preventDefault();
  2887.         event.stopPropagation();
  2888.       }
  2889.     },
  2890.  
  2891.     /**
  2892.      * Attach the bubble to the document's DOM.
  2893.      * @private
  2894.      */
  2895.     attachToDOM_: function() {
  2896.       document.body.appendChild(this);
  2897.     },
  2898.  
  2899.     /**
  2900.      * Update the arrow so that it appears at the correct position.
  2901.      * @param {Boolean} visible Whether the arrow should be visible.
  2902.      * @param {Boolean} atTop Whether the arrow should be at the top of the
  2903.      * bubble.
  2904.      * @param {number} tipOffset The horizontal distance between the tip of the
  2905.      * arrow and the reference edge of the bubble (as specified by the arrow
  2906.      * location).
  2907.      * @private
  2908.      */
  2909.     updateArrowPosition_: function(visible, atTop, tipOffset) {
  2910.       var bubbleArrow = this.querySelector('.bubble-arrow');
  2911.       bubbleArrow.hidden = !visible;
  2912.       if (!visible)
  2913.         return;
  2914.  
  2915.       var edgeOffset = (-bubbleArrow.clientHeight / 2) + 'px';
  2916.       bubbleArrow.style.top = atTop ? edgeOffset : 'auto';
  2917.       bubbleArrow.style.bottom = atTop ? 'auto' : edgeOffset;
  2918.  
  2919.       edgeOffset = (tipOffset - bubbleArrow.offsetWidth / 2) + 'px';
  2920.       bubbleArrow.style.left = this.arrowAtRight_ ? 'auto' : edgeOffset;
  2921.       bubbleArrow.style.right = this.arrowAtRight_ ? edgeOffset : 'auto';
  2922.     },
  2923.   };
  2924.  
  2925.   /**
  2926.    * A bubble that remains open until the user explicitly dismisses it or clicks
  2927.    * outside the bubble after it has been shown for at least the specified
  2928.    * amount of time (making it less likely that the user will unintentionally
  2929.    * dismiss the bubble). The bubble repositions itself on layout changes.
  2930.    */
  2931.   var Bubble = cr.ui.define('div');
  2932.  
  2933.   Bubble.prototype = {
  2934.     // Set up the prototype chain
  2935.     __proto__: BubbleBase.prototype,
  2936.  
  2937.     /**
  2938.      * Initialization function for the cr.ui framework.
  2939.      */
  2940.     decorate: function() {
  2941.       BubbleBase.prototype.decorate.call(this);
  2942.  
  2943.       var close = document.createElement('div');
  2944.       close.className = 'bubble-close';
  2945.       this.insertBefore(close, this.querySelector('.bubble-content'));
  2946.  
  2947.       this.handleCloseEvent = this.hide;
  2948.       this.deactivateToDismissDelay_ = 0;
  2949.       this.bubbleAlignment = BubbleAlignment.ARROW_TO_MID_ANCHOR;
  2950.     },
  2951.  
  2952.     /**
  2953.      * Handler for close events triggered when the close button is clicked. By
  2954.      * default, set to this.hide. Only available when the bubble is not being
  2955.      * shown.
  2956.      * @param {function} handler The new handler, a function with no parameters.
  2957.      */
  2958.     set handleCloseEvent(handler) {
  2959.       if (!this.hidden)
  2960.         return;
  2961.  
  2962.       this.handleCloseEvent_ = handler;
  2963.     },
  2964.  
  2965.     /**
  2966.      * Set the delay before the user is allowed to click outside the bubble to
  2967.      * dismiss it. Using a delay makes it less likely that the user will
  2968.      * unintentionally dismiss the bubble.
  2969.      * @param {number} delay The delay in milliseconds.
  2970.      */
  2971.     set deactivateToDismissDelay(delay) {
  2972.       this.deactivateToDismissDelay_ = delay;
  2973.     },
  2974.  
  2975.     /**
  2976.      * Hide or show the close button.
  2977.      * @param {Boolean} isVisible True if the close button should be visible.
  2978.      */
  2979.     set closeButtonVisible(isVisible) {
  2980.       this.querySelector('.bubble-close').hidden = !isVisible;
  2981.     },
  2982.  
  2983.     /**
  2984.      * Show the bubble.
  2985.      */
  2986.     show: function() {
  2987.       if (!this.hidden)
  2988.         return;
  2989.  
  2990.       BubbleBase.prototype.show.call(this);
  2991.  
  2992.       this.showTime_ = Date.now();
  2993.       this.eventTracker_.add(window, 'resize', this.reposition.bind(this));
  2994.     },
  2995.  
  2996.     /**
  2997.      * Handle keyboard and mouse events, dismissing the bubble if necessary.
  2998.      * @param {Event} event The event.
  2999.      */
  3000.     handleEvent: function(event) {
  3001.       BubbleBase.prototype.handleEvent.call(this, event);
  3002.  
  3003.       if (event.type == 'mousedown') {
  3004.         // Dismiss the bubble when the user clicks on the close button.
  3005.         if (event.target == this.querySelector('.bubble-close')) {
  3006.           this.handleCloseEvent_();
  3007.         // Dismiss the bubble when the user clicks outside it after the
  3008.         // specified delay has passed.
  3009.         } else if (!this.contains(event.target) &&
  3010.             Date.now() - this.showTime_ >= this.deactivateToDismissDelay_) {
  3011.           this.hide();
  3012.         }
  3013.       }
  3014.     },
  3015.   };
  3016.  
  3017.   return {
  3018.     ArrowLocation: ArrowLocation,
  3019.     BubbleAlignment: BubbleAlignment,
  3020.     BubbleBase: BubbleBase,
  3021.     Bubble: Bubble
  3022.   };
  3023. });
  3024. </script>
  3025. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3026. // Use of this source code is governed by a BSD-style license that can be
  3027. // found in the LICENSE file.
  3028.  
  3029. /**
  3030.  * @fileoverview Card slider implementation. Allows you to create interactions
  3031.  * that have items that can slide left to right to reveal additional items.
  3032.  * Works by adding the necessary event handlers to a specific DOM structure
  3033.  * including a frame, container and cards.
  3034.  * - The frame defines the boundary of one item. Each card will be expanded to
  3035.  *   fill the width of the frame. This element is also overflow hidden so that
  3036.  *   the additional items left / right do not trigger horizontal scrolling.
  3037.  * - The container is what all the touch events are attached to. This element
  3038.  *   will be expanded to be the width of all cards.
  3039.  * - The cards are the individual viewable items. There should be one card for
  3040.  *   each item in the list. Only one card will be visible at a time. Two cards
  3041.  *   will be visible while you are transitioning between cards.
  3042.  *
  3043.  * This class is designed to work well on any hardware-accelerated touch device.
  3044.  * It should still work on pre-hardware accelerated devices it just won't feel
  3045.  * very good. It should also work well with a mouse.
  3046.  */
  3047.  
  3048. // Use an anonymous function to enable strict mode just for this file (which
  3049. // will be concatenated with other files when embedded in Chrome
  3050. cr.define('cr.ui', function() {
  3051.   'use strict';
  3052.  
  3053.   /**
  3054.    * @constructor
  3055.    * @param {!Element} frame The bounding rectangle that cards are visible in.
  3056.    * @param {!Element} container The surrounding element that will have event
  3057.    *     listeners attached to it.
  3058.    * @param {number} cardWidth The width of each card should have.
  3059.    */
  3060.   function CardSlider(frame, container, cardWidth) {
  3061.     /**
  3062.      * @type {!Element}
  3063.      * @private
  3064.      */
  3065.     this.frame_ = frame;
  3066.  
  3067.     /**
  3068.      * @type {!Element}
  3069.      * @private
  3070.      */
  3071.     this.container_ = container;
  3072.  
  3073.     /**
  3074.      * Array of card elements.
  3075.      * @type {!Array.<!Element>}
  3076.      * @private
  3077.      */
  3078.     this.cards_ = [];
  3079.  
  3080.     /**
  3081.      * Index of currently shown card.
  3082.      * @type {number}
  3083.      * @private
  3084.      */
  3085.     this.currentCard_ = -1;
  3086.  
  3087.     /**
  3088.      * @type {number}
  3089.      * @private
  3090.      */
  3091.     this.cardWidth_ = cardWidth;
  3092.  
  3093.     /**
  3094.      * @type {!cr.ui.TouchHandler}
  3095.      * @private
  3096.      */
  3097.     this.touchHandler_ = new cr.ui.TouchHandler(this.container_);
  3098.   }
  3099.  
  3100.  
  3101.   /**
  3102.    * The time to transition between cards when animating. Measured in ms.
  3103.    * @type {number}
  3104.    * @private
  3105.    * @const
  3106.    */
  3107.   CardSlider.TRANSITION_TIME_ = 200;
  3108.  
  3109.  
  3110.   /**
  3111.    * The minimum velocity required to transition cards if they did not drag past
  3112.    * the halfway point between cards. Measured in pixels / ms.
  3113.    * @type {number}
  3114.    * @private
  3115.    * @const
  3116.    */
  3117.   CardSlider.TRANSITION_VELOCITY_THRESHOLD_ = 0.2;
  3118.  
  3119.  
  3120.   CardSlider.prototype = {
  3121.     /**
  3122.      * The current left offset of the container relative to the frame. This
  3123.      * position does not include deltas from active drag operations, and
  3124.      * always aligns with a frame boundary.
  3125.      * @type {number}
  3126.      * @private
  3127.      */
  3128.     currentLeft_: 0,
  3129.  
  3130.     /**
  3131.      * Current offset relative to |currentLeft_| due to an active drag
  3132.      * operation.
  3133.      * @type {number}
  3134.      * @private
  3135.      */
  3136.     deltaX_: 0,
  3137.  
  3138.     /**
  3139.      * Initialize all elements and event handlers. Must call after construction
  3140.      * and before usage.
  3141.      * @param {boolean} ignoreMouseWheelEvents If true, horizontal mouse wheel
  3142.      *     events will be ignored, rather than flipping between pages.
  3143.      */
  3144.     initialize: function(ignoreMouseWheelEvents) {
  3145.       var view = this.container_.ownerDocument.defaultView;
  3146.       assert(view.getComputedStyle(this.container_).display == '-webkit-box',
  3147.           'Container should be display -webkit-box.');
  3148.       assert(view.getComputedStyle(this.frame_).overflow == 'hidden',
  3149.           'Frame should be overflow hidden.');
  3150.       assert(view.getComputedStyle(this.container_).position == 'static',
  3151.           'Container should be position static.');
  3152.  
  3153.       this.updateCardWidths_();
  3154.  
  3155.       this.mouseWheelScrollAmount_ = 0;
  3156.       this.mouseWheelCardSelected_ = false;
  3157.       this.mouseWheelIsContinuous_ = false;
  3158.       this.scrollClearTimeout_ = null;
  3159.       if (!ignoreMouseWheelEvents) {
  3160.         this.frame_.addEventListener('mousewheel',
  3161.                                      this.onMouseWheel_.bind(this));
  3162.       }
  3163.       this.container_.addEventListener(
  3164.           'webkitTransitionEnd', this.onWebkitTransitionEnd_.bind(this));
  3165.  
  3166.       // Also support touch events in case a touch screen happens to be
  3167.       // available.  Note that this has minimal impact in the common case of
  3168.       // no touch events (eg. we're mainly just adding listeners for events that
  3169.       // will never trigger).
  3170.       var TouchHandler = cr.ui.TouchHandler;
  3171.       this.container_.addEventListener(TouchHandler.EventType.TOUCH_START,
  3172.                                        this.onTouchStart_.bind(this));
  3173.       this.container_.addEventListener(TouchHandler.EventType.DRAG_START,
  3174.                                        this.onDragStart_.bind(this));
  3175.       this.container_.addEventListener(TouchHandler.EventType.DRAG_MOVE,
  3176.                                        this.onDragMove_.bind(this));
  3177.       this.container_.addEventListener(TouchHandler.EventType.DRAG_END,
  3178.                                        this.onDragEnd_.bind(this));
  3179.  
  3180.       this.touchHandler_.enable(/* opt_capture */ false);
  3181.     },
  3182.  
  3183.     /**
  3184.      * Use in cases where the width of the frame has changed in order to update
  3185.      * the width of cards. For example should be used when orientation changes
  3186.      * in full width sliders.
  3187.      * @param {number} newCardWidth Width all cards should have, in pixels.
  3188.      */
  3189.     resize: function(newCardWidth) {
  3190.       if (newCardWidth != this.cardWidth_) {
  3191.         this.cardWidth_ = newCardWidth;
  3192.  
  3193.         this.updateCardWidths_();
  3194.  
  3195.         // Must upate the transform on the container to show the correct card.
  3196.         this.transformToCurrentCard_();
  3197.       }
  3198.     },
  3199.  
  3200.     /**
  3201.      * Sets the cards used. Can be called more than once to switch card sets.
  3202.      * @param {!Array.<!Element>} cards The individual viewable cards.
  3203.      * @param {number} index Index of the card to in the new set of cards to
  3204.      *     navigate to.
  3205.      */
  3206.     setCards: function(cards, index) {
  3207.       assert(index >= 0 && index < cards.length,
  3208.           'Invalid index in CardSlider#setCards');
  3209.       this.cards_ = cards;
  3210.  
  3211.       this.updateCardWidths_();
  3212.       this.updateSelectedCardAttributes_();
  3213.  
  3214.       // Jump to the given card index.
  3215.       this.selectCard(index, false, false, true);
  3216.     },
  3217.  
  3218.     /**
  3219.      * Ensures that for all cards:
  3220.      * - if the card is the current card, then it has 'selected-card' in its
  3221.      *   classList, and is visible for accessibility
  3222.      * - if the card is not the selected card, then it does not have
  3223.      *   'selected-card' in its classList, and is invisible for accessibility.
  3224.      * @private
  3225.      */
  3226.     updateSelectedCardAttributes_: function() {
  3227.       for (var i = 0; i < this.cards_.length; i++) {
  3228.         if (i == this.currentCard_) {
  3229.           this.cards_[i].classList.add('selected-card');
  3230.           this.cards_[i].removeAttribute('aria-hidden');
  3231.         } else {
  3232.           this.cards_[i].classList.remove('selected-card');
  3233.           this.cards_[i].setAttribute('aria-hidden', true);
  3234.         }
  3235.       }
  3236.     },
  3237.  
  3238.     /**
  3239.      * Updates the width of each card.
  3240.      * @private
  3241.      */
  3242.     updateCardWidths_: function() {
  3243.       for (var i = 0, card; card = this.cards_[i]; i++)
  3244.         card.style.width = this.cardWidth_ + 'px';
  3245.     },
  3246.  
  3247.     /**
  3248.      * Returns the index of the current card.
  3249.      * @return {number} index of the current card.
  3250.      */
  3251.     get currentCard() {
  3252.       return this.currentCard_;
  3253.     },
  3254.  
  3255.     /**
  3256.      * Allows setting the current card index.
  3257.      * @param {number} index A new index to set the current index to.
  3258.      * @return {number} The new index after having been set.
  3259.      */
  3260.     set currentCard(index) {
  3261.       return (this.currentCard_ = index);
  3262.     },
  3263.  
  3264.     /**
  3265.      * Returns the number of cards.
  3266.      * @return {number} number of cards.
  3267.      */
  3268.     get cardCount() {
  3269.       return this.cards_.length;
  3270.     },
  3271.  
  3272.     /**
  3273.      * Returns the current card itself.
  3274.      * @return {!Element} the currently shown card.
  3275.      */
  3276.     get currentCardValue() {
  3277.       return this.cards_[this.currentCard_];
  3278.     },
  3279.  
  3280.     /**
  3281.      * Returns the frame holding the cards.
  3282.      * @return {Element} The frame used to position the cards.
  3283.      */
  3284.     get frame() {
  3285.       return this.frame_;
  3286.     },
  3287.  
  3288.     /**
  3289.      * Handle horizontal scrolls to flip between pages.
  3290.      * @private
  3291.      */
  3292.     onMouseWheel_: function(e) {
  3293.       if (e.wheelDeltaX == 0)
  3294.         return;
  3295.  
  3296.       // Continuous devices such as an Apple Touchpad or Apple MagicMouse will
  3297.       // send arbitrary delta values. Conversly, standard mousewheels will
  3298.       // send delta values in increments of 120.  (There is of course a small
  3299.       // chance we mistake a continuous device for a non-continuous device.
  3300.       // Unfortunately there isn't a better way to do this until real touch
  3301.       // events are available to desktop clients.)
  3302.       var DISCRETE_DELTA = 120;
  3303.       if (e.wheelDeltaX % DISCRETE_DELTA)
  3304.         this.mouseWheelIsContinuous_ = true;
  3305.  
  3306.       if (this.mouseWheelIsContinuous_) {
  3307.         // For continuous devices, detect a page swipe when the accumulated
  3308.         // delta matches a pre-defined threshhold.  After changing the page,
  3309.         // ignore wheel events for a short time before repeating this process.
  3310.         if (this.mouseWheelCardSelected_) return;
  3311.         this.mouseWheelScrollAmount_ += e.wheelDeltaX;
  3312.         if (Math.abs(this.mouseWheelScrollAmount_) >= 600) {
  3313.           var pagesToScroll = this.mouseWheelScrollAmount_ > 0 ? 1 : -1;
  3314.           if (!isRTL())
  3315.             pagesToScroll *= -1;
  3316.           var newCardIndex = this.currentCard + pagesToScroll;
  3317.           newCardIndex = Math.min(this.cards_.length - 1,
  3318.                                   Math.max(0, newCardIndex));
  3319.           this.selectCard(newCardIndex, true);
  3320.           this.mouseWheelCardSelected_ = true;
  3321.         }
  3322.       } else {
  3323.         // For discrete devices, consider each wheel tick a page change.
  3324.         var pagesToScroll = e.wheelDeltaX / DISCRETE_DELTA;
  3325.         if (!isRTL())
  3326.           pagesToScroll *= -1;
  3327.         var newCardIndex = this.currentCard + pagesToScroll;
  3328.         newCardIndex = Math.min(this.cards_.length - 1,
  3329.                                 Math.max(0, newCardIndex));
  3330.         this.selectCard(newCardIndex, true);
  3331.       }
  3332.  
  3333.       // We got a mouse wheel event, so cancel any pending scroll wheel timeout.
  3334.       if (this.scrollClearTimeout_ != null)
  3335.         clearTimeout(this.scrollClearTimeout_);
  3336.       // If we didn't use up all the scroll, hold onto it for a little bit, but
  3337.       // drop it after a delay.
  3338.       if (this.mouseWheelScrollAmount_ != 0) {
  3339.         this.scrollClearTimeout_ =
  3340.             setTimeout(this.clearMouseWheelScroll_.bind(this), 500);
  3341.       }
  3342.     },
  3343.  
  3344.     /**
  3345.      * Resets the amount of horizontal scroll we've seen to 0. See
  3346.      * onMouseWheel_.
  3347.      * @private
  3348.      */
  3349.     clearMouseWheelScroll_: function() {
  3350.       this.mouseWheelScrollAmount_ = 0;
  3351.       this.mouseWheelCardSelected_ = false;
  3352.     },
  3353.  
  3354.     /**
  3355.      * Handles the ends of -webkit-transitions on -webkit-transform (animated
  3356.      * card switches).
  3357.      * @param {Event} e The webkitTransitionEnd event.
  3358.      * @private
  3359.      */
  3360.     onWebkitTransitionEnd_: function(e) {
  3361.       // Ignore irrelevant transitions that might bubble up.
  3362.       if (e.target !== this.container_ ||
  3363.           e.propertyName != '-webkit-transform') {
  3364.         return;
  3365.       }
  3366.       this.fireChangeEndedEvent_(true);
  3367.     },
  3368.  
  3369.     /**
  3370.      * Dispatches a simple event to tell subscribers we're done moving to the
  3371.      * newly selected card.
  3372.      * @param {boolean} wasAnimated whether or not the change was animated.
  3373.      * @private
  3374.      */
  3375.     fireChangeEndedEvent_: function(wasAnimated) {
  3376.       var e = document.createEvent('Event');
  3377.       e.initEvent('cardSlider:card_change_ended', true, true);
  3378.       e.cardSlider = this;
  3379.       e.changedTo = this.currentCard_;
  3380.       e.wasAnimated = wasAnimated;
  3381.       this.container_.dispatchEvent(e);
  3382.     },
  3383.  
  3384.     /**
  3385.      * Add a card to the card slider at a particular index. If the card being
  3386.      * added is inserted in front of the current card, cardSlider.currentCard
  3387.      * will be adjusted accordingly (to current card + 1).
  3388.      * @param {!Node} card A card that will be added to the card slider.
  3389.      * @param {number} index An index at which the given |card| should be
  3390.      *     inserted. Must be positive and less than the number of cards.
  3391.      */
  3392.     addCardAtIndex: function(card, index) {
  3393.       assert(card instanceof Node, '|card| isn\'t a Node');
  3394.       this.assertValidIndex_(index);
  3395.       this.cards_ = Array.prototype.concat.call(
  3396.           this.cards_.slice(0, index), card, this.cards_.slice(index));
  3397.  
  3398.       this.updateSelectedCardAttributes_();
  3399.  
  3400.       if (this.currentCard_ == -1)
  3401.         this.currentCard_ = 0;
  3402.       else if (index <= this.currentCard_)
  3403.         this.selectCard(this.currentCard_ + 1, false, true, true);
  3404.  
  3405.       this.fireAddedEvent_(card, index);
  3406.     },
  3407.  
  3408.     /**
  3409.      * Append a card to the end of the list.
  3410.      * @param {!Node} card A card to add at the end of the card slider.
  3411.      */
  3412.     appendCard: function(card) {
  3413.       assert(card instanceof Node, '|card| isn\'t a Node');
  3414.       this.cards_.push(card);
  3415.       this.fireAddedEvent_(card, this.cards_.length - 1);
  3416.     },
  3417.  
  3418.     /**
  3419.      * Dispatches a simple event to tell interested subscribers that a card was
  3420.      * added to this card slider.
  3421.      * @param {Node} card The recently added card.
  3422.      * @param {number} index The position of the newly added card.
  3423.      * @private
  3424.      */
  3425.     fireAddedEvent_: function(card, index) {
  3426.       this.assertValidIndex_(index);
  3427.       var e = document.createEvent('Event');
  3428.       e.initEvent('cardSlider:card_added', true, true);
  3429.       e.addedIndex = index;
  3430.       e.addedCard = card;
  3431.       this.container_.dispatchEvent(e);
  3432.     },
  3433.  
  3434.     /**
  3435.      * Returns the card at a particular index.
  3436.      * @param {number} index The index of the card to return.
  3437.      * @return {!Element} The card at the given index.
  3438.      */
  3439.     getCardAtIndex: function(index) {
  3440.       this.assertValidIndex_(index);
  3441.       return this.cards_[index];
  3442.     },
  3443.  
  3444.     /**
  3445.      * Removes a card by index from the card slider. If the card to be removed
  3446.      * is the current card or in front of the current card, the current card
  3447.      * will be updated (to current card - 1).
  3448.      * @param {!Node} card A card to be removed.
  3449.      */
  3450.     removeCard: function(card) {
  3451.       assert(card instanceof Node, '|card| isn\'t a Node');
  3452.       this.removeCardAtIndex(this.cards_.indexOf(card));
  3453.     },
  3454.  
  3455.     /**
  3456.      * Removes a card by index from the card slider. If the card to be removed
  3457.      * is the current card or in front of the current card, the current card
  3458.      * will be updated (to current card - 1).
  3459.      * @param {number} index The index of the tile that should be removed.
  3460.      */
  3461.     removeCardAtIndex: function(index) {
  3462.       this.assertValidIndex_(index);
  3463.       var removed = this.cards_.splice(index, 1).pop();
  3464.  
  3465.       if (this.cards_.length == 0)
  3466.         this.currentCard_ = -1;
  3467.       else if (index < this.currentCard_)
  3468.         this.selectCard(this.currentCard_ - 1, false, true);
  3469.  
  3470.       this.fireRemovedEvent_(removed, index);
  3471.     },
  3472.  
  3473.     /**
  3474.      * Dispatches a cardSlider:card_removed event so interested subscribers know
  3475.      * when a card was removed from this card slider.
  3476.      * @param {Node} card The recently removed card.
  3477.      * @param {number} index The index of the card before it was removed.
  3478.      * @private
  3479.      */
  3480.     fireRemovedEvent_: function(card, index) {
  3481.       var e = document.createEvent('Event');
  3482.       e.initEvent('cardSlider:card_removed', true, true);
  3483.       e.removedCard = card;
  3484.       e.removedIndex = index;
  3485.       this.container_.dispatchEvent(e);
  3486.     },
  3487.  
  3488.     /**
  3489.      * This re-syncs the -webkit-transform that's used to position the frame in
  3490.      * the likely event it needs to be updated by a card being inserted or
  3491.      * removed in the flow.
  3492.      */
  3493.     repositionFrame: function() {
  3494.       this.transformToCurrentCard_();
  3495.     },
  3496.  
  3497.     /**
  3498.      * Checks the the given |index| exists in this.cards_.
  3499.      * @param {number} index An index to check.
  3500.      * @private
  3501.      */
  3502.     assertValidIndex_: function(index) {
  3503.       assert(index >= 0 && index < this.cards_.length);
  3504.     },
  3505.  
  3506.     /**
  3507.      * Selects a new card, ensuring that it is a valid index, transforming the
  3508.      * view and possibly calling the change card callback.
  3509.      * @param {number} newCardIndex Index of card to show.
  3510.      * @param {boolean=} opt_animate If true will animate transition from
  3511.      *     current position to new position.
  3512.      * @param {boolean=} opt_dontNotify If true, don't tell subscribers that
  3513.      *     we've changed cards.
  3514.      * @param {boolean=} opt_forceChange If true, ignore if the card already
  3515.      *     selected.
  3516.      */
  3517.     selectCard: function(newCardIndex,
  3518.                          opt_animate,
  3519.                          opt_dontNotify,
  3520.                          opt_forceChange) {
  3521.       this.assertValidIndex_(newCardIndex);
  3522.  
  3523.       var previousCard = this.currentCardValue;
  3524.       var isChangingCard =
  3525.           !this.cards_[newCardIndex].classList.contains('selected-card');
  3526.  
  3527.       if (typeof opt_forceChange != 'undefined' && opt_forceChange)
  3528.         isChangingCard = true;
  3529.  
  3530.       if (isChangingCard) {
  3531.         this.currentCard_ = newCardIndex;
  3532.         this.updateSelectedCardAttributes_();
  3533.       }
  3534.  
  3535.       var willTransitionHappen = this.transformToCurrentCard_(opt_animate);
  3536.  
  3537.       if (isChangingCard && !opt_dontNotify) {
  3538.         var event = document.createEvent('Event');
  3539.         event.initEvent('cardSlider:card_changed', true, true);
  3540.         event.cardSlider = this;
  3541.         event.wasAnimated = !!opt_animate;
  3542.         this.container_.dispatchEvent(event);
  3543.  
  3544.         // We also dispatch an event on the cards themselves.
  3545.         if (previousCard) {
  3546.           cr.dispatchSimpleEvent(previousCard, 'carddeselected',
  3547.                                  true, true);
  3548.         }
  3549.         cr.dispatchSimpleEvent(this.currentCardValue, 'cardselected',
  3550.                                true, true);
  3551.       }
  3552.  
  3553.       // If we're not changing, animated, or transitioning, fire a
  3554.       // cardSlider:card_change_ended event right away.
  3555.       if ((!isChangingCard || !opt_animate || !willTransitionHappen) &&
  3556.           !opt_dontNotify) {
  3557.         this.fireChangeEndedEvent_(false);
  3558.       }
  3559.     },
  3560.  
  3561.     /**
  3562.      * Selects a card from the stack. Passes through to selectCard.
  3563.      * @param {Node} newCard The card that should be selected.
  3564.      * @param {boolean=} opt_animate Whether to animate.
  3565.      */
  3566.     selectCardByValue: function(newCard, opt_animate) {
  3567.       var i = this.cards_.indexOf(newCard);
  3568.       assert(i != -1);
  3569.       this.selectCard(i, opt_animate);
  3570.     },
  3571.  
  3572.     /**
  3573.      * Centers the view on the card denoted by this.currentCard. Can either
  3574.      * animate to that card or snap to it.
  3575.      * @param {boolean=} opt_animate If true will animate transition from
  3576.      *     current position to new position.
  3577.      * @return {boolean} Whether or not a transformation was necessary.
  3578.      * @private
  3579.      */
  3580.     transformToCurrentCard_: function(opt_animate) {
  3581.       var prevLeft = this.currentLeft_;
  3582.       this.currentLeft_ = -this.cardWidth_ *
  3583.           (isRTL() ? this.cards_.length - this.currentCard - 1 :
  3584.                      this.currentCard);
  3585.  
  3586.       // If there's no change, return something to let the caller know there
  3587.       // won't be a transition occuring.
  3588.       if (prevLeft == this.currentLeft_ && this.deltaX_ == 0)
  3589.         return false;
  3590.  
  3591.       // Animate to the current card, which will either transition if the
  3592.       // current card is new, or reset the existing card if we didn't drag
  3593.       // enough to change cards.
  3594.       var transition = '';
  3595.       if (opt_animate) {
  3596.         transition = '-webkit-transform ' + CardSlider.TRANSITION_TIME_ +
  3597.                      'ms ease-in-out';
  3598.       }
  3599.       this.container_.style.WebkitTransition = transition;
  3600.       this.translateTo_(this.currentLeft_);
  3601.  
  3602.       return true;
  3603.     },
  3604.  
  3605.     /**
  3606.      * Moves the view to the specified position.
  3607.      * @param {number} x Horizontal position to move to.
  3608.      * @private
  3609.      */
  3610.     translateTo_: function(x) {
  3611.       // We use a webkitTransform to slide because this is GPU accelerated on
  3612.       // Chrome and iOS.  Once Chrome does GPU acceleration on the position
  3613.       // fixed-layout elements we could simply set the element's position to
  3614.       // fixed and modify 'left' instead.
  3615.       this.deltaX_ = x - this.currentLeft_;
  3616.       this.container_.style.WebkitTransform = 'translate3d(' + x + 'px, 0, 0)';
  3617.     },
  3618.  
  3619.     /* Touch ******************************************************************/
  3620.  
  3621.     /**
  3622.      * Clear any transition that is in progress and enable dragging for the
  3623.      * touch.
  3624.      * @param {!cr.ui.TouchHandler.Event} e The TouchHandler event.
  3625.      * @private
  3626.      */
  3627.     onTouchStart_: function(e) {
  3628.       this.container_.style.WebkitTransition = '';
  3629.       e.enableDrag = true;
  3630.     },
  3631.  
  3632.     /**
  3633.      * Tell the TouchHandler that dragging is acceptable when the user begins by
  3634.      * scrolling horizontally and there is more than one card to slide.
  3635.      * @param {!cr.ui.TouchHandler.Event} e The TouchHandler event.
  3636.      * @private
  3637.      */
  3638.     onDragStart_: function(e) {
  3639.       e.enableDrag = this.cardCount > 1 && Math.abs(e.dragDeltaX) >
  3640.           Math.abs(e.dragDeltaY);
  3641.     },
  3642.  
  3643.     /**
  3644.      * On each drag move event reposition the container appropriately so the
  3645.      * cards look like they are sliding.
  3646.      * @param {!cr.ui.TouchHandler.Event} e The TouchHandler event.
  3647.      * @private
  3648.      */
  3649.     onDragMove_: function(e) {
  3650.       var deltaX = e.dragDeltaX;
  3651.       // If dragging beyond the first or last card then apply a backoff so the
  3652.       // dragging feels stickier than usual.
  3653.       if (!this.currentCard && deltaX > 0 ||
  3654.           this.currentCard == (this.cards_.length - 1) && deltaX < 0) {
  3655.         deltaX /= 2;
  3656.       }
  3657.       this.translateTo_(this.currentLeft_ + deltaX);
  3658.     },
  3659.  
  3660.     /**
  3661.      * On drag end events we may want to transition to another card, depending
  3662.      * on the ending position of the drag and the velocity of the drag.
  3663.      * @param {!cr.ui.TouchHandler.Event} e The TouchHandler event.
  3664.      * @private
  3665.      */
  3666.     onDragEnd_: function(e) {
  3667.       var deltaX = e.dragDeltaX;
  3668.       var velocity = this.touchHandler_.getEndVelocity().x;
  3669.       var newX = this.currentLeft_ + deltaX;
  3670.       var newCardIndex = Math.round(-newX / this.cardWidth_);
  3671.  
  3672.       if (newCardIndex == this.currentCard && Math.abs(velocity) >
  3673.           CardSlider.TRANSITION_VELOCITY_THRESHOLD_) {
  3674.         // The drag wasn't far enough to change cards but the velocity was
  3675.         // high enough to transition anyways. If the velocity is to the left
  3676.         // (negative) then the user wishes to go right (card + 1).
  3677.         newCardIndex += velocity > 0 ? -1 : 1;
  3678.       }
  3679.       // Ensure that the new card index is valid.  The new card index could be
  3680.       // invalid if a swipe suggests scrolling off the end of the list of
  3681.       // cards.
  3682.       if (newCardIndex < 0)
  3683.         newCardIndex = 0;
  3684.       else if (newCardIndex >= this.cardCount)
  3685.         newCardIndex = this.cardCount - 1;
  3686.       this.selectCard(newCardIndex, /* animate */ true);
  3687.     },
  3688.  
  3689.     /**
  3690.      * Cancel any current touch/slide as if we saw a touch end
  3691.      */
  3692.     cancelTouch: function() {
  3693.       // Stop listening to any current touch
  3694.       this.touchHandler_.cancelTouch();
  3695.  
  3696.       // Ensure we're at a card bounary
  3697.       this.transformToCurrentCard_(true);
  3698.     },
  3699.   };
  3700.  
  3701.   return {
  3702.     CardSlider: CardSlider
  3703.   };
  3704. });
  3705. </script>
  3706. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3707. // Use of this source code is governed by a BSD-style license that can be
  3708. // found in the LICENSE file.
  3709.  
  3710. cr.define('cr.ui', function() {
  3711.  
  3712.   /** @const */ var Menu = cr.ui.Menu;
  3713.  
  3714.   /**
  3715.    * Handles context menus.
  3716.    * @constructor
  3717.    */
  3718.   function ContextMenuHandler() {}
  3719.  
  3720.   ContextMenuHandler.prototype = {
  3721.  
  3722.     /**
  3723.      * The menu that we are currently showing.
  3724.      * @type {cr.ui.Menu}
  3725.      */
  3726.     menu_: null,
  3727.     get menu() {
  3728.       return this.menu_;
  3729.     },
  3730.  
  3731.     /**
  3732.      * Shows a menu as a context menu.
  3733.      * @param {!Event} e The event triggering the show (usually a contextmenu
  3734.      *     event).
  3735.      * @param {!cr.ui.Menu} menu The menu to show.
  3736.      */
  3737.     showMenu: function(e, menu) {
  3738.       this.menu_ = menu;
  3739.       menu.updateCommands(e.currentTarget);
  3740.       menu.hidden = false;
  3741.       menu.contextElement = e.currentTarget;
  3742.  
  3743.       // when the menu is shown we steal all keyboard events.
  3744.       var doc = menu.ownerDocument;
  3745.       doc.addEventListener('keydown', this, true);
  3746.       doc.addEventListener('mousedown', this, true);
  3747.       // Note: this should be listening for focus, as in menu_button.js, but
  3748.       // as per crbug.com/162190 this indirectly causes odd behaviour.
  3749.       // Since the context menu is currently not keyboard-accessible, blur
  3750.       // is sufficient for now.
  3751.       doc.addEventListener('blur', this, true);
  3752.       doc.defaultView.addEventListener('resize', this);
  3753.       menu.addEventListener('contextmenu', this);
  3754.       menu.addEventListener('activate', this);
  3755.       this.positionMenu_(e, menu);
  3756.     },
  3757.  
  3758.     /**
  3759.      * Hide the currently shown menu.
  3760.      */
  3761.     hideMenu: function() {
  3762.       var menu = this.menu;
  3763.       if (!menu)
  3764.         return;
  3765.  
  3766.       menu.hidden = true;
  3767.       menu.contextElement = null;
  3768.       var doc = menu.ownerDocument;
  3769.       doc.removeEventListener('keydown', this, true);
  3770.       doc.removeEventListener('mousedown', this, true);
  3771.       doc.removeEventListener('blur', this, true);
  3772.       doc.defaultView.removeEventListener('resize', this);
  3773.       menu.removeEventListener('contextmenu', this);
  3774.       menu.removeEventListener('activate', this);
  3775.       menu.selectedIndex = -1;
  3776.       this.menu_ = null;
  3777.  
  3778.       // On windows we might hide the menu in a right mouse button up and if
  3779.       // that is the case we wait some short period before we allow the menu
  3780.       // to be shown again.
  3781.       this.hideTimestamp_ = cr.isWindows ? Date.now() : 0;
  3782.     },
  3783.  
  3784.     /**
  3785.      * Positions the menu
  3786.      * @param {!Event} e The event object triggering the showing.
  3787.      * @param {!cr.ui.Menu} menu The menu to position.
  3788.      * @private
  3789.      */
  3790.     positionMenu_: function(e, menu) {
  3791.       // TODO(arv): Handle scrolled documents when needed.
  3792.  
  3793.       var element = e.currentTarget;
  3794.       var x, y;
  3795.       // When the user presses the context menu key (on the keyboard) we need
  3796.       // to detect this.
  3797.       if (this.keyIsDown_) {
  3798.         var rect = element.getRectForContextMenu ?
  3799.                        element.getRectForContextMenu() :
  3800.                        element.getBoundingClientRect();
  3801.         var offset = Math.min(rect.width, rect.height) / 2;
  3802.         x = rect.left + offset;
  3803.         y = rect.top + offset;
  3804.       } else {
  3805.         x = e.clientX;
  3806.         y = e.clientY;
  3807.       }
  3808.  
  3809.       cr.ui.positionPopupAtPoint(x, y, menu);
  3810.     },
  3811.  
  3812.     /**
  3813.      * Handles event callbacks.
  3814.      * @param {!Event} e The event object.
  3815.      */
  3816.     handleEvent: function(e) {
  3817.       // Keep track of keydown state so that we can use that to determine the
  3818.       // reason for the contextmenu event.
  3819.       switch (e.type) {
  3820.         case 'keydown':
  3821.           this.keyIsDown_ = !e.ctrlKey && !e.altKey &&
  3822.               // context menu key or Shift-F10
  3823.               (e.keyCode == 93 && !e.shiftKey ||
  3824.                e.keyIdentifier == 'F10' && e.shiftKey);
  3825.           break;
  3826.  
  3827.         case 'keyup':
  3828.           this.keyIsDown_ = false;
  3829.           break;
  3830.       }
  3831.  
  3832.       // Context menu is handled even when we have no menu.
  3833.       if (e.type != 'contextmenu' && !this.menu)
  3834.         return;
  3835.  
  3836.       switch (e.type) {
  3837.         case 'mousedown':
  3838.           if (!this.menu.contains(e.target))
  3839.             this.hideMenu();
  3840.           else
  3841.             e.preventDefault();
  3842.           break;
  3843.         case 'keydown':
  3844.           // keyIdentifier does not report 'Esc' correctly
  3845.           if (e.keyCode == 27 /* Esc */) {
  3846.             this.hideMenu();
  3847.             e.stopPropagation();
  3848.             e.preventDefault();
  3849.  
  3850.           // If the menu is visible we let it handle all the keyboard events.
  3851.           } else if (this.menu) {
  3852.             this.menu.handleKeyDown(e);
  3853.             e.preventDefault();
  3854.             e.stopPropagation();
  3855.           }
  3856.           break;
  3857.  
  3858.         case 'activate':
  3859.         case 'blur':
  3860.         case 'resize':
  3861.           this.hideMenu();
  3862.           break;
  3863.  
  3864.         case 'contextmenu':
  3865.           if ((!this.menu || !this.menu.contains(e.target)) &&
  3866.               (!this.hideTimestamp_ || Date.now() - this.hideTimestamp_ > 50))
  3867.             this.showMenu(e, e.currentTarget.contextMenu);
  3868.           e.preventDefault();
  3869.           // Don't allow elements further up in the DOM to show their menus.
  3870.           e.stopPropagation();
  3871.           break;
  3872.       }
  3873.     },
  3874.  
  3875.     /**
  3876.      * Adds a contextMenu property to an element or element class.
  3877.      * @param {!Element|!Function} element The element or class to add the
  3878.      *     contextMenu property to.
  3879.      */
  3880.     addContextMenuProperty: function(element) {
  3881.       if (typeof element == 'function')
  3882.         element = element.prototype;
  3883.  
  3884.       element.__defineGetter__('contextMenu', function() {
  3885.         return this.contextMenu_;
  3886.       });
  3887.       element.__defineSetter__('contextMenu', function(menu) {
  3888.         var oldContextMenu = this.contextMenu;
  3889.  
  3890.         if (typeof menu == 'string' && menu[0] == '#') {
  3891.           menu = this.ownerDocument.getElementById(menu.slice(1));
  3892.           cr.ui.decorate(menu, Menu);
  3893.         }
  3894.  
  3895.         if (menu === oldContextMenu)
  3896.           return;
  3897.  
  3898.         if (oldContextMenu && !menu) {
  3899.           this.removeEventListener('contextmenu', contextMenuHandler);
  3900.           this.removeEventListener('keydown', contextMenuHandler);
  3901.           this.removeEventListener('keyup', contextMenuHandler);
  3902.         }
  3903.         if (menu && !oldContextMenu) {
  3904.           this.addEventListener('contextmenu', contextMenuHandler);
  3905.           this.addEventListener('keydown', contextMenuHandler);
  3906.           this.addEventListener('keyup', contextMenuHandler);
  3907.         }
  3908.  
  3909.         this.contextMenu_ = menu;
  3910.  
  3911.         if (menu && menu.id)
  3912.           this.setAttribute('contextmenu', '#' + menu.id);
  3913.  
  3914.         cr.dispatchPropertyChange(this, 'contextMenu', menu, oldContextMenu);
  3915.       });
  3916.  
  3917.       if (!element.getRectForContextMenu) {
  3918.         /**
  3919.          * @return {!ClientRect} The rect to use for positioning the context
  3920.          *     menu when the context menu is not opened using a mouse position.
  3921.          */
  3922.         element.getRectForContextMenu = function() {
  3923.           return this.getBoundingClientRect();
  3924.         };
  3925.       }
  3926.     },
  3927.  
  3928.     /**
  3929.      * Sets the given contextMenu to the given element. A contextMenu property
  3930.      * would be added if necessary.
  3931.      * @param {!Element} element The element or class to set the contextMenu to.
  3932.      * @param {!cr.ui.Menu} contextMenu The contextMenu property to be set.
  3933.      */
  3934.     setContextMenu: function(element, contextMenu) {
  3935.       if (!element.contextMenu)
  3936.         this.addContextMenuProperty(element);
  3937.       element.contextMenu = contextMenu;
  3938.     }
  3939.   };
  3940.  
  3941.   /**
  3942.    * The singleton context menu handler.
  3943.    * @type {!ContextMenuHandler}
  3944.    */
  3945.   var contextMenuHandler = new ContextMenuHandler;
  3946.  
  3947.   // Export
  3948.   return {
  3949.     contextMenuHandler: contextMenuHandler
  3950.   };
  3951. });
  3952. </script>
  3953. <script>// Copyright (c) 2011 The Chromium Authors. All rights reserved.
  3954. // Use of this source code is governed by a BSD-style license that can be
  3955. // found in the LICENSE file.
  3956.  
  3957. /**
  3958.  * @fileoverview DragWrapper
  3959.  * A class for simplifying HTML5 drag and drop. Classes should use this to
  3960.  * handle the nitty gritty of nested drag enters and leaves.
  3961.  */
  3962. cr.define('cr.ui', function() {
  3963.   /**
  3964.    * Creates a DragWrapper which listens for drag target events on |target| and
  3965.    * delegates event handling to |handler|. The |handler| must implement:
  3966.    *   shouldAcceptDrag
  3967.    *   doDragEnter
  3968.    *   doDragLeave
  3969.    *   doDragOver
  3970.    *   doDrop
  3971.    */
  3972.   function DragWrapper(target, handler) {
  3973.     this.initialize(target, handler);
  3974.   }
  3975.  
  3976.   DragWrapper.prototype = {
  3977.     initialize: function(target, handler) {
  3978.       target.addEventListener('dragenter',
  3979.                               this.onDragEnter_.bind(this));
  3980.       target.addEventListener('dragover', this.onDragOver_.bind(this));
  3981.       target.addEventListener('drop', this.onDrop_.bind(this));
  3982.       target.addEventListener('dragleave', this.onDragLeave_.bind(this));
  3983.  
  3984.       this.target_ = target;
  3985.       this.handler_ = handler;
  3986.     },
  3987.  
  3988.     /**
  3989.      * The number of un-paired dragenter events that have fired on |this|. This
  3990.      * is incremented by |onDragEnter_| and decremented by |onDragLeave_|. This
  3991.      * is necessary because dragging over child widgets will fire additional
  3992.      * enter and leave events on |this|. A non-zero value does not necessarily
  3993.      * indicate that |isCurrentDragTarget()| is true.
  3994.      * @type {number}
  3995.      * @private
  3996.      */
  3997.     dragEnters_: 0,
  3998.  
  3999.     /**
  4000.      * Whether the tile page is currently being dragged over with data it can
  4001.      * accept.
  4002.      * @type {boolean}
  4003.      */
  4004.     get isCurrentDragTarget() {
  4005.       return this.target_.classList.contains('drag-target');
  4006.     },
  4007.  
  4008.     /**
  4009.      * Handler for dragenter events fired on |target_|.
  4010.      * @param {Event} e A MouseEvent for the drag.
  4011.      * @private
  4012.      */
  4013.     onDragEnter_: function(e) {
  4014.       if (++this.dragEnters_ == 1) {
  4015.         if (this.handler_.shouldAcceptDrag(e)) {
  4016.           this.target_.classList.add('drag-target');
  4017.           this.handler_.doDragEnter(e);
  4018.         }
  4019.       } else {
  4020.         // Sometimes we'll get an enter event over a child element without an
  4021.         // over event following it. In this case we have to still call the
  4022.         // drag over handler so that we make the necessary updates (one visible
  4023.         // symptom of not doing this is that the cursor's drag state will
  4024.         // flicker during drags).
  4025.         this.onDragOver_(e);
  4026.       }
  4027.     },
  4028.  
  4029.     /**
  4030.      * Thunk for dragover events fired on |target_|.
  4031.      * @param {Event} e A MouseEvent for the drag.
  4032.      * @private
  4033.      */
  4034.     onDragOver_: function(e) {
  4035.       if (!this.target_.classList.contains('drag-target'))
  4036.         return;
  4037.       this.handler_.doDragOver(e);
  4038.     },
  4039.  
  4040.     /**
  4041.      * Thunk for drop events fired on |target_|.
  4042.      * @param {Event} e A MouseEvent for the drag.
  4043.      * @private
  4044.      */
  4045.     onDrop_: function(e) {
  4046.       this.dragEnters_ = 0;
  4047.       if (!this.target_.classList.contains('drag-target'))
  4048.         return;
  4049.       this.target_.classList.remove('drag-target');
  4050.       this.handler_.doDrop(e);
  4051.     },
  4052.  
  4053.     /**
  4054.      * Thunk for dragleave events fired on |target_|.
  4055.      * @param {Event} e A MouseEvent for the drag.
  4056.      * @private
  4057.      */
  4058.     onDragLeave_: function(e) {
  4059.       if (--this.dragEnters_ > 0)
  4060.         return;
  4061.  
  4062.       this.target_.classList.remove('drag-target');
  4063.       this.handler_.doDragLeave(e);
  4064.     },
  4065.   };
  4066.  
  4067.   return {
  4068.     DragWrapper: DragWrapper
  4069.   };
  4070. });
  4071. </script>
  4072. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4073. // Use of this source code is governed by a BSD-style license that can be
  4074. // found in the LICENSE file.
  4075.  
  4076. // require: event_tracker.js
  4077.  
  4078. cr.define('cr.ui', function() {
  4079.   'use strict';
  4080.  
  4081.   /**
  4082.    * ExpandableBubble is a free-floating compact informational bubble with an
  4083.    * arrow that points at a place of interest on the page. When clicked, the
  4084.    * bubble expands to show more of its content. Width of the bubble is the
  4085.    * width of the node it is overlapping when unexpanded. Expanded, it is of a
  4086.    * fixed width, but variable height. Currently the arrow is always positioned
  4087.    * at the bottom right and points down.
  4088.    * @constructor
  4089.    * @extends {cr.ui.div}
  4090.    */
  4091.   var ExpandableBubble = cr.ui.define('div');
  4092.  
  4093.   ExpandableBubble.prototype = {
  4094.     __proto__: HTMLDivElement.prototype,
  4095.  
  4096.     /** @override */
  4097.     decorate: function() {
  4098.       this.className = 'expandable-bubble';
  4099.       this.innerHTML =
  4100.           '<div class="expandable-bubble-contents">' +
  4101.             '<div class="expandable-bubble-title"></div>' +
  4102.             '<div class="expandable-bubble-main" hidden></div>' +
  4103.           '</div>' +
  4104.           '<div class="expandable-bubble-close" hidden></div>';
  4105.  
  4106.       this.hidden = true;
  4107.       this.bubbleSuppressed = false;
  4108.       this.handleCloseEvent = this.hide;
  4109.     },
  4110.  
  4111.     /**
  4112.      * Sets the title of the bubble. The title is always visible when the
  4113.      * bubble is visible.
  4114.      * @type {Node} An HTML element to set as the title.
  4115.      */
  4116.     set contentTitle(node) {
  4117.       var bubbleTitle = this.querySelector('.expandable-bubble-title');
  4118.       bubbleTitle.textContent = '';
  4119.       bubbleTitle.appendChild(node);
  4120.     },
  4121.  
  4122.     /**
  4123.      * Sets the content node of the bubble. The content node is only visible
  4124.      * when the bubble is expanded.
  4125.      * @param {Node} An HTML element.
  4126.      */
  4127.     set content(node) {
  4128.       var bubbleMain = this.querySelector('.expandable-bubble-main');
  4129.       bubbleMain.textContent = '';
  4130.       bubbleMain.appendChild(node);
  4131.     },
  4132.  
  4133.     /**
  4134.      * Sets the anchor node, i.e. the node that this bubble points at and
  4135.      * partially overlaps.
  4136.      * @param {HTMLElement} node The new anchor node.
  4137.      */
  4138.     set anchorNode(node) {
  4139.       this.anchorNode_ = node;
  4140.  
  4141.       if (!this.hidden)
  4142.         this.resizeAndReposition();
  4143.     },
  4144.  
  4145.     /**
  4146.      * Handles the close event which is triggered when the close button
  4147.      * is clicked. By default is set to this.hide.
  4148.      * @param {function} A function with no parameters
  4149.      */
  4150.     set handleCloseEvent(func) {
  4151.       this.handleCloseEvent_ = func;
  4152.     },
  4153.  
  4154.     /**
  4155.      * Temporarily suppresses the bubble from view (and toggles it back).
  4156.      * 'Suppressed' and 'hidden' are two bubble states that both indicate that
  4157.      * the bubble should not be visible, but when you 'un-suppress' a bubble,
  4158.      * only a suppressed bubble becomes visible. This can be handy, for example,
  4159.      * if the user switches away from the app card (then we need to know which
  4160.      * bubbles to show (only the suppressed ones, not the hidden ones). Hiding
  4161.      * and un-hiding a bubble overrides the suppressed state (a bubble cannot
  4162.      * be suppressed but not hidden).
  4163.      */
  4164.     set suppressed(suppress) {
  4165.       if (suppress) {
  4166.         // If the bubble is already hidden, then we don't need to suppress it.
  4167.         if (this.hidden)
  4168.           return;
  4169.  
  4170.         this.hidden = true;
  4171.       } else if (this.bubbleSuppressed) {
  4172.         this.hidden = false;
  4173.       }
  4174.       this.bubbleSuppressed = suppress;
  4175.       this.resizeAndReposition(this);
  4176.     },
  4177.  
  4178.     /**
  4179.      * Updates the position of the bubble.
  4180.      * @private
  4181.      */
  4182.     reposition_: function() {
  4183.       var clientRect = this.anchorNode_.getBoundingClientRect();
  4184.  
  4185.       // Center bubble in collapsed mode (if it doesn't take up all the room we
  4186.       // have).
  4187.       var offset = 0;
  4188.       if (!this.expanded)
  4189.         offset = (clientRect.width - parseInt(this.style.width)) / 2;
  4190.       this.style.left = this.style.right = clientRect.left + offset + 'px';
  4191.  
  4192.       var top = Math.max(0, clientRect.top - 4);
  4193.       this.style.top = this.expanded ?
  4194.           (top - this.offsetHeight + this.unexpandedHeight) + 'px' :
  4195.           top + 'px';
  4196.     },
  4197.  
  4198.     /**
  4199.      * Resizes the bubble and then repositions it.
  4200.      * @private
  4201.      */
  4202.     resizeAndReposition: function() {
  4203.       var clientRect = this.anchorNode_.getBoundingClientRect();
  4204.       var width = clientRect.width;
  4205.  
  4206.       var bubbleTitle = this.querySelector('.expandable-bubble-title');
  4207.       var closeElement = this.querySelector('.expandable-bubble-close');
  4208.       var closeWidth = this.expanded ? closeElement.clientWidth : 0;
  4209.       var margin = 15;
  4210.  
  4211.       // Suppress the width style so we can get it to calculate its width.
  4212.       // We'll set the right width again when we are done.
  4213.       bubbleTitle.style.width = '';
  4214.  
  4215.       if (this.expanded) {
  4216.         // We always show the full title but never show less width than 250
  4217.         // pixels.
  4218.         var expandedWidth =
  4219.             Math.max(250, bubbleTitle.scrollWidth + closeWidth + margin);
  4220.         this.style.marginLeft = (width - expandedWidth) + 'px';
  4221.         width = expandedWidth;
  4222.       } else {
  4223.         var newWidth = Math.min(bubbleTitle.scrollWidth + margin, width);
  4224.         // If we've maxed out in width then apply the mask.
  4225.         this.masked = newWidth == width;
  4226.         width = newWidth;
  4227.         this.style.marginLeft = '0';
  4228.       }
  4229.  
  4230.       // Width is determined by the width of the title (when not expanded) but
  4231.       // capped to the width of the anchor node.
  4232.       this.style.width = width + 'px';
  4233.       bubbleTitle.style.width = Math.max(0, width - margin - closeWidth) + 'px';
  4234.  
  4235.       // Also reposition the bubble -- dimensions have potentially changed.
  4236.       this.reposition_();
  4237.     },
  4238.  
  4239.     /*
  4240.      * Expand the bubble (bringing the full content into view).
  4241.      * @private
  4242.      */
  4243.     expandBubble_: function() {
  4244.       this.querySelector('.expandable-bubble-main').hidden = false;
  4245.       this.querySelector('.expandable-bubble-close').hidden = false;
  4246.       this.expanded = true;
  4247.       this.resizeAndReposition();
  4248.     },
  4249.  
  4250.     /**
  4251.      * Collapse the bubble, hiding the main content and the close button.
  4252.      * This is automatically called when the window is resized.
  4253.      * @private
  4254.      */
  4255.     collapseBubble_: function() {
  4256.       this.querySelector('.expandable-bubble-main').hidden = true;
  4257.       this.querySelector('.expandable-bubble-close').hidden = true;
  4258.       this.expanded = false;
  4259.       this.resizeAndReposition();
  4260.     },
  4261.  
  4262.     /**
  4263.      * The onclick handler for the notification (expands the bubble).
  4264.      * @param {Event} e The event.
  4265.      * @private
  4266.      */
  4267.     onNotificationClick_: function(e) {
  4268.       if (!this.contains(e.target))
  4269.         return;
  4270.  
  4271.       if (!this.expanded) {
  4272.         // Save the height of the unexpanded bubble, so we can make sure to
  4273.         // position it correctly (arrow points in the same location) after
  4274.         // we expand it.
  4275.         this.unexpandedHeight = this.offsetHeight;
  4276.       }
  4277.  
  4278.       this.expandBubble_();
  4279.     },
  4280.  
  4281.     /**
  4282.      * Shows the bubble. The bubble will start collapsed and expand when
  4283.      * clicked.
  4284.      */
  4285.     show: function() {
  4286.       if (!this.hidden)
  4287.         return;
  4288.  
  4289.       document.body.appendChild(this);
  4290.       this.hidden = false;
  4291.       this.resizeAndReposition();
  4292.  
  4293.       this.eventTracker_ = new EventTracker;
  4294.       this.eventTracker_.add(window,
  4295.                              'load', this.resizeAndReposition.bind(this));
  4296.       this.eventTracker_.add(window,
  4297.                              'resize', this.resizeAndReposition.bind(this));
  4298.       this.eventTracker_.add(this, 'click', this.onNotificationClick_);
  4299.  
  4300.       var doc = this.ownerDocument;
  4301.       this.eventTracker_.add(doc, 'keydown', this, true);
  4302.       this.eventTracker_.add(doc, 'mousedown', this, true);
  4303.     },
  4304.  
  4305.     /**
  4306.      * Hides the bubble from view.
  4307.      */
  4308.     hide: function() {
  4309.       this.hidden = true;
  4310.       this.bubbleSuppressed = false;
  4311.       this.eventTracker_.removeAll();
  4312.       this.parentNode.removeChild(this);
  4313.     },
  4314.  
  4315.     /**
  4316.      * Handles keydown and mousedown events, dismissing the bubble if
  4317.      * necessary.
  4318.      * @param {Event} e The event.
  4319.      * @private
  4320.      */
  4321.     handleEvent: function(e) {
  4322.       var handled = false;
  4323.       switch (e.type) {
  4324.         case 'keydown':
  4325.           if (e.keyCode == 27) {  // Esc.
  4326.             if (this.expanded) {
  4327.               this.collapseBubble_();
  4328.               handled = true;
  4329.             }
  4330.           }
  4331.           break;
  4332.  
  4333.         case 'mousedown':
  4334.           if (e.target == this.querySelector('.expandable-bubble-close')) {
  4335.             this.handleCloseEvent_();
  4336.             handled = true;
  4337.           } else if (!this.contains(e.target)) {
  4338.             if (this.expanded) {
  4339.               this.collapseBubble_();
  4340.               handled = true;
  4341.             }
  4342.           }
  4343.           break;
  4344.       }
  4345.  
  4346.       if (handled) {
  4347.         // The bubble emulates a focus grab when expanded, so when we've
  4348.         // collapsed/hide the bubble we consider the event handles and don't
  4349.         // need to propagate it further.
  4350.         e.stopPropagation();
  4351.         e.preventDefault();
  4352.       }
  4353.     },
  4354.   };
  4355.  
  4356.   /**
  4357.    * Whether the bubble is expanded or not.
  4358.    * @type {boolean}
  4359.    */
  4360.   cr.defineProperty(ExpandableBubble, 'expanded', cr.PropertyKind.BOOL_ATTR);
  4361.  
  4362.   /**
  4363.    * Whether the title needs to be masked out towards the right, which indicates
  4364.    * to the user that part of the text is clipped. This is only used when the
  4365.    * bubble is collapsed and the title doesn't fit because it is maxed out in
  4366.    * width within the anchored node.
  4367.    * @type {boolean}
  4368.    */
  4369.   cr.defineProperty(ExpandableBubble, 'masked', cr.PropertyKind.BOOL_ATTR);
  4370.  
  4371.   return {
  4372.     ExpandableBubble: ExpandableBubble
  4373.   };
  4374. });
  4375. </script>
  4376. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4377. // Use of this source code is governed by a BSD-style license that can be
  4378. // found in the LICENSE file.
  4379.  
  4380. cr.define('cr.ui', function() {
  4381.  
  4382.   /** @const */ var MenuItem = cr.ui.MenuItem;
  4383.  
  4384.   /**
  4385.    * Creates a new menu element. Menu dispatches all commands on the element it
  4386.    * was shown for.
  4387.    *
  4388.    * @param {Object=} opt_propertyBag Optional properties.
  4389.    * @constructor
  4390.    * @extends {HTMLMenuElement}
  4391.    */
  4392.   var Menu = cr.ui.define('menu');
  4393.  
  4394.   Menu.prototype = {
  4395.     __proto__: HTMLMenuElement.prototype,
  4396.  
  4397.     selectedIndex_: -1,
  4398.  
  4399.     /**
  4400.      * Element for which menu is being shown.
  4401.      */
  4402.     contextElement: null,
  4403.  
  4404.     /**
  4405.      * Initializes the menu element.
  4406.      */
  4407.     decorate: function() {
  4408.       this.addEventListener('mouseover', this.handleMouseOver_);
  4409.       this.addEventListener('mouseout', this.handleMouseOut_);
  4410.  
  4411.       this.classList.add('decorated');
  4412.       this.setAttribute('role', 'menu');
  4413.       this.hidden = true;  // Hide the menu by default.
  4414.  
  4415.       // Decorate the children as menu items.
  4416.       var children = this.children;
  4417.       for (var i = 0, child; child = children[i]; i++) {
  4418.         cr.ui.decorate(child, MenuItem);
  4419.       }
  4420.     },
  4421.  
  4422.     /**
  4423.      * Adds menu item at the end of the list.
  4424.      * @param {Object} item Menu item properties.
  4425.      * @return {cr.ui.MenuItem} The created menu item.
  4426.      */
  4427.     addMenuItem: function(item) {
  4428.       var menuItem = this.ownerDocument.createElement('menuitem');
  4429.       this.appendChild(menuItem);
  4430.  
  4431.       cr.ui.decorate(menuItem, MenuItem);
  4432.  
  4433.       if (item.label)
  4434.         menuItem.label = item.label;
  4435.  
  4436.       if (item.iconUrl)
  4437.         menuItem.iconUrl = item.iconUrl;
  4438.  
  4439.       return menuItem;
  4440.     },
  4441.  
  4442.     /**
  4443.      * Adds separator at the end of the list.
  4444.      */
  4445.     addSeparator: function() {
  4446.       var separator = this.ownerDocument.createElement('hr');
  4447.       this.appendChild(separator);
  4448.     },
  4449.  
  4450.     /**
  4451.      * Clears menu.
  4452.      */
  4453.     clear: function() {
  4454.       this.textContent = '';
  4455.     },
  4456.  
  4457.     /**
  4458.      * Walks up the ancestors of |el| until a menu item belonging to this menu
  4459.      * is found.
  4460.      * @param {Element} el The element to start searching from.
  4461.      * @return {cr.ui.MenuItem} The found menu item or null.
  4462.      * @private
  4463.      */
  4464.     findMenuItem_: function(el) {
  4465.       while (el && el.parentNode != this) {
  4466.         el = el.parentNode;
  4467.       }
  4468.       return el;
  4469.     },
  4470.  
  4471.     /**
  4472.      * Handles mouseover events and selects the hovered item.
  4473.      * @param {Event} e The mouseover event.
  4474.      * @private
  4475.      */
  4476.     handleMouseOver_: function(e) {
  4477.       var overItem = this.findMenuItem_(e.target);
  4478.       this.selectedItem = overItem;
  4479.     },
  4480.  
  4481.     /**
  4482.      * Handles mouseout events and deselects any selected item.
  4483.      * @param {Event} e The mouseout event.
  4484.      * @private
  4485.      */
  4486.     handleMouseOut_: function(e) {
  4487.       this.selectedItem = null;
  4488.     },
  4489.  
  4490.     /**
  4491.      * The selected menu item or null if none.
  4492.      * @type {cr.ui.MenuItem}
  4493.      */
  4494.     get selectedItem() {
  4495.       return this.children[this.selectedIndex];
  4496.     },
  4497.     set selectedItem(item) {
  4498.       var index = Array.prototype.indexOf.call(this.children, item);
  4499.       this.selectedIndex = index;
  4500.     },
  4501.  
  4502.     /**
  4503.      * Focuses the selected item. If selectedIndex is invalid, set it to 0
  4504.      * first.
  4505.      */
  4506.     focusSelectedItem: function() {
  4507.       if (this.selectedIndex < 0 ||
  4508.           this.selectedIndex > this.children.length) {
  4509.         this.selectedIndex = 0;
  4510.       }
  4511.  
  4512.       if (this.selectedItem)
  4513.         this.selectedItem.focus();
  4514.     },
  4515.  
  4516.     /**
  4517.      * Menu length
  4518.      */
  4519.     get length() {
  4520.       return this.children.length;
  4521.     },
  4522.  
  4523.     /**
  4524.      * This is the function that handles keyboard navigation. This is usually
  4525.      * called by the element responsible for managing the menu.
  4526.      * @param {Event} e The keydown event object.
  4527.      * @return {boolean} Whether the event was handled be the menu.
  4528.      */
  4529.     handleKeyDown: function(e) {
  4530.       var item = this.selectedItem;
  4531.  
  4532.       var self = this;
  4533.       function selectNextAvailable(m) {
  4534.         var children = self.children;
  4535.         var len = children.length;
  4536.         var i = self.selectedIndex;
  4537.         if (i == -1 && m == -1) {
  4538.           // Edge case when needed to go the last item first.
  4539.           i = 0;
  4540.         }
  4541.  
  4542.         // "i" may be negative(-1), so modulus operation and cycle below
  4543.         // wouldn't work as assumed. This trick makes startPosition positive
  4544.         // without altering it's modulo.
  4545.         var startPosition = (i + len) % len;
  4546.  
  4547.         while (true) {
  4548.           i = (i + m + len) % len;
  4549.  
  4550.           // Check not to enter into infinite loop if all items are hidden or
  4551.           // disabled.
  4552.           if (i == startPosition)
  4553.             break;
  4554.  
  4555.           item = children[i];
  4556.           if (item && !item.isSeparator() && !item.hidden && !item.disabled)
  4557.             break;
  4558.         }
  4559.         if (item && !item.disabled)
  4560.           self.selectedIndex = i;
  4561.       }
  4562.  
  4563.       switch (e.keyIdentifier) {
  4564.         case 'Down':
  4565.           selectNextAvailable(1);
  4566.           this.focusSelectedItem();
  4567.           return true;
  4568.         case 'Up':
  4569.           selectNextAvailable(-1);
  4570.           this.focusSelectedItem();
  4571.           return true;
  4572.         case 'Enter':
  4573.         case 'U+0020': // Space
  4574.           if (item) {
  4575.             var activationEvent = cr.doc.createEvent('Event');
  4576.             activationEvent.initEvent('activate', true, true);
  4577.             activationEvent.originalEvent = e;
  4578.             if (item.dispatchEvent(activationEvent)) {
  4579.               if (item.command)
  4580.                 item.command.execute();
  4581.             }
  4582.           }
  4583.           return true;
  4584.       }
  4585.  
  4586.       return false;
  4587.     },
  4588.  
  4589.     /**
  4590.      * Updates menu items command according to context.
  4591.      * @param {Node=} node Node for which to actuate commands state.
  4592.      */
  4593.     updateCommands: function(node) {
  4594.       var children = this.children;
  4595.  
  4596.       for (var i = 0, child; child = children[i]; i++)
  4597.         child.updateCommand(node);
  4598.     }
  4599.   };
  4600.  
  4601.   function selectedIndexChanged(selectedIndex, oldSelectedIndex) {
  4602.     var oldSelectedItem = this.children[oldSelectedIndex];
  4603.     if (oldSelectedItem) {
  4604.       oldSelectedItem.selected = false;
  4605.       oldSelectedItem.blur();
  4606.     }
  4607.     var item = this.selectedItem;
  4608.     if (item)
  4609.       item.selected = true;
  4610.   }
  4611.  
  4612.   /**
  4613.    * The selected menu item.
  4614.    * @type {number}
  4615.    */
  4616.   cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS,
  4617.       selectedIndexChanged);
  4618.  
  4619.   // Export
  4620.   return {
  4621.     Menu: Menu
  4622.   };
  4623. });
  4624. </script>
  4625. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4626. // Use of this source code is governed by a BSD-style license that can be
  4627. // found in the LICENSE file.
  4628.  
  4629. cr.define('cr.ui', function() {
  4630.   /** @const */ var Command = cr.ui.Command;
  4631.  
  4632.   /**
  4633.    * Creates a new menu item element.
  4634.    * @param {Object=} opt_propertyBag Optional properties.
  4635.    * @constructor
  4636.    * @extends {HTMLDivElement}
  4637.    */
  4638.   var MenuItem = cr.ui.define('div');
  4639.  
  4640.   /**
  4641.    * Creates a new menu separator element.
  4642.    * @return {cr.ui.MenuItem} The new separator element.
  4643.    */
  4644.   MenuItem.createSeparator = function() {
  4645.     var el = cr.doc.createElement('hr');
  4646.     MenuItem.decorate(el);
  4647.     return el;
  4648.   };
  4649.  
  4650.   MenuItem.prototype = {
  4651.     __proto__: HTMLButtonElement.prototype,
  4652.  
  4653.     /**
  4654.      * Initializes the menu item.
  4655.      */
  4656.     decorate: function() {
  4657.       var commandId;
  4658.       if ((commandId = this.getAttribute('command')))
  4659.         this.command = commandId;
  4660.  
  4661.       this.addEventListener('mouseup', this.handleMouseUp_);
  4662.  
  4663.       // Adding the 'custom-appearance' class prevents widgets.css from changing
  4664.       // the appearance of this element.
  4665.       this.classList.add('custom-appearance');
  4666.  
  4667.       this.setAttribute('role', 'menuitem');
  4668.  
  4669.       var iconUrl;
  4670.       if ((iconUrl = this.getAttribute('icon')))
  4671.         this.iconUrl = iconUrl;
  4672.     },
  4673.  
  4674.     /**
  4675.      * The command associated with this menu item. If this is set to a string
  4676.      * of the form "#element-id" then the element is looked up in the document
  4677.      * of the command.
  4678.      * @type {cr.ui.Command}
  4679.      */
  4680.     command_: null,
  4681.     get command() {
  4682.       return this.command_;
  4683.     },
  4684.     set command(command) {
  4685.       if (this.command_) {
  4686.         this.command_.removeEventListener('labelChange', this);
  4687.         this.command_.removeEventListener('disabledChange', this);
  4688.         this.command_.removeEventListener('hiddenChange', this);
  4689.         this.command_.removeEventListener('checkedChange', this);
  4690.       }
  4691.  
  4692.       if (typeof command == 'string' && command[0] == '#') {
  4693.         command = this.ownerDocument.getElementById(command.slice(1));
  4694.         cr.ui.decorate(command, Command);
  4695.       }
  4696.  
  4697.       this.command_ = command;
  4698.       if (command) {
  4699.         if (command.id)
  4700.           this.setAttribute('command', '#' + command.id);
  4701.  
  4702.         this.label = command.label;
  4703.         this.disabled = command.disabled;
  4704.         this.hidden = command.hidden;
  4705.  
  4706.         this.command_.addEventListener('labelChange', this);
  4707.         this.command_.addEventListener('disabledChange', this);
  4708.         this.command_.addEventListener('hiddenChange', this);
  4709.         this.command_.addEventListener('checkedChange', this);
  4710.       }
  4711.  
  4712.       this.updateShortcut_();
  4713.     },
  4714.  
  4715.     /**
  4716.      * The text label.
  4717.      * @type {string}
  4718.      */
  4719.     get label() {
  4720.       return this.textContent;
  4721.     },
  4722.     set label(label) {
  4723.       this.textContent = label;
  4724.     },
  4725.  
  4726.     /**
  4727.      * Menu icon.
  4728.      * @type {string}
  4729.      */
  4730.     get iconUrl() {
  4731.       return this.style.backgroundImage;
  4732.     },
  4733.     set iconUrl(url) {
  4734.       this.style.backgroundImage = 'url(' + url + ')';
  4735.     },
  4736.  
  4737.     /**
  4738.      * @return {boolean} Whether the menu item is a separator.
  4739.      */
  4740.     isSeparator: function() {
  4741.       return this.tagName == 'HR';
  4742.     },
  4743.  
  4744.     /**
  4745.      * Updates shortcut text according to associated command. If command has
  4746.      * multiple shortcuts, only first one is displayed.
  4747.      */
  4748.     updateShortcut_: function() {
  4749.       this.removeAttribute('shortcutText');
  4750.  
  4751.       if (!(this.command_ && this.command_.shortcut))
  4752.         return;
  4753.  
  4754.       var shortcuts = this.command_.shortcut.split(/\s+/);
  4755.  
  4756.       if (shortcuts.length == 0)
  4757.         return;
  4758.  
  4759.       var shortcut = shortcuts[0];
  4760.       var mods = {};
  4761.       var ident = '';
  4762.       shortcut.split('-').forEach(function(part) {
  4763.         var partUc = part.toUpperCase();
  4764.         switch (partUc) {
  4765.           case 'CTRL':
  4766.           case 'ALT':
  4767.           case 'SHIFT':
  4768.           case 'META':
  4769.             mods[partUc] = true;
  4770.             break;
  4771.           default:
  4772.             console.assert(!ident, 'Shortcut has two non-modifier keys');
  4773.             ident = part;
  4774.         }
  4775.       });
  4776.  
  4777.       var shortcutText = '';
  4778.  
  4779.       // TODO(zvorygin): if more cornercases appear - optimize following
  4780.       // code. Currently 'Enter' keystroke is passed as 'Enter', and 'Space'
  4781.       // is passed as 'U+0020'
  4782.       if (ident == 'U+0020')
  4783.         ident = 'Space';
  4784.  
  4785.       ['CTRL', 'ALT', 'SHIFT', 'META'].forEach(function(mod) {
  4786.         if (mods[mod])
  4787.           shortcutText += loadTimeData.getString('SHORTCUT_' + mod) + '+';
  4788.       });
  4789.  
  4790.       if (ident.indexOf('U+') != 0) {
  4791.         shortcutText +=
  4792.             loadTimeData.getString('SHORTCUT_' + ident.toUpperCase());
  4793.       } else {
  4794.         shortcutText +=
  4795.             String.fromCharCode(parseInt(ident.substring(2), 16));
  4796.       }
  4797.  
  4798.       this.setAttribute('shortcutText', shortcutText);
  4799.     },
  4800.  
  4801.     /**
  4802.      * Handles mouseup events. This dispatches an activate event; if there is an
  4803.      * associated command, that command is executed.
  4804.      * @param {Event} e The mouseup event object.
  4805.      * @private
  4806.      */
  4807.     handleMouseUp_: function(e) {
  4808.       if (!this.disabled && !this.isSeparator() && this.selected) {
  4809.         // Store |contextElement| since it'll be removed by {Menu} on handling
  4810.         // 'activate' event.
  4811.         var contextElement = this.parentNode.contextElement;
  4812.         var activationEvent = cr.doc.createEvent('Event');
  4813.         activationEvent.initEvent('activate', true, true);
  4814.         activationEvent.originalEvent = e;
  4815.         // Dispatch command event followed by executing the command object.
  4816.         if (this.dispatchEvent(activationEvent)) {
  4817.           var command = this.command;
  4818.           if (command) {
  4819.             command.execute(contextElement);
  4820.             cr.ui.swallowDoubleClick(e);
  4821.           }
  4822.         }
  4823.       }
  4824.     },
  4825.  
  4826.     /**
  4827.      * Updates command according to the node on which this menu was invoked.
  4828.      * @param {Node=} opt_node Node on which menu was opened.
  4829.      */
  4830.     updateCommand: function(opt_node) {
  4831.       if (this.command_) {
  4832.         this.command_.canExecuteChange(opt_node);
  4833.       }
  4834.     },
  4835.  
  4836.     /**
  4837.      * Handles changes to the associated command.
  4838.      * @param {Event} e The event object.
  4839.      */
  4840.     handleEvent: function(e) {
  4841.       switch (e.type) {
  4842.         case 'disabledChange':
  4843.           this.disabled = this.command.disabled;
  4844.           break;
  4845.         case 'hiddenChange':
  4846.           this.hidden = this.command.hidden;
  4847.           break;
  4848.         case 'labelChange':
  4849.           this.label = this.command.label;
  4850.           break;
  4851.         case 'checkedChange':
  4852.           this.checked = this.command.checked;
  4853.           break;
  4854.       }
  4855.     }
  4856.   };
  4857.  
  4858.   /**
  4859.    * Whether the menu item is disabled or not.
  4860.    * @type {boolean}
  4861.    */
  4862.   cr.defineProperty(MenuItem, 'disabled', cr.PropertyKind.BOOL_ATTR);
  4863.  
  4864.   /**
  4865.    * Whether the menu item is hidden or not.
  4866.    * @type {boolean}
  4867.    */
  4868.   cr.defineProperty(MenuItem, 'hidden', cr.PropertyKind.BOOL_ATTR);
  4869.  
  4870.   /**
  4871.    * Whether the menu item is selected or not.
  4872.    * @type {boolean}
  4873.    */
  4874.   cr.defineProperty(MenuItem, 'selected', cr.PropertyKind.BOOL_ATTR);
  4875.  
  4876.   /**
  4877.    * Whether the menu item is checked or not.
  4878.    * @type {boolean}
  4879.    */
  4880.   cr.defineProperty(MenuItem, 'checked', cr.PropertyKind.BOOL_ATTR);
  4881.  
  4882.   // Export
  4883.   return {
  4884.     MenuItem: MenuItem
  4885.   };
  4886. });
  4887. </script>
  4888. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4889. // Use of this source code is governed by a BSD-style license that can be
  4890. // found in the LICENSE file.
  4891.  
  4892. /**
  4893.  * @fileoverview This file provides utility functions for position popups.
  4894.  */
  4895.  
  4896. cr.define('cr.ui', function() {
  4897.  
  4898.   /**
  4899.    * Type def for rects as returned by getBoundingClientRect.
  4900.    * @typedef { {left: number, top: number, width: number, height: number,
  4901.    *             right: number, bottom: number}}
  4902.    */
  4903.   var Rect;
  4904.  
  4905.   /**
  4906.    * Enum for defining how to anchor a popup to an anchor element.
  4907.    * @enum {number}
  4908.    */
  4909.   var AnchorType = {
  4910.     /**
  4911.      * The popup's right edge is aligned with the left edge of the anchor.
  4912.      * The popup's top edge is aligned with the top edge of the anchor.
  4913.      */
  4914.     BEFORE: 1,  // p: right, a: left, p: top, a: top
  4915.  
  4916.     /**
  4917.      * The popop's left edge is aligned with the right edge of the anchor.
  4918.      * The popup's top edge is aligned with the top edge of the anchor.
  4919.      */
  4920.     AFTER: 2,  // p: left a: right, p: top, a: top
  4921.  
  4922.     /**
  4923.      * The popop's bottom edge is aligned with the top edge of the anchor.
  4924.      * The popup's left edge is aligned with the left edge of the anchor.
  4925.      */
  4926.     ABOVE: 3,  // p: bottom, a: top, p: left, a: left
  4927.  
  4928.     /**
  4929.      * The popop's top edge is aligned with the bottom edge of the anchor.
  4930.      * The popup's left edge is aligned with the left edge of the anchor.
  4931.      */
  4932.     BELOW: 4  // p: top, a: bottom, p: left, a: left
  4933.   };
  4934.  
  4935.   /**
  4936.    * Helper function for positionPopupAroundElement and positionPopupAroundRect.
  4937.    * @param {!Rect} anchorRect The rect for the anchor.
  4938.    * @param {!HTMLElement} popupElement The element used for the popup.
  4939.    * @param {AnchorType} type The type of anchoring to do.
  4940.    * @param {boolean} invertLeftRight Whether to invert the right/left
  4941.    *     alignment.
  4942.    */
  4943.   function positionPopupAroundRect(anchorRect, popupElement, type,
  4944.                                    invertLeftRight) {
  4945.     var popupRect = popupElement.getBoundingClientRect();
  4946.     var availRect;
  4947.     var ownerDoc = popupElement.ownerDocument;
  4948.     var cs = ownerDoc.defaultView.getComputedStyle(popupElement);
  4949.     var docElement = ownerDoc.documentElement;
  4950.  
  4951.     if (cs.position == 'fixed') {
  4952.       // For 'fixed' positioned popups, the available rectangle should be based
  4953.       // on the viewport rather than the document.
  4954.       availRect = {
  4955.         height: docElement.clientHeight,
  4956.         width: docElement.clientWidth,
  4957.         top: 0,
  4958.         bottom: docElement.clientHeight,
  4959.         left: 0,
  4960.         right: docElement.clientWidth
  4961.       };
  4962.     } else {
  4963.       availRect = popupElement.offsetParent.getBoundingClientRect();
  4964.     }
  4965.  
  4966.     if (cs.direction == 'rtl')
  4967.       invertLeftRight = !invertLeftRight;
  4968.  
  4969.     // Flip BEFORE, AFTER based on alignment.
  4970.     if (invertLeftRight) {
  4971.       if (type == AnchorType.BEFORE)
  4972.         type = AnchorType.AFTER;
  4973.       else if (type == AnchorType.AFTER)
  4974.         type = AnchorType.BEFORE;
  4975.     }
  4976.  
  4977.     // Flip type based on available size
  4978.     switch (type) {
  4979.       case AnchorType.BELOW:
  4980.         if (anchorRect.bottom + popupRect.height > availRect.height &&
  4981.             popupRect.height <= anchorRect.top) {
  4982.           type = AnchorType.ABOVE;
  4983.         }
  4984.         break;
  4985.       case AnchorType.ABOVE:
  4986.         if (popupRect.height > anchorRect.top &&
  4987.             anchorRect.bottom + popupRect.height <= availRect.height) {
  4988.           type = AnchorType.BELOW;
  4989.         }
  4990.         break;
  4991.       case AnchorType.AFTER:
  4992.         if (anchorRect.right + popupRect.width > availRect.width &&
  4993.             popupRect.width <= anchorRect.left) {
  4994.           type = AnchorType.BEFORE;
  4995.         }
  4996.         break;
  4997.       case AnchorType.BEFORE:
  4998.         if (popupRect.width > anchorRect.left &&
  4999.             anchorRect.right + popupRect.width <= availRect.width) {
  5000.           type = AnchorType.AFTER;
  5001.         }
  5002.         break;
  5003.     }
  5004.     // flipping done
  5005.  
  5006.     var style = popupElement.style;
  5007.     // Reset all directions.
  5008.     style.left = style.right = style.top = style.bottom = 'auto';
  5009.  
  5010.     // Primary direction
  5011.     switch (type) {
  5012.       case AnchorType.BELOW:
  5013.         if (anchorRect.bottom + popupRect.height <= availRect.height)
  5014.           style.top = anchorRect.bottom + 'px';
  5015.         else
  5016.           style.bottom = '0';
  5017.         break;
  5018.       case AnchorType.ABOVE:
  5019.         if (availRect.height - anchorRect.top >= 0)
  5020.           style.bottom = availRect.height - anchorRect.top + 'px';
  5021.         else
  5022.           style.top = '0';
  5023.         break;
  5024.       case AnchorType.AFTER:
  5025.         if (anchorRect.right + popupRect.width <= availRect.width)
  5026.           style.left = anchorRect.right + 'px';
  5027.         else
  5028.           style.right = '0';
  5029.         break;
  5030.       case AnchorType.BEFORE:
  5031.         if (availRect.width - anchorRect.left >= 0)
  5032.           style.right = availRect.width - anchorRect.left + 'px';
  5033.         else
  5034.           style.left = '0';
  5035.         break;
  5036.     }
  5037.  
  5038.     // Secondary direction
  5039.     switch (type) {
  5040.       case AnchorType.BELOW:
  5041.       case AnchorType.ABOVE:
  5042.         if (invertLeftRight) {
  5043.           // align right edges
  5044.           if (anchorRect.right - popupRect.width >= 0) {
  5045.             style.right = availRect.width - anchorRect.right + 'px';
  5046.  
  5047.           // align left edges
  5048.           } else if (anchorRect.left + popupRect.width <= availRect.width) {
  5049.             style.left = anchorRect.left + 'px';
  5050.  
  5051.           // not enough room on either side
  5052.           } else {
  5053.             style.right = '0';
  5054.           }
  5055.         } else {
  5056.           // align left edges
  5057.           if (anchorRect.left + popupRect.width <= availRect.width) {
  5058.             style.left = anchorRect.left + 'px';
  5059.  
  5060.           // align right edges
  5061.           } else if (anchorRect.right - popupRect.width >= 0) {
  5062.             style.right = availRect.width - anchorRect.right + 'px';
  5063.  
  5064.           // not enough room on either side
  5065.           } else {
  5066.             style.left = '0';
  5067.           }
  5068.         }
  5069.         break;
  5070.  
  5071.       case AnchorType.AFTER:
  5072.       case AnchorType.BEFORE:
  5073.         // align top edges
  5074.         if (anchorRect.top + popupRect.height <= availRect.height) {
  5075.           style.top = anchorRect.top + 'px';
  5076.  
  5077.         // align bottom edges
  5078.         } else if (anchorRect.bottom - popupRect.height >= 0) {
  5079.           style.bottom = availRect.height - anchorRect.bottom + 'px';
  5080.  
  5081.           // not enough room on either side
  5082.         } else {
  5083.           style.top = '0';
  5084.         }
  5085.         break;
  5086.     }
  5087.   }
  5088.  
  5089.   /**
  5090.    * Positions a popup element relative to an anchor element. The popup element
  5091.    * should have position set to absolute and it should be a child of the body
  5092.    * element.
  5093.    * @param {!HTMLElement} anchorElement The element that the popup is anchored
  5094.    *     to.
  5095.    * @param {!HTMLElement} popupElement The popup element we are positioning.
  5096.    * @param {AnchorType} type The type of anchoring we want.
  5097.    * @param {boolean} invertLeftRight Whether to invert the right/left
  5098.    *     alignment.
  5099.    */
  5100.   function positionPopupAroundElement(anchorElement, popupElement, type,
  5101.                                       invertLeftRight) {
  5102.     var anchorRect = anchorElement.getBoundingClientRect();
  5103.     positionPopupAroundRect(anchorRect, popupElement, type, invertLeftRight);
  5104.   }
  5105.  
  5106.   /**
  5107.    * Positions a popup around a point.
  5108.    * @param {number} x The client x position.
  5109.    * @param {number} y The client y position.
  5110.    * @param {!HTMLElement} popupElement The popup element we are positioning.
  5111.    */
  5112.   function positionPopupAtPoint(x, y, popupElement) {
  5113.     var rect = {
  5114.       left: x,
  5115.       top: y,
  5116.       width: 0,
  5117.       height: 0,
  5118.       right: x,
  5119.       bottom: y
  5120.     };
  5121.     positionPopupAroundRect(rect, popupElement, AnchorType.BELOW);
  5122.   }
  5123.  
  5124.   // Export
  5125.   return {
  5126.     AnchorType: AnchorType,
  5127.     positionPopupAroundElement: positionPopupAroundElement,
  5128.     positionPopupAtPoint: positionPopupAtPoint
  5129.   };
  5130. });
  5131. </script>
  5132. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5133. // Use of this source code is governed by a BSD-style license that can be
  5134. // found in the LICENSE file.
  5135.  
  5136. cr.define('cr.ui', function() {
  5137.   /** @const */
  5138.   var Menu = cr.ui.Menu;
  5139.   /** @const */
  5140.   var positionPopupAroundElement = cr.ui.positionPopupAroundElement;
  5141.  
  5142.   /**
  5143.    * Creates a new menu button element.
  5144.    * @param {Object=} opt_propertyBag Optional properties.
  5145.    * @constructor
  5146.    * @extends {HTMLButtonElement}
  5147.    */
  5148.   var MenuButton = cr.ui.define('button');
  5149.  
  5150.   MenuButton.prototype = {
  5151.     __proto__: HTMLButtonElement.prototype,
  5152.  
  5153.     /**
  5154.      * Initializes the menu button.
  5155.      */
  5156.     decorate: function() {
  5157.       this.addEventListener('mousedown', this);
  5158.       this.addEventListener('keydown', this);
  5159.  
  5160.       // Adding the 'custom-appearance' class prevents widgets.css from changing
  5161.       // the appearance of this element.
  5162.       this.classList.add('custom-appearance');
  5163.       this.classList.add('menu-button');  // For styles in menu_button.css.
  5164.  
  5165.       var menu;
  5166.       if ((menu = this.getAttribute('menu')))
  5167.         this.menu = menu;
  5168.  
  5169.       // An event tracker for events we only connect to while the menu is
  5170.       // displayed.
  5171.       this.showingEvents_ = new EventTracker();
  5172.  
  5173.       this.anchorType = cr.ui.AnchorType.BELOW;
  5174.       this.invertLeftRight = false;
  5175.     },
  5176.  
  5177.     /**
  5178.      * The menu associated with the menu button.
  5179.      * @type {cr.ui.Menu}
  5180.      */
  5181.     get menu() {
  5182.       return this.menu_;
  5183.     },
  5184.     set menu(menu) {
  5185.       if (typeof menu == 'string' && menu[0] == '#') {
  5186.         menu = this.ownerDocument.getElementById(menu.slice(1));
  5187.         cr.ui.decorate(menu, Menu);
  5188.       }
  5189.  
  5190.       this.menu_ = menu;
  5191.       if (menu) {
  5192.         if (menu.id)
  5193.           this.setAttribute('menu', '#' + menu.id);
  5194.       }
  5195.     },
  5196.  
  5197.     /**
  5198.      * Handles event callbacks.
  5199.      * @param {Event} e The event object.
  5200.      */
  5201.     handleEvent: function(e) {
  5202.       if (!this.menu)
  5203.         return;
  5204.  
  5205.       switch (e.type) {
  5206.         case 'mousedown':
  5207.           if (e.currentTarget == this.ownerDocument) {
  5208.             if (!this.contains(e.target) && !this.menu.contains(e.target))
  5209.               this.hideMenu();
  5210.             else
  5211.               e.preventDefault();
  5212.           } else {
  5213.             if (this.isMenuShown()) {
  5214.               this.hideMenu();
  5215.             } else if (e.button == 0) {  // Only show the menu when using left
  5216.                                          // mouse button.
  5217.               this.showMenu(false);
  5218.               // Prevent the button from stealing focus on mousedown.
  5219.               e.preventDefault();
  5220.             }
  5221.           }
  5222.           break;
  5223.         case 'keydown':
  5224.           this.handleKeyDown(e);
  5225.           // If the menu is visible we let it handle all the keyboard events.
  5226.           if (this.isMenuShown() && e.currentTarget == this.ownerDocument) {
  5227.             if (this.menu.handleKeyDown(e)) {
  5228.               e.preventDefault();
  5229.               e.stopPropagation();
  5230.             }
  5231.           }
  5232.           break;
  5233.  
  5234.         case 'focus':
  5235.           if (!this.contains(e.target) && !this.menu.contains(e.target))
  5236.             this.hideMenu();
  5237.           break;
  5238.  
  5239.         case 'activate':
  5240.         case 'resize':
  5241.           this.hideMenu();
  5242.           break;
  5243.       }
  5244.     },
  5245.  
  5246.     /**
  5247.      * Shows the menu.
  5248.      * @param {boolean} shouldSetFocus Whether to set focus on the
  5249.      *     selected menu item.
  5250.      */
  5251.     showMenu: function(shouldSetFocus) {
  5252.       this.hideMenu();
  5253.  
  5254.       var event = document.createEvent('UIEvents');
  5255.       event.initUIEvent('menushow', true, true, window, null);
  5256.  
  5257.       if (this.dispatchEvent(event)) {
  5258.         this.menu.hidden = false;
  5259.  
  5260.         this.setAttribute('menu-shown', '');
  5261.         if (shouldSetFocus)
  5262.           this.menu.focusSelectedItem();
  5263.  
  5264.         // when the menu is shown we steal all keyboard events.
  5265.         var doc = this.ownerDocument;
  5266.         var win = doc.defaultView;
  5267.         this.showingEvents_.add(doc, 'keydown', this, true);
  5268.         this.showingEvents_.add(doc, 'mousedown', this, true);
  5269.         this.showingEvents_.add(doc, 'focus', this, true);
  5270.         this.showingEvents_.add(win, 'resize', this);
  5271.         this.showingEvents_.add(this.menu, 'activate', this);
  5272.         this.positionMenu_();
  5273.       }
  5274.     },
  5275.  
  5276.     /**
  5277.      * Hides the menu. If your menu can go out of scope, make sure to call this
  5278.      * first.
  5279.      */
  5280.     hideMenu: function() {
  5281.       if (!this.isMenuShown())
  5282.         return;
  5283.  
  5284.       this.removeAttribute('menu-shown');
  5285.       this.menu.hidden = true;
  5286.  
  5287.       this.showingEvents_.removeAll();
  5288.       this.focus();
  5289.     },
  5290.  
  5291.     /**
  5292.      * Whether the menu is shown.
  5293.      */
  5294.     isMenuShown: function() {
  5295.       return this.hasAttribute('menu-shown');
  5296.     },
  5297.  
  5298.     /**
  5299.      * Positions the menu below the menu button. At this point we do not use any
  5300.      * advanced positioning logic to ensure the menu fits in the viewport.
  5301.      * @private
  5302.      */
  5303.     positionMenu_: function() {
  5304.       positionPopupAroundElement(this, this.menu, this.anchorType,
  5305.                                  this.invertLeftRight);
  5306.     },
  5307.  
  5308.     /**
  5309.      * Handles the keydown event for the menu button.
  5310.      */
  5311.     handleKeyDown: function(e) {
  5312.       switch (e.keyIdentifier) {
  5313.         case 'Down':
  5314.         case 'Up':
  5315.         case 'Enter':
  5316.         case 'U+0020': // Space
  5317.           if (!this.isMenuShown())
  5318.             this.showMenu(true);
  5319.           e.preventDefault();
  5320.           break;
  5321.         case 'Esc':
  5322.         case 'U+001B': // Maybe this is remote desktop playing a prank?
  5323.         case 'U+0009': // Tab
  5324.           this.hideMenu();
  5325.           break;
  5326.       }
  5327.     }
  5328.   };
  5329.  
  5330.   /**
  5331.    * Helper for styling a menu button with a drop-down arrow indicator.
  5332.    * Creates a new 2D canvas context and draws a downward-facing arrow into it.
  5333.    * @param {string} canvasName The name of the canvas. The canvas can be
  5334.    *     addressed from CSS using -webkit-canvas(<canvasName>).
  5335.    * @param {number} width The width of the canvas and the arrow.
  5336.    * @param {number} height The height of the canvas and the arrow.
  5337.    * @param {string} colorSpec The CSS color to use when drawing the arrow.
  5338.    */
  5339.   function createDropDownArrowCanvas(canvasName, width, height, colorSpec) {
  5340.     var ctx = document.getCSSCanvasContext('2d', canvasName, width, height);
  5341.     ctx.fillStyle = ctx.strokeStyle = colorSpec;
  5342.     ctx.beginPath();
  5343.     ctx.moveTo(0, 0);
  5344.     ctx.lineTo(width, 0);
  5345.     ctx.lineTo(height, height);
  5346.     ctx.closePath();
  5347.     ctx.fill();
  5348.     ctx.stroke();
  5349.   };
  5350.  
  5351.   /** @const */ var ARROW_WIDTH = 6;
  5352.   /** @const */ var ARROW_HEIGHT = 3;
  5353.  
  5354.   /**
  5355.    * Create the images used to style drop-down-style MenuButtons.
  5356.    * This should be called before creating any MenuButtons that will have the
  5357.    * CSS class 'drop-down'. If no colors are specified, defaults will be used.
  5358.    * @param {=string} normalColor CSS color for the default button state.
  5359.    * @param {=string} hoverColor CSS color for the hover button state.
  5360.    * @param {=string} activeColor CSS color for the active button state.
  5361.    */
  5362.   MenuButton.createDropDownArrows = function(
  5363.       normalColor, hoverColor, activeColor) {
  5364.     normalColor = normalColor || 'rgb(192, 195, 198)';
  5365.     hoverColor = hoverColor || 'rgb(48, 57, 66)';
  5366.     activeColor = activeColor || 'white';
  5367.  
  5368.     createDropDownArrowCanvas(
  5369.         'drop-down-arrow', ARROW_WIDTH, ARROW_HEIGHT, normalColor);
  5370.     createDropDownArrowCanvas(
  5371.         'drop-down-arrow-hover', ARROW_WIDTH, ARROW_HEIGHT, hoverColor);
  5372.     createDropDownArrowCanvas(
  5373.         'drop-down-arrow-active', ARROW_WIDTH, ARROW_HEIGHT, activeColor);
  5374.   };
  5375.  
  5376.   // Export
  5377.   return {
  5378.     MenuButton: MenuButton
  5379.   };
  5380. });
  5381. </script>
  5382. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5383. // Use of this source code is governed by a BSD-style license that can be
  5384. // found in the LICENSE file.
  5385.  
  5386. /**
  5387.  * @fileoverview This implements a special button that is useful for showing a
  5388.  * context menu.
  5389.  */
  5390.  
  5391. cr.define('cr.ui', function() {
  5392.   /** @const */ var MenuButton = cr.ui.MenuButton;
  5393.  
  5394.   /**
  5395.    * Helper function for ContextMenuButton to find the first ancestor of the
  5396.    * button that has a context menu.
  5397.    * @param {!MenuButton} el The button to start the search from.
  5398.    * @return {HTMLElement} The found element or null if not found.
  5399.    */
  5400.   function getContextMenuTarget(el) {
  5401.     do {
  5402.       el = el.parentNode;
  5403.     } while (el && !('contextMenu' in el));
  5404.     return el;
  5405.   }
  5406.  
  5407.   /**
  5408.    * Creates a new menu button which is used to show the context menu for an
  5409.    * ancestor that has a {@code contextMenu} property.
  5410.    * @param {Object=} opt_propertyBag Optional properties.
  5411.    * @constructor
  5412.    * @extends {MenuButton}
  5413.    */
  5414.   var ContextMenuButton = cr.ui.define('button');
  5415.  
  5416.   ContextMenuButton.prototype = {
  5417.     __proto__: MenuButton.prototype,
  5418.  
  5419.     /**
  5420.      * Override to return the contextMenu for the ancestor.
  5421.      * @override
  5422.      * @type {cr.ui.Menu}
  5423.      */
  5424.     get menu() {
  5425.       var target = getContextMenuTarget(this);
  5426.       return target && target.contextMenu;
  5427.     },
  5428.  
  5429.     /** @override */
  5430.     decorate: function() {
  5431.       this.tabIndex = -1;
  5432.       this.addEventListener('mouseup', this);
  5433.       MenuButton.prototype.decorate.call(this);
  5434.     },
  5435.  
  5436.     /** @override */
  5437.     handleEvent: function(e) {
  5438.       switch (e.type) {
  5439.         case 'mousedown':
  5440.           // Menu buttons prevent focus changes.
  5441.           var target = getContextMenuTarget(this);
  5442.           if (target)
  5443.             target.focus();
  5444.           break;
  5445.         case 'mouseup':
  5446.           // Stop mouseup to prevent selection changes.
  5447.           e.stopPropagation();
  5448.           break;
  5449.       }
  5450.       MenuButton.prototype.handleEvent.call(this, e);
  5451.     }
  5452.   };
  5453.  
  5454.   // Export
  5455.   return {
  5456.     ContextMenuButton: ContextMenuButton
  5457.   };
  5458. });
  5459. </script>
  5460. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5461. // Use of this source code is governed by a BSD-style license that can be
  5462. // found in the LICENSE file.
  5463.  
  5464. /**
  5465.  * @fileoverview Touch Handler. Class that handles all touch events and
  5466.  * uses them to interpret higher level gestures and behaviors. TouchEvent is a
  5467.  * built in mobile safari type:
  5468.  * http://developer.apple.com/safari/library/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html.
  5469.  * This class is intended to work with all webkit browsers, tested on Chrome and
  5470.  * iOS.
  5471.  *
  5472.  * The following types of gestures are currently supported.  See the definition
  5473.  * of TouchHandler.EventType for details.
  5474.  *
  5475.  * Single Touch:
  5476.  *      This provides simple single-touch events.  Any secondary touch is
  5477.  *      ignored.
  5478.  *
  5479.  * Drag:
  5480.  *      A single touch followed by some movement. This behavior will handle all
  5481.  *      of the required events and report the properties of the drag to you
  5482.  *      while the touch is happening and at the end of the drag sequence. This
  5483.  *      behavior will NOT perform the actual dragging (redrawing the element)
  5484.  *      for you, this responsibility is left to the client code.
  5485.  *
  5486.  * Long press:
  5487.  *     When your element is touched and held without any drag occuring, the
  5488.  *     LONG_PRESS event will fire.
  5489.  */
  5490.  
  5491. // Use an anonymous function to enable strict mode just for this file (which
  5492. // will be concatenated with other files when embedded in Chrome)
  5493. cr.define('cr.ui', function() {
  5494.   'use strict';
  5495.  
  5496.   /**
  5497.    * A TouchHandler attaches to an Element, listents for low-level touch (or
  5498.    * mouse) events and dispatching higher-level events on the element.
  5499.    * @param {!Element} element The element to listen on and fire events
  5500.    * for.
  5501.    * @constructor
  5502.    */
  5503.   function TouchHandler(element) {
  5504.     /**
  5505.      * @type {!Element}
  5506.      * @private
  5507.      */
  5508.     this.element_ = element;
  5509.  
  5510.     /**
  5511.      * The absolute sum of all touch y deltas.
  5512.      * @type {number}
  5513.      * @private
  5514.      */
  5515.     this.totalMoveY_ = 0;
  5516.  
  5517.     /**
  5518.      * The absolute sum of all touch x deltas.
  5519.      * @type {number}
  5520.      * @private
  5521.      */
  5522.     this.totalMoveX_ = 0;
  5523.  
  5524.     /**
  5525.      * An array of tuples where the first item is the horizontal component of a
  5526.      * recent relevant touch and the second item is the touch's time stamp. Old
  5527.      * touches are removed based on the max tracking time and when direction
  5528.      * changes.
  5529.       * @type {!Array.<number>}
  5530.       * @private
  5531.       */
  5532.     this.recentTouchesX_ = [];
  5533.  
  5534.     /**
  5535.      * An array of tuples where the first item is the vertical component of a
  5536.      * recent relevant touch and the second item is the touch's time stamp. Old
  5537.      * touches are removed based on the max tracking time and when direction
  5538.      * changes.
  5539.      * @type {!Array.<number>}
  5540.      * @private
  5541.      */
  5542.     this.recentTouchesY_ = [];
  5543.  
  5544.     /**
  5545.      * Used to keep track of all events we subscribe to so we can easily clean
  5546.      * up
  5547.      * @type {EventTracker}
  5548.      * @private
  5549.      */
  5550.     this.events_ = new EventTracker();
  5551.   }
  5552.  
  5553.  
  5554.   /**
  5555.    * DOM Events that may be fired by the TouchHandler at the element
  5556.    */
  5557.   TouchHandler.EventType = {
  5558.     // Fired whenever the element is touched as the only touch to the device.
  5559.     // enableDrag defaults to false, set to true to permit dragging.
  5560.     TOUCH_START: 'touchHandler:touch_start',
  5561.  
  5562.     // Fired when an element is held for a period of time.  Prevents dragging
  5563.     // from occuring (even if enableDrag was set to true).
  5564.     LONG_PRESS: 'touchHandler:long_press',
  5565.  
  5566.     // If enableDrag was set to true at TOUCH_START, DRAG_START will fire when
  5567.     // the touch first moves sufficient distance.  enableDrag is set to true but
  5568.     // can be reset to false to cancel the drag.
  5569.     DRAG_START: 'touchHandler:drag_start',
  5570.  
  5571.     // If enableDrag was true after DRAG_START, DRAG_MOVE will fire whenever the
  5572.     // touch is moved.
  5573.     DRAG_MOVE: 'touchHandler:drag_move',
  5574.  
  5575.     // Fired just before TOUCH_END when a drag is released.  Correlates 1:1 with
  5576.     // a DRAG_START.
  5577.     DRAG_END: 'touchHandler:drag_end',
  5578.  
  5579.     // Fired whenever a touch that is being tracked has been released.
  5580.     // Correlates 1:1 with a TOUCH_START.
  5581.     TOUCH_END: 'touchHandler:touch_end',
  5582.  
  5583.     // Fired whenever the element is tapped in a short time and no dragging is
  5584.     // detected.
  5585.     TAP: 'touchHandler:tap'
  5586.   };
  5587.  
  5588.  
  5589.   /**
  5590.    * The type of event sent by TouchHandler
  5591.    * @constructor
  5592.    * @param {string} type The type of event (one of cr.ui.Grabber.EventType).
  5593.    * @param {boolean} bubbles Whether or not the event should bubble.
  5594.    * @param {number} clientX The X location of the touch.
  5595.    * @param {number} clientY The Y location of the touch.
  5596.    * @param {!Element} touchedElement The element at the current location of the
  5597.    *        touch.
  5598.    */
  5599.   TouchHandler.Event = function(type, bubbles, clientX, clientY,
  5600.       touchedElement) {
  5601.     var event = document.createEvent('Event');
  5602.     event.initEvent(type, bubbles, true);
  5603.     event.__proto__ = TouchHandler.Event.prototype;
  5604.  
  5605.     /**
  5606.      * The X location of the touch affected
  5607.      * @type {number}
  5608.      */
  5609.     event.clientX = clientX;
  5610.  
  5611.     /**
  5612.      * The Y location of the touch affected
  5613.      * @type {number}
  5614.      */
  5615.     event.clientY = clientY;
  5616.  
  5617.     /**
  5618.      * The element at the current location of the touch.
  5619.      * @type {!Element}
  5620.      */
  5621.     event.touchedElement = touchedElement;
  5622.  
  5623.     return event;
  5624.   };
  5625.  
  5626.   TouchHandler.Event.prototype = {
  5627.     __proto__: Event.prototype,
  5628.  
  5629.     /**
  5630.      * For TOUCH_START and DRAG START events, set to true to enable dragging or
  5631.      * false to disable dragging.
  5632.      * @type {boolean|undefined}
  5633.      */
  5634.     enableDrag: undefined,
  5635.  
  5636.     /**
  5637.      * For DRAG events, provides the horizontal component of the
  5638.      * drag delta. Drag delta is defined as the delta of the start touch
  5639.      * position and the current drag position.
  5640.      * @type {number|undefined}
  5641.      */
  5642.     dragDeltaX: undefined,
  5643.  
  5644.     /**
  5645.      * For DRAG events, provides the vertical component of the
  5646.      * drag delta.
  5647.      * @type {number|undefined}
  5648.      */
  5649.     dragDeltaY: undefined
  5650.   };
  5651.  
  5652.   /**
  5653.    * Maximum movement of touch required to be considered a tap.
  5654.    * @type {number}
  5655.    * @private
  5656.    */
  5657.   TouchHandler.MAX_TRACKING_FOR_TAP_ = 8;
  5658.  
  5659.  
  5660.   /**
  5661.    * The maximum number of ms to track a touch event. After an event is older
  5662.    * than this value, it will be ignored in velocity calculations.
  5663.    * @type {number}
  5664.    * @private
  5665.    */
  5666.   TouchHandler.MAX_TRACKING_TIME_ = 250;
  5667.  
  5668.  
  5669.   /**
  5670.    * The maximum number of touches to track.
  5671.    * @type {number}
  5672.    * @private
  5673.    */
  5674.   TouchHandler.MAX_TRACKING_TOUCHES_ = 5;
  5675.  
  5676.  
  5677.   /**
  5678.    * The maximum velocity to return, in pixels per millisecond, that is used
  5679.    * to guard against errors in calculating end velocity of a drag. This is a
  5680.    * very fast drag velocity.
  5681.    * @type {number}
  5682.    * @private
  5683.    */
  5684.   TouchHandler.MAXIMUM_VELOCITY_ = 5;
  5685.  
  5686.  
  5687.   /**
  5688.    * The velocity to return, in pixel per millisecond, when the time stamps on
  5689.    * the events are erroneous. The browser can return bad time stamps if the
  5690.    * thread is blocked for the duration of the drag. This is a low velocity to
  5691.    * prevent the content from moving quickly after a slow drag. It is less
  5692.    * jarring if the content moves slowly after a fast drag.
  5693.    * @type {number}
  5694.    * @private
  5695.    */
  5696.   TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ = 1;
  5697.  
  5698.   /**
  5699.    * The time, in milliseconds, that a touch must be held to be considered
  5700.    * 'long'.
  5701.    * @type {number}
  5702.    * @private
  5703.    */
  5704.   TouchHandler.TIME_FOR_LONG_PRESS_ = 500;
  5705.  
  5706.   TouchHandler.prototype = {
  5707.     /**
  5708.      * If defined, the identifer of the single touch that is active.  Note that
  5709.      * 0 is a valid touch identifier - it should not be treated equivalently to
  5710.      * undefined.
  5711.      * @type {number|undefined}
  5712.      * @private
  5713.      */
  5714.     activeTouch_: undefined,
  5715.  
  5716.     /**
  5717.      * @type {boolean|undefined}
  5718.      * @private
  5719.      */
  5720.     tracking_: undefined,
  5721.  
  5722.     /**
  5723.      * @type {number|undefined}
  5724.      * @private
  5725.      */
  5726.     startTouchX_: undefined,
  5727.  
  5728.     /**
  5729.      * @type {number|undefined}
  5730.      * @private
  5731.      */
  5732.     startTouchY_: undefined,
  5733.  
  5734.     /**
  5735.      * @type {number|undefined}
  5736.      * @private
  5737.      */
  5738.     endTouchX_: undefined,
  5739.  
  5740.     /**
  5741.      * @type {number|undefined}
  5742.      * @private
  5743.      */
  5744.     endTouchY_: undefined,
  5745.  
  5746.     /**
  5747.      * Time of the touchstart event.
  5748.      * @type {number|undefined}
  5749.      * @private
  5750.      */
  5751.     startTime_: undefined,
  5752.  
  5753.     /**
  5754.      * The time of the touchend event.
  5755.      * @type {number|undefined}
  5756.      * @private
  5757.      */
  5758.     endTime_: undefined,
  5759.  
  5760.     /**
  5761.      * @type {number|undefined}
  5762.      * @private
  5763.      */
  5764.     lastTouchX_: undefined,
  5765.  
  5766.     /**
  5767.      * @type {number|undefined}
  5768.      * @private
  5769.      */
  5770.     lastTouchY_: undefined,
  5771.  
  5772.     /**
  5773.      * @type {number|undefined}
  5774.      * @private
  5775.      */
  5776.     lastMoveX_: undefined,
  5777.  
  5778.     /**
  5779.      * @type {number|undefined}
  5780.      * @private
  5781.      */
  5782.     lastMoveY_: undefined,
  5783.  
  5784.     /**
  5785.      * @type {number|undefined}
  5786.      * @private
  5787.      */
  5788.     longPressTimeout_: undefined,
  5789.  
  5790.     /**
  5791.      * If defined and true, the next click event should be swallowed
  5792.      * @type {boolean|undefined}
  5793.      * @private
  5794.      */
  5795.     swallowNextClick_: undefined,
  5796.  
  5797.     /**
  5798.      * @type {boolean}
  5799.      * @private
  5800.      */
  5801.     draggingEnabled_: false,
  5802.  
  5803.     /**
  5804.      * Start listenting for events.
  5805.      * @param {boolean=} opt_capture True if the TouchHandler should listen to
  5806.      *      during the capture phase.
  5807.      * @param {boolean=} opt_mouse True if the TouchHandler should generate
  5808.      *      events for mouse input (in addition to touch input).
  5809.      */
  5810.     enable: function(opt_capture, opt_mouse) {
  5811.       var capture = !!opt_capture;
  5812.  
  5813.       // Just listen to start events for now. When a touch is occuring we'll
  5814.       // want to be subscribed to move and end events on the document, but we
  5815.       // don't want to incur the cost of lots of no-op handlers on the document.
  5816.       this.events_.add(this.element_, 'touchstart', this.onStart_.bind(this),
  5817.                        capture);
  5818.       if (opt_mouse) {
  5819.         this.events_.add(this.element_, 'mousedown',
  5820.                          this.mouseToTouchCallback_(this.onStart_.bind(this)),
  5821.                          capture);
  5822.       }
  5823.  
  5824.       // If the element is long-pressed, we may need to swallow a click
  5825.       this.events_.add(this.element_, 'click', this.onClick_.bind(this), true);
  5826.     },
  5827.  
  5828.     /**
  5829.      * Stop listening to all events.
  5830.      */
  5831.     disable: function() {
  5832.       this.stopTouching_();
  5833.       this.events_.removeAll();
  5834.     },
  5835.  
  5836.     /**
  5837.      * Wraps a callback with translations of mouse events to touch events.
  5838.      * NOTE: These types really should be function(Event) but then we couldn't
  5839.      * use this with bind (which operates on any type of function).  Doesn't
  5840.      * JSDoc support some sort of polymorphic types?
  5841.      * @param {Function} callback The event callback.
  5842.      * @return {Function} The wrapping callback.
  5843.      * @private
  5844.      */
  5845.     mouseToTouchCallback_: function(callback) {
  5846.       return function(e) {
  5847.         // Note that there may be synthesizes mouse events caused by touch
  5848.         // events (a mouseDown after a touch-click).  We leave it up to the
  5849.         // client to worry about this if it matters to them (typically a short
  5850.         // mouseDown/mouseUp without a click is no big problem and it's not
  5851.         // obvious how we identify such synthesized events in a general way).
  5852.         var touch = {
  5853.           // any fixed value will do for the identifier - there will only
  5854.           // ever be a single active 'touch' when using the mouse.
  5855.           identifier: 0,
  5856.           clientX: e.clientX,
  5857.           clientY: e.clientY,
  5858.           target: e.target
  5859.         };
  5860.         e.touches = [];
  5861.         e.targetTouches = [];
  5862.         e.changedTouches = [touch];
  5863.         if (e.type != 'mouseup') {
  5864.           e.touches[0] = touch;
  5865.           e.targetTouches[0] = touch;
  5866.         }
  5867.         callback(e);
  5868.       };
  5869.     },
  5870.  
  5871.     /**
  5872.      * Begin tracking the touchable element, it is eligible for dragging.
  5873.      * @private
  5874.      */
  5875.     beginTracking_: function() {
  5876.       this.tracking_ = true;
  5877.     },
  5878.  
  5879.     /**
  5880.      * Stop tracking the touchable element, it is no longer dragging.
  5881.      * @private
  5882.      */
  5883.     endTracking_: function() {
  5884.       this.tracking_ = false;
  5885.       this.dragging_ = false;
  5886.       this.totalMoveY_ = 0;
  5887.       this.totalMoveX_ = 0;
  5888.     },
  5889.  
  5890.     /**
  5891.      * Reset the touchable element as if we never saw the touchStart
  5892.      * Doesn't dispatch any end events - be careful of existing listeners.
  5893.      */
  5894.     cancelTouch: function() {
  5895.       this.stopTouching_();
  5896.       this.endTracking_();
  5897.       // If clients needed to be aware of this, we could fire a cancel event
  5898.       // here.
  5899.     },
  5900.  
  5901.     /**
  5902.      * Record that touching has stopped
  5903.      * @private
  5904.      */
  5905.     stopTouching_: function() {
  5906.       // Mark as no longer being touched
  5907.       this.activeTouch_ = undefined;
  5908.  
  5909.       // If we're waiting for a long press, stop
  5910.       window.clearTimeout(this.longPressTimeout_);
  5911.  
  5912.       // Stop listening for move/end events until there's another touch.
  5913.       // We don't want to leave handlers piled up on the document.
  5914.       // Note that there's no harm in removing handlers that weren't added, so
  5915.       // rather than track whether we're using mouse or touch we do both.
  5916.       this.events_.remove(document, 'touchmove');
  5917.       this.events_.remove(document, 'touchend');
  5918.       this.events_.remove(document, 'touchcancel');
  5919.       this.events_.remove(document, 'mousemove');
  5920.       this.events_.remove(document, 'mouseup');
  5921.     },
  5922.  
  5923.     /**
  5924.      * Touch start handler.
  5925.      * @param {!TouchEvent} e The touchstart event.
  5926.      * @private
  5927.      */
  5928.     onStart_: function(e) {
  5929.       // Only process single touches.  If there is already a touch happening, or
  5930.       // two simultaneous touches then just ignore them.
  5931.       if (e.touches.length > 1)
  5932.         // Note that we could cancel an active touch here.  That would make
  5933.         // simultaneous touch behave similar to near-simultaneous. However, if
  5934.         // the user is dragging something, an accidental second touch could be
  5935.         // quite disruptive if it cancelled their drag.  Better to just ignore
  5936.         // it.
  5937.         return;
  5938.  
  5939.       // It's still possible there could be an active "touch" if the user is
  5940.       // simultaneously using a mouse and a touch input.
  5941.       if (this.activeTouch_ !== undefined)
  5942.         return;
  5943.  
  5944.       var touch = e.targetTouches[0];
  5945.       this.activeTouch_ = touch.identifier;
  5946.  
  5947.       // We've just started touching so shouldn't swallow any upcoming click
  5948.       if (this.swallowNextClick_)
  5949.         this.swallowNextClick_ = false;
  5950.  
  5951.       this.disableTap_ = false;
  5952.  
  5953.       // Sign up for end/cancel notifications for this touch.
  5954.       // Note that we do this on the document so that even if the user drags
  5955.       // their finger off the element, we'll still know what they're doing.
  5956.       if (e.type == 'mousedown') {
  5957.         this.events_.add(document, 'mouseup',
  5958.             this.mouseToTouchCallback_(this.onEnd_.bind(this)), false);
  5959.       } else {
  5960.         this.events_.add(document, 'touchend', this.onEnd_.bind(this), false);
  5961.         this.events_.add(document, 'touchcancel', this.onEnd_.bind(this),
  5962.             false);
  5963.       }
  5964.  
  5965.       // This timeout is cleared on touchEnd and onDrag
  5966.       // If we invoke the function then we have a real long press
  5967.       window.clearTimeout(this.longPressTimeout_);
  5968.       this.longPressTimeout_ = window.setTimeout(
  5969.           this.onLongPress_.bind(this),
  5970.           TouchHandler.TIME_FOR_LONG_PRESS_);
  5971.  
  5972.       // Dispatch the TOUCH_START event
  5973.       this.draggingEnabled_ =
  5974.           !!this.dispatchEvent_(TouchHandler.EventType.TOUCH_START, touch);
  5975.  
  5976.       // We want dragging notifications
  5977.       if (e.type == 'mousedown') {
  5978.         this.events_.add(document, 'mousemove',
  5979.             this.mouseToTouchCallback_(this.onMove_.bind(this)), false);
  5980.       } else {
  5981.         this.events_.add(document, 'touchmove', this.onMove_.bind(this), false);
  5982.       }
  5983.  
  5984.       this.startTouchX_ = this.lastTouchX_ = touch.clientX;
  5985.       this.startTouchY_ = this.lastTouchY_ = touch.clientY;
  5986.       this.startTime_ = e.timeStamp;
  5987.  
  5988.       this.recentTouchesX_ = [];
  5989.       this.recentTouchesY_ = [];
  5990.       this.recentTouchesX_.push(touch.clientX, e.timeStamp);
  5991.       this.recentTouchesY_.push(touch.clientY, e.timeStamp);
  5992.  
  5993.       this.beginTracking_();
  5994.     },
  5995.  
  5996.     /**
  5997.      * Given a list of Touches, find the one matching our activeTouch
  5998.      * identifier. Note that Chrome currently always uses 0 as the identifier.
  5999.      * In that case we'll end up always choosing the first element in the list.
  6000.      * @param {TouchList} touches The list of Touch objects to search.
  6001.      * @return {!Touch|undefined} The touch matching our active ID if any.
  6002.      * @private
  6003.      */
  6004.     findActiveTouch_: function(touches) {
  6005.       assert(this.activeTouch_ !== undefined, 'Expecting an active touch');
  6006.       // A TouchList isn't actually an array, so we shouldn't use
  6007.       // Array.prototype.filter/some, etc.
  6008.       for (var i = 0; i < touches.length; i++) {
  6009.         if (touches[i].identifier == this.activeTouch_)
  6010.           return touches[i];
  6011.       }
  6012.       return undefined;
  6013.     },
  6014.  
  6015.     /**
  6016.      * Touch move handler.
  6017.      * @param {!TouchEvent} e The touchmove event.
  6018.      * @private
  6019.      */
  6020.     onMove_: function(e) {
  6021.       if (!this.tracking_)
  6022.         return;
  6023.  
  6024.       // Our active touch should always be in the list of touches still active
  6025.       assert(this.findActiveTouch_(e.touches), 'Missing touchEnd');
  6026.  
  6027.       var that = this;
  6028.       var touch = this.findActiveTouch_(e.changedTouches);
  6029.       if (!touch)
  6030.         return;
  6031.  
  6032.       var clientX = touch.clientX;
  6033.       var clientY = touch.clientY;
  6034.  
  6035.       var moveX = this.lastTouchX_ - clientX;
  6036.       var moveY = this.lastTouchY_ - clientY;
  6037.       this.totalMoveX_ += Math.abs(moveX);
  6038.       this.totalMoveY_ += Math.abs(moveY);
  6039.       this.lastTouchX_ = clientX;
  6040.       this.lastTouchY_ = clientY;
  6041.  
  6042.       var couldBeTap =
  6043.           this.totalMoveY_ <= TouchHandler.MAX_TRACKING_FOR_TAP_ ||
  6044.           this.totalMoveX_ <= TouchHandler.MAX_TRACKING_FOR_TAP_;
  6045.  
  6046.       if (!couldBeTap)
  6047.         this.disableTap_ = true;
  6048.  
  6049.       if (this.draggingEnabled_ && !this.dragging_ && !couldBeTap) {
  6050.         // If we're waiting for a long press, stop
  6051.         window.clearTimeout(this.longPressTimeout_);
  6052.  
  6053.         // Dispatch the DRAG_START event and record whether dragging should be
  6054.         // allowed or not.  Note that this relies on the current value of
  6055.         // startTouchX/Y - handlers may use the initial drag delta to determine
  6056.         // if dragging should be permitted.
  6057.         this.dragging_ = this.dispatchEvent_(
  6058.             TouchHandler.EventType.DRAG_START, touch);
  6059.  
  6060.         if (this.dragging_) {
  6061.           // Update the start position here so that drag deltas have better
  6062.           // values but don't touch the recent positions so that velocity
  6063.           // calculations can still use touchstart position in the time and
  6064.           // distance delta.
  6065.           this.startTouchX_ = clientX;
  6066.           this.startTouchY_ = clientY;
  6067.           this.startTime_ = e.timeStamp;
  6068.         } else {
  6069.           this.endTracking_();
  6070.         }
  6071.       }
  6072.  
  6073.       if (this.dragging_) {
  6074.         this.dispatchEvent_(TouchHandler.EventType.DRAG_MOVE, touch);
  6075.  
  6076.         this.removeTouchesInWrongDirection_(this.recentTouchesX_,
  6077.             this.lastMoveX_, moveX);
  6078.         this.removeTouchesInWrongDirection_(this.recentTouchesY_,
  6079.             this.lastMoveY_, moveY);
  6080.         this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
  6081.         this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
  6082.         this.recentTouchesX_.push(clientX, e.timeStamp);
  6083.         this.recentTouchesY_.push(clientY, e.timeStamp);
  6084.       }
  6085.  
  6086.       this.lastMoveX_ = moveX;
  6087.       this.lastMoveY_ = moveY;
  6088.     },
  6089.  
  6090.     /**
  6091.      * Filters the provided recent touches array to remove all touches except
  6092.      * the last if the move direction has changed.
  6093.      * @param {!Array.<number>} recentTouches An array of tuples where the first
  6094.      *     item is the x or y component of the recent touch and the second item
  6095.      *     is the touch time stamp.
  6096.      * @param {number|undefined} lastMove The x or y component of the previous
  6097.      *     move.
  6098.      * @param {number} recentMove The x or y component of the most recent move.
  6099.      * @private
  6100.      */
  6101.     removeTouchesInWrongDirection_: function(recentTouches, lastMove,
  6102.         recentMove) {
  6103.       if (lastMove && recentMove && recentTouches.length > 2 &&
  6104.           (lastMove > 0 ^ recentMove > 0)) {
  6105.         recentTouches.splice(0, recentTouches.length - 2);
  6106.       }
  6107.     },
  6108.  
  6109.     /**
  6110.      * Filters the provided recent touches array to remove all touches older
  6111.      * than the max tracking time or the 5th most recent touch.
  6112.      * @param {!Array.<number>} recentTouches An array of tuples where the first
  6113.      *     item is the x or y component of the recent touch and the second item
  6114.      *     is the touch time stamp.
  6115.      * @param {number} recentTime The time of the most recent event.
  6116.      * @private
  6117.      */
  6118.     removeOldTouches_: function(recentTouches, recentTime) {
  6119.       while (recentTouches.length && recentTime - recentTouches[1] >
  6120.           TouchHandler.MAX_TRACKING_TIME_ ||
  6121.           recentTouches.length >
  6122.               TouchHandler.MAX_TRACKING_TOUCHES_ * 2) {
  6123.         recentTouches.splice(0, 2);
  6124.       }
  6125.     },
  6126.  
  6127.     /**
  6128.      * Touch end handler.
  6129.      * @param {!TouchEvent} e The touchend event.
  6130.      * @private
  6131.      */
  6132.     onEnd_: function(e) {
  6133.       var that = this;
  6134.       assert(this.activeTouch_ !== undefined, 'Expect to already be touching');
  6135.  
  6136.       // If the touch we're tracking isn't changing here, ignore this touch end.
  6137.       var touch = this.findActiveTouch_(e.changedTouches);
  6138.       if (!touch) {
  6139.         // In most cases, our active touch will be in the 'touches' collection,
  6140.         // but we can't assert that because occasionally two touchend events can
  6141.         // occur at almost the same time with both having empty 'touches' lists.
  6142.         // I.e., 'touches' seems like it can be a bit more up-to-date than the
  6143.         // current event.
  6144.         return;
  6145.       }
  6146.  
  6147.       // This is touchEnd for the touch we're monitoring
  6148.       assert(!this.findActiveTouch_(e.touches),
  6149.              'Touch ended also still active');
  6150.  
  6151.       // Indicate that touching has finished
  6152.       this.stopTouching_();
  6153.  
  6154.       if (this.tracking_) {
  6155.         var clientX = touch.clientX;
  6156.         var clientY = touch.clientY;
  6157.  
  6158.         if (this.dragging_) {
  6159.           this.endTime_ = e.timeStamp;
  6160.           this.endTouchX_ = clientX;
  6161.           this.endTouchY_ = clientY;
  6162.  
  6163.           this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
  6164.           this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
  6165.  
  6166.           this.dispatchEvent_(TouchHandler.EventType.DRAG_END, touch);
  6167.  
  6168.           // Note that in some situations we can get a click event here as well.
  6169.           // For now this isn't a problem, but we may want to consider having
  6170.           // some logic that hides clicks that appear to be caused by a touchEnd
  6171.           // used for dragging.
  6172.         }
  6173.  
  6174.         this.endTracking_();
  6175.       }
  6176.       this.draggingEnabled_ = false;
  6177.  
  6178.       // Note that we dispatch the touchEnd event last so that events at
  6179.       // different levels of semantics nest nicely (similar to how DOM
  6180.       // drag-and-drop events are nested inside of the mouse events that trigger
  6181.       // them).
  6182.       this.dispatchEvent_(TouchHandler.EventType.TOUCH_END, touch);
  6183.       if (!this.disableTap_)
  6184.         this.dispatchEvent_(TouchHandler.EventType.TAP, touch);
  6185.     },
  6186.  
  6187.     /**
  6188.      * Get end velocity of the drag. This method is specific to drag behavior,
  6189.      * so if touch behavior and drag behavior is split then this should go with
  6190.      * drag behavior. End velocity is defined as deltaXY / deltaTime where
  6191.      * deltaXY is the difference between endPosition and the oldest recent
  6192.      * position, and deltaTime is the difference between endTime and the oldest
  6193.      * recent time stamp.
  6194.      * @return {Object} The x and y velocity.
  6195.      */
  6196.     getEndVelocity: function() {
  6197.       // Note that we could move velocity to just be an end-event parameter.
  6198.       var velocityX = this.recentTouchesX_.length ?
  6199.           (this.endTouchX_ - this.recentTouchesX_[0]) /
  6200.           (this.endTime_ - this.recentTouchesX_[1]) : 0;
  6201.       var velocityY = this.recentTouchesY_.length ?
  6202.           (this.endTouchY_ - this.recentTouchesY_[0]) /
  6203.           (this.endTime_ - this.recentTouchesY_[1]) : 0;
  6204.  
  6205.       velocityX = this.correctVelocity_(velocityX);
  6206.       velocityY = this.correctVelocity_(velocityY);
  6207.  
  6208.       return {
  6209.         x: velocityX,
  6210.         y: velocityY
  6211.       };
  6212.     },
  6213.  
  6214.     /**
  6215.      * Correct erroneous velocities by capping the velocity if we think it's too
  6216.      * high, or setting it to a default velocity if know that the event data is
  6217.      * bad.
  6218.      * @param {number} velocity The x or y velocity component.
  6219.      * @return {number} The corrected velocity.
  6220.      * @private
  6221.      */
  6222.     correctVelocity_: function(velocity) {
  6223.       var absVelocity = Math.abs(velocity);
  6224.  
  6225.       // We add to recent touches for each touchstart and touchmove. If we have
  6226.       // fewer than 3 touches (6 entries), we assume that the thread was blocked
  6227.       // for the duration of the drag and we received events in quick succession
  6228.       // with the wrong time stamps.
  6229.       if (absVelocity > TouchHandler.MAXIMUM_VELOCITY_) {
  6230.         absVelocity = this.recentTouchesY_.length < 3 ?
  6231.             TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ :
  6232.                 TouchHandler.MAXIMUM_VELOCITY_;
  6233.       }
  6234.       return absVelocity * (velocity < 0 ? -1 : 1);
  6235.     },
  6236.  
  6237.     /**
  6238.      * Handler when an element has been pressed for a long time
  6239.      * @private
  6240.      */
  6241.     onLongPress_: function() {
  6242.       // Swallow any click that occurs on this element without an intervening
  6243.       // touch start event.  This simple click-busting technique should be
  6244.       // sufficient here since a real click should have a touchstart first.
  6245.       this.swallowNextClick_ = true;
  6246.       this.disableTap_ = true;
  6247.  
  6248.       // Dispatch to the LONG_PRESS
  6249.       this.dispatchEventXY_(TouchHandler.EventType.LONG_PRESS, this.element_,
  6250.           this.startTouchX_, this.startTouchY_);
  6251.     },
  6252.  
  6253.     /**
  6254.      * Click handler - used to swallow clicks after a long-press
  6255.      * @param {!Event} e The click event.
  6256.      * @private
  6257.      */
  6258.     onClick_: function(e) {
  6259.       if (this.swallowNextClick_) {
  6260.         e.preventDefault();
  6261.         e.stopPropagation();
  6262.         this.swallowNextClick_ = false;
  6263.       }
  6264.     },
  6265.  
  6266.     /**
  6267.      * Dispatch a TouchHandler event to the element
  6268.      * @param {string} eventType The event to dispatch.
  6269.      * @param {Touch} touch The touch triggering this event.
  6270.      * @return {boolean|undefined} The value of enableDrag after dispatching
  6271.      *         the event.
  6272.      * @private
  6273.      */
  6274.     dispatchEvent_: function(eventType, touch) {
  6275.  
  6276.       // Determine which element was touched.  For mouse events, this is always
  6277.       // the event/touch target.  But for touch events, the target is always the
  6278.       // target of the touchstart (and it's unlikely we can change this
  6279.       // since the common implementation of touch dragging relies on it). Since
  6280.       // touch is our primary scenario (which we want to emulate with mouse),
  6281.       // we'll treat both cases the same and not depend on the target.
  6282.       var touchedElement;
  6283.       if (eventType == TouchHandler.EventType.TOUCH_START) {
  6284.         touchedElement = touch.target;
  6285.       } else {
  6286.         touchedElement = this.element_.ownerDocument.
  6287.             elementFromPoint(touch.clientX, touch.clientY);
  6288.       }
  6289.  
  6290.       return this.dispatchEventXY_(eventType, touchedElement, touch.clientX,
  6291.           touch.clientY);
  6292.     },
  6293.  
  6294.     /**
  6295.      * Dispatch a TouchHandler event to the element
  6296.      * @param {string} eventType The event to dispatch.
  6297.        @param {number} clientX The X location for the event.
  6298.        @param {number} clientY The Y location for the event.
  6299.      * @return {boolean|undefined} The value of enableDrag after dispatching
  6300.      *         the event.
  6301.      * @private
  6302.      */
  6303.     dispatchEventXY_: function(eventType, touchedElement, clientX, clientY) {
  6304.       var isDrag = (eventType == TouchHandler.EventType.DRAG_START ||
  6305.           eventType == TouchHandler.EventType.DRAG_MOVE ||
  6306.           eventType == TouchHandler.EventType.DRAG_END);
  6307.  
  6308.       // Drag events don't bubble - we're really just dragging the element,
  6309.       // not affecting its parent at all.
  6310.       var bubbles = !isDrag;
  6311.  
  6312.       var event = new TouchHandler.Event(eventType, bubbles, clientX, clientY,
  6313.           touchedElement);
  6314.  
  6315.       // Set enableDrag when it can be overridden
  6316.       if (eventType == TouchHandler.EventType.TOUCH_START)
  6317.         event.enableDrag = false;
  6318.       else if (eventType == TouchHandler.EventType.DRAG_START)
  6319.         event.enableDrag = true;
  6320.  
  6321.       if (isDrag) {
  6322.         event.dragDeltaX = clientX - this.startTouchX_;
  6323.         event.dragDeltaY = clientY - this.startTouchY_;
  6324.       }
  6325.  
  6326.       this.element_.dispatchEvent(event);
  6327.       return event.enableDrag;
  6328.     }
  6329.   };
  6330.  
  6331.   return {
  6332.     TouchHandler: TouchHandler
  6333.   };
  6334. });
  6335. </script>
  6336.  
  6337. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6338. // Use of this source code is governed by a BSD-style license that can be
  6339. // found in the LICENSE file.
  6340.  
  6341. /**
  6342.  * @fileoverview This file provides utility functions for position popups.
  6343.  */
  6344.  
  6345. cr.define('cr.ui', function() {
  6346.  
  6347.   /**
  6348.    * Type def for rects as returned by getBoundingClientRect.
  6349.    * @typedef { {left: number, top: number, width: number, height: number,
  6350.    *             right: number, bottom: number}}
  6351.    */
  6352.   var Rect;
  6353.  
  6354.   /** @const */
  6355.   var AnchorType = cr.ui.AnchorType;
  6356.  
  6357.   /**
  6358.    * @type {Number}
  6359.    * @const
  6360.    */
  6361.   var BOOKMARK_BAR_HEIGHT = 48;
  6362.  
  6363.   /**
  6364.    * Helper function for positionPopupAroundElement and positionPopupAroundRect.
  6365.    * @param {!Rect} anchorRect The rect for the anchor.
  6366.    * @param {!HTMLElement} popupElement The element used for the popup.
  6367.    * @param {AnchorType} type The type of anchoring to do.
  6368.    * @param {boolean} invertLeftRight Whether to invert the right/left
  6369.    *     alignment.
  6370.    */
  6371.   function positionPopupAroundRect(anchorRect, popupElement, type,
  6372.                                    invertLeftRight) {
  6373.     var popupRect = popupElement.getBoundingClientRect();
  6374.     var availRect;
  6375.     var ownerDoc = popupElement.ownerDocument;
  6376.     var cs = ownerDoc.defaultView.getComputedStyle(popupElement);
  6377.     var docElement = ownerDoc.documentElement;
  6378.  
  6379.     if (cs.position == 'fixed') {
  6380.       // For 'fixed' positioned popups, the available rectangle should be based
  6381.       // on the viewport rather than the document.
  6382.       availRect = {
  6383.         height: docElement.clientHeight,
  6384.         width: docElement.clientWidth,
  6385.         top: 0,
  6386.         bottom: docElement.clientHeight,
  6387.         left: 0,
  6388.         right: docElement.clientWidth
  6389.       };
  6390.     } else {
  6391.       availRect = popupElement.offsetParent.getBoundingClientRect();
  6392.     }
  6393.  
  6394.     if (cs.direction == 'rtl')
  6395.       invertLeftRight = !invertLeftRight;
  6396.  
  6397.     // Flip BEFORE, AFTER based on alignment.
  6398.     if (invertLeftRight) {
  6399.       if (type == AnchorType.BEFORE)
  6400.         type = AnchorType.AFTER;
  6401.       else if (type == AnchorType.AFTER)
  6402.         type = AnchorType.BEFORE;
  6403.     }
  6404.  
  6405.     // Flip type based on available size
  6406.     switch (type) {
  6407.       case AnchorType.BELOW:
  6408.         // Do not flip when the type is below to avoid crbug.com/164113.
  6409.         break;
  6410.       case AnchorType.ABOVE:
  6411.         if (popupRect.height > anchorRect.top &&
  6412.             anchorRect.bottom + popupRect.height <= availRect.height) {
  6413.           type = AnchorType.BELOW;
  6414.         }
  6415.         break;
  6416.       case AnchorType.AFTER:
  6417.         if (anchorRect.right + popupRect.width > availRect.width &&
  6418.             popupRect.width <= anchorRect.left) {
  6419.           type = AnchorType.BEFORE;
  6420.         }
  6421.         break;
  6422.       case AnchorType.BEFORE:
  6423.         if (popupRect.width > anchorRect.left &&
  6424.             anchorRect.right + popupRect.width <= availRect.width) {
  6425.           type = AnchorType.AFTER;
  6426.         }
  6427.         break;
  6428.       default:
  6429.         assert(false, 'unknown type');
  6430.     }
  6431.     // flipping done
  6432.  
  6433.     var style = popupElement.style;
  6434.     // Reset all directions.
  6435.     style.left = style.right = style.top = style.bottom = 'auto';
  6436.  
  6437.     // Primary direction
  6438.     switch (type) {
  6439.       case AnchorType.BELOW:
  6440.         if (anchorRect.bottom + popupRect.height <= availRect.height -
  6441.             BOOKMARK_BAR_HEIGHT) {
  6442.           style.top = anchorRect.bottom + 'px';
  6443.         } else {
  6444.           style.bottom = BOOKMARK_BAR_HEIGHT + 'px';
  6445.         }
  6446.         break;
  6447.       case AnchorType.ABOVE:
  6448.         if (availRect.height - anchorRect.top >= 0)
  6449.           style.bottom = availRect.height - anchorRect.top + 'px';
  6450.         else
  6451.           style.top = '0';
  6452.         break;
  6453.       case AnchorType.AFTER:
  6454.         if (anchorRect.right + popupRect.width <= availRect.width)
  6455.           style.left = anchorRect.right + 'px';
  6456.         else
  6457.           style.right = '0';
  6458.         break;
  6459.       case AnchorType.BEFORE:
  6460.         if (availRect.width - anchorRect.left >= 0)
  6461.           style.right = availRect.width - anchorRect.left + 'px';
  6462.         else
  6463.           style.left = '0';
  6464.         break;
  6465.     }
  6466.  
  6467.     // Secondary direction
  6468.     switch (type) {
  6469.       case AnchorType.BELOW:
  6470.       case AnchorType.ABOVE:
  6471.         if (invertLeftRight) {
  6472.           // align right edges
  6473.           if (anchorRect.right - popupRect.width >= 0) {
  6474.             style.right = availRect.width - anchorRect.right + 'px';
  6475.  
  6476.           // align left edges
  6477.           } else if (anchorRect.left + popupRect.width <= availRect.width) {
  6478.             style.left = anchorRect.left + 'px';
  6479.  
  6480.           // not enough room on either side
  6481.           } else {
  6482.             style.right = '0';
  6483.           }
  6484.         } else {
  6485.           // align left edges
  6486.           if (anchorRect.left + popupRect.width <= availRect.width) {
  6487.             style.left = anchorRect.left + 'px';
  6488.  
  6489.           // align right edges
  6490.           } else if (anchorRect.right - popupRect.width >= 0) {
  6491.             style.right = availRect.width - anchorRect.right + 'px';
  6492.  
  6493.           // not enough room on either side
  6494.           } else {
  6495.             style.left = '0';
  6496.           }
  6497.         }
  6498.         break;
  6499.  
  6500.       case AnchorType.AFTER:
  6501.       case AnchorType.BEFORE:
  6502.         // align top edges
  6503.         if (anchorRect.top + popupRect.height <= availRect.height) {
  6504.           style.top = anchorRect.top + 'px';
  6505.  
  6506.         // align bottom edges
  6507.         } else if (anchorRect.bottom - popupRect.height >= 0) {
  6508.           style.bottom = availRect.height - anchorRect.bottom + 'px';
  6509.  
  6510.           // not enough room on either side
  6511.         } else {
  6512.           style.top = '0';
  6513.         }
  6514.         break;
  6515.     }
  6516.   }
  6517.  
  6518.   /**
  6519.    * Positions a popup element relative to an anchor element. The popup element
  6520.    * should have position set to absolute and it should be a child of the body
  6521.    * element.
  6522.    * @param {!HTMLElement} anchorElement The element that the popup is anchored
  6523.    *     to.
  6524.    * @param {!HTMLElement} popupElement The popup element we are positioning.
  6525.    * @param {AnchorType} type The type of anchoring we want.
  6526.    * @param {boolean} invertLeftRight Whether to invert the right/left
  6527.    *     alignment.
  6528.    */
  6529.   function positionPopupAroundElement(anchorElement, popupElement, type,
  6530.                                       invertLeftRight) {
  6531.     var anchorRect = anchorElement.getBoundingClientRect();
  6532.     positionPopupAroundRect(anchorRect, popupElement, type, invertLeftRight);
  6533.   }
  6534.  
  6535.   /**
  6536.    * Positions a popup around a point.
  6537.    * @param {number} x The client x position.
  6538.    * @param {number} y The client y position.
  6539.    * @param {!HTMLElement} popupElement The popup element we are positioning.
  6540.    */
  6541.   function positionPopupAtPoint(x, y, popupElement) {
  6542.     var rect = {
  6543.       left: x,
  6544.       top: y,
  6545.       width: 0,
  6546.       height: 0,
  6547.       right: x,
  6548.       bottom: y
  6549.     };
  6550.     positionPopupAroundRect(rect, popupElement, AnchorType.BELOW);
  6551.   }
  6552.  
  6553.   // Monkey patch popup positioning methods to avoid the popup being hidden
  6554.   // by the top overlay. This is a hacky solution, but temporarily necessary
  6555.   // until we get rid of the overlay madness.
  6556.   // TODO(pedrosimonetti): Remove this code when deprecating the NTP5 Apps.
  6557.   cr.ui.positionPopupAroundElement = positionPopupAroundElement;
  6558.   cr.ui.positionPopupAtPoint = positionPopupAtPoint;
  6559. });
  6560. </script>
  6561. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6562. // Use of this source code is governed by a BSD-style license that can be
  6563. // found in the LICENSE file.
  6564.  
  6565. /**
  6566.  * @fileoverview New tab page
  6567.  * This is the main code for the new tab page. NewTabView manages page list,
  6568.  * dot list and handles apps pages callbacks from backend. It also handles
  6569.  * the layout of the Bottom Panel and the global UI states of the New Tab Page.
  6570.  */
  6571.  
  6572. // Use an anonymous function to enable strict mode just for this file (which
  6573. // will be concatenated with other files when embedded in Chrome
  6574. cr.define('ntp', function() {
  6575.   'use strict';
  6576.  
  6577.   var APP_LAUNCH = {
  6578.     // The histogram buckets (keep in sync with extension_constants.h).
  6579.     NTP_APPS_MAXIMIZED: 0,
  6580.     NTP_APPS_COLLAPSED: 1,
  6581.     NTP_APPS_MENU: 2,
  6582.     NTP_MOST_VISITED: 3,
  6583.     NTP_RECENTLY_CLOSED: 4,
  6584.     NTP_APP_RE_ENABLE: 16,
  6585.     NTP_WEBSTORE_FOOTER: 18,
  6586.     NTP_WEBSTORE_PLUS_ICON: 19,
  6587.   };
  6588.  
  6589.   /**
  6590.    * @type {number}
  6591.    * @const
  6592.    */
  6593.   var BOTTOM_PANEL_HORIZONTAL_MARGIN = 100;
  6594.  
  6595.   /**
  6596.    * The height required to show the Bottom Panel.
  6597.    * @type {number}
  6598.    * @const
  6599.    */
  6600.   var HEIGHT_FOR_BOTTOM_PANEL = 531;
  6601.  
  6602.   /**
  6603.    * The Bottom Panel width required to show 6 cols of Tiles, which is used
  6604.    * in the width computation.
  6605.    * @type {number}
  6606.    * @const
  6607.    */
  6608.   var MAX_BOTTOM_PANEL_WIDTH = 920;
  6609.  
  6610.   /**
  6611.    * The minimum width of the Bottom Panel's content.
  6612.    * @type {number}
  6613.    * @const
  6614.    */
  6615.   var MIN_BOTTOM_PANEL_CONTENT_WIDTH = 200;
  6616.  
  6617.   /**
  6618.    * The minimum Bottom Panel width. If the available width is smaller than
  6619.    * this value, then the width of the Bottom Panel's content will be fixed to
  6620.    * MIN_BOTTOM_PANEL_CONTENT_WIDTH.
  6621.    * @type {number}
  6622.    * @const
  6623.    */
  6624.   var MIN_BOTTOM_PANEL_WIDTH = 300;
  6625.  
  6626.   /**
  6627.    * The normal Bottom Panel width. If the window width is greater than or
  6628.    * equal to this value, then the width of the Bottom Panel's content will be
  6629.    * the available width minus side margin. If the available width is smaller
  6630.    * than this value, then the width of the Bottom Panel's content will be an
  6631.    * interpolation between the normal width, and the minimum width defined by
  6632.    * the constant MIN_BOTTOM_PANEL_CONTENT_WIDTH.
  6633.    * @type {number}
  6634.    * @const
  6635.    */
  6636.   var NORMAL_BOTTOM_PANEL_WIDTH = 500;
  6637.  
  6638.   /**
  6639.    * @type {number}
  6640.    * @const
  6641.    */
  6642.   var TILE_ROW_HEIGHT = 100;
  6643.  
  6644.   //----------------------------------------------------------------------------
  6645.  
  6646.   /**
  6647.    * NewTabView instance.
  6648.    * @type {!Object|undefined}
  6649.    */
  6650.   var newTabView;
  6651.  
  6652.   /**
  6653.    * The 'notification-container' element.
  6654.    * @type {!Element|undefined}
  6655.    */
  6656.   var notificationContainer;
  6657.  
  6658.   /**
  6659.    * If non-null, an info bubble for showing messages to the user. It points at
  6660.    * the Most Visited label, and is used to draw more attention to the
  6661.    * navigation dot UI.
  6662.    * @type {!Element|undefined}
  6663.    */
  6664.   var promoBubble;
  6665.  
  6666.   /**
  6667.    * The total number of thumbnails that were hovered over.
  6668.    * @type {number}
  6669.    * @private
  6670.    */
  6671.   var hoveredThumbnailCount = 0;
  6672.  
  6673.   /**
  6674.    * The time when all sections are ready.
  6675.    * @type {number|undefined}
  6676.    * @private
  6677.    */
  6678.   var startTime;
  6679.  
  6680.   /**
  6681.    * The top position of the Bottom Panel.
  6682.    * @type {number|undefined}
  6683.    * @private
  6684.    */
  6685.   var bottomPanelOffsetTop;
  6686.  
  6687.   /**
  6688.    * The height of the Bottom Panel Header, in pixels.
  6689.    * @type {number|undefined}
  6690.    * @private
  6691.    */
  6692.   var headerHeight;
  6693.  
  6694.   /**
  6695.    * The time in milliseconds for most transitions.  This should match what's
  6696.    * in new_tab.css.  Unfortunately there's no better way to try to time
  6697.    * something to occur until after a transition has completed.
  6698.    * @type {number}
  6699.    * @const
  6700.    */
  6701.   var DEFAULT_TRANSITION_TIME = 500;
  6702.  
  6703.   /**
  6704.    * See description for these values in ntp_stats.h.
  6705.    * @enum {number}
  6706.    */
  6707.   var NtpFollowAction = {
  6708.     CLICKED_TILE: 11,
  6709.     CLICKED_OTHER_NTP_PANE: 12,
  6710.     OTHER: 13,
  6711.   };
  6712.  
  6713.   /**
  6714.    * Creates a NewTabView object.
  6715.    * @constructor
  6716.    */
  6717.   function NewTabView() {
  6718.     this.initialize(getRequiredElement('page-list'),
  6719.                     getRequiredElement('dot-list'),
  6720.                     getRequiredElement('card-slider-frame'));
  6721.   }
  6722.  
  6723.   NewTabView.prototype = {
  6724.     /**
  6725.      * The CardSlider object to use for changing app pages.
  6726.      * @type {CardSlider|undefined}
  6727.      */
  6728.     cardSlider: undefined,
  6729.  
  6730.     /**
  6731.      * The frame div for this.cardSlider.
  6732.      * @type {!Element|undefined}
  6733.      */
  6734.     sliderFrame: undefined,
  6735.  
  6736.     /**
  6737.      * The 'page-list' element.
  6738.      * @type {!Element|undefined}
  6739.      */
  6740.     pageList: undefined,
  6741.  
  6742.     /**
  6743.      * A list of all 'tile-page' elements.
  6744.      * @type {!NodeList|undefined}
  6745.      */
  6746.     tilePages: undefined,
  6747.  
  6748.     /**
  6749.      * The Apps page.
  6750.      * @type {!Element|undefined}
  6751.      */
  6752.     appsPage: undefined,
  6753.  
  6754.     /**
  6755.      * The Most Visited page.
  6756.      * @type {!Element|undefined}
  6757.      */
  6758.     mostVisitedPage: undefined,
  6759.  
  6760.     /**
  6761.      * The Recently Closed page.
  6762.      * @type {!Element|undefined}
  6763.      */
  6764.     recentlyClosedPage: undefined,
  6765.  
  6766.     /**
  6767.      * The Devices page.
  6768.      * @type {!Element|undefined}
  6769.      */
  6770.     otherDevicesPage: undefined,
  6771.  
  6772.     /**
  6773.      * The 'dots-list' element.
  6774.      * @type {!Element|undefined}
  6775.      */
  6776.     dotList: undefined,
  6777.  
  6778.     /**
  6779.      * The type of page that is currently shown. The value is a numerical ID.
  6780.      * @type {number}
  6781.      */
  6782.     shownPage: 0,
  6783.  
  6784.     /**
  6785.      * The index of the page that is currently shown, within the page type.
  6786.      * For example if the third Apps page is showing, this will be 2.
  6787.      * @type {number}
  6788.      */
  6789.     shownPageIndex: 0,
  6790.  
  6791.     /**
  6792.      * If non-null, this is the ID of the app to highlight to the user the next
  6793.      * time getAppsCallback runs. "Highlight" in this case means to switch to
  6794.      * the page and run the new tile animation.
  6795.      * @type {?string}
  6796.      */
  6797.     highlightAppId: null,
  6798.  
  6799.     /**
  6800.      * Initializes new tab view.
  6801.      * @param {!Element} pageList A DIV element to host all pages.
  6802.      * @param {!Element} dotList An UL element to host nav dots. Each dot
  6803.      *     represents a page.
  6804.      * @param {!Element} cardSliderFrame The card slider frame that hosts
  6805.      *     pageList.
  6806.      */
  6807.     initialize: function(pageList, dotList, cardSliderFrame) {
  6808.       this.pageList = pageList;
  6809.  
  6810.       this.dotList = dotList;
  6811.       cr.ui.decorate(this.dotList, ntp.DotList);
  6812.  
  6813.       this.shownPage = loadTimeData.getInteger('shown_page_type');
  6814.       this.shownPageIndex = loadTimeData.getInteger('shown_page_index');
  6815.  
  6816.       if (loadTimeData.getBoolean('showApps')) {
  6817.         // When the Apps Page is available, then the dot list should be visible.
  6818.         this.dotList.removeAttribute('hidden');
  6819.         // Request data on the apps so we can fill them in.
  6820.         // Note that this is kicked off asynchronously.  'getAppsCallback' will
  6821.         // be invoked at some point after this function returns.
  6822.         chrome.send('getApps');
  6823.       } else if (this.shownPage == loadTimeData.getInteger('apps_page_id')) {
  6824.         // No apps page.
  6825.         this.setShownPage_(
  6826.             loadTimeData.getInteger('most_visited_page_id'), 0);
  6827.       }
  6828.  
  6829.       this.tilePages = this.pageList.getElementsByClassName('tile-page');
  6830.  
  6831.       // Initialize the cardSlider without any cards at the moment.
  6832.       this.sliderFrame = cardSliderFrame;
  6833.       this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList,
  6834.           this.sliderFrame.offsetWidth);
  6835.  
  6836.       var cardSlider = this.cardSlider;
  6837.       this.cardSlider.initialize(
  6838.           loadTimeData.getBoolean('isSwipeTrackingFromScrollEventsEnabled'));
  6839.  
  6840.       // Prevent touch events from triggering any sort of native scrolling.
  6841.       document.addEventListener('touchmove', function(e) {
  6842.         e.preventDefault();
  6843.       }, true);
  6844.  
  6845.       // Handle events from the card slider.
  6846.       this.pageList.addEventListener('cardSlider:card_changed',
  6847.           this.onCardChanged_.bind(this));
  6848.       this.pageList.addEventListener('cardSlider:card_added',
  6849.           this.onCardAdded_.bind(this));
  6850.       this.pageList.addEventListener('cardSlider:card_removed',
  6851.          this.onCardRemoved_.bind(this));
  6852.  
  6853.       // Update apps when online state changes.
  6854.       window.addEventListener('online',
  6855.           this.updateOfflineEnabledApps_.bind(this));
  6856.       window.addEventListener('offline',
  6857.           this.updateOfflineEnabledApps_.bind(this));
  6858.     },
  6859.  
  6860.     /**
  6861.      * Starts listening to user input events. The resize and keydown events
  6862.      * must be added only when all NTP have finished loading because they
  6863.      * will act in the current selected page.
  6864.      */
  6865.     onReady: function() {
  6866.       window.addEventListener('resize', this.onWindowResize_.bind(this));
  6867.       document.addEventListener('keydown', this.onDocKeyDown_.bind(this));
  6868.     },
  6869.  
  6870.     /**
  6871.      * Appends a tile page.
  6872.      *
  6873.      * @param {TilePage} page The page element.
  6874.      * @param {string} title The title of the tile page.
  6875.      * @param {TilePage=} opt_refNode Optional reference node to insert in front
  6876.      *     of.
  6877.      * When opt_refNode is falsey, |page| will just be appended to the end of
  6878.      * the page list.
  6879.      */
  6880.     appendTilePage: function(page, title, opt_refNode) {
  6881.       if (opt_refNode) {
  6882.         var refIndex = this.getTilePageIndex(opt_refNode);
  6883.         this.cardSlider.addCardAtIndex(page, refIndex);
  6884.       } else {
  6885.         this.cardSlider.appendCard(page);
  6886.       }
  6887.  
  6888.       // Remember special MostVisitedPage.
  6889.       if (typeof ntp.MostVisitedPage != 'undefined' &&
  6890.           page instanceof ntp.MostVisitedPage) {
  6891.         assert(this.tilePages.length == 1,
  6892.                'MostVisitedPage should be added as first tile page');
  6893.         this.mostVisitedPage = page;
  6894.       }
  6895.  
  6896.       if (typeof ntp.AppsPage != 'undefined' &&
  6897.           page instanceof ntp.AppsPage) {
  6898.         this.appsPage = page;
  6899.       }
  6900.  
  6901.       if (typeof ntp.RecentlyClosedPage != 'undefined' &&
  6902.           page instanceof ntp.RecentlyClosedPage) {
  6903.         this.recentlyClosedPage = page;
  6904.       }
  6905.  
  6906.       // Remember special OtherDevicesPage.
  6907.       if (typeof ntp.OtherDevicesPage != 'undefined' &&
  6908.           page instanceof ntp.OtherDevicesPage) {
  6909.         this.otherDevicesPage = page;
  6910.       }
  6911.  
  6912.       // Make a deep copy of the dot template to add a new one.
  6913.       var newDot = new ntp.NavDot(page, title);
  6914.       page.navigationDot = newDot;
  6915.       this.dotList.insertBefore(newDot,
  6916.                                 opt_refNode ? opt_refNode.navigationDot : null);
  6917.       // Set a tab index on the first dot.
  6918.       if (this.dotList.dots.length == 1)
  6919.         newDot.tabIndex = 3;
  6920.     },
  6921.  
  6922.     /**
  6923.      * Called by chrome when an app has changed positions.
  6924.      * @param {Object} data The data for the app. This contains page and
  6925.      *     position indices.
  6926.      */
  6927.     appMoved: function(data) {
  6928.       assert(loadTimeData.getBoolean('showApps'));
  6929.  
  6930.       var app = $(data.id);
  6931.       assert(app, 'trying to move an app that doesn\'t exist');
  6932.       app.remove(false);
  6933.  
  6934.       this.appsPage.insertApp(data, false);
  6935.     },
  6936.  
  6937.     /**
  6938.      * Called by chrome when an existing app has been disabled or
  6939.      * removed/uninstalled from chrome.
  6940.      * @param {Object} data A data structure full of relevant information for
  6941.      *     the app.
  6942.      * @param {boolean} isUninstall True if the app is being uninstalled;
  6943.      *     false if the app is being disabled.
  6944.      * @param {boolean} fromPage True if the removal was from the current page.
  6945.      */
  6946.     appRemoved: function(data, isUninstall, fromPage) {
  6947.       assert(loadTimeData.getBoolean('showApps'));
  6948.  
  6949.       var app = $(data.id);
  6950.       assert(app, 'trying to remove an app that doesn\'t exist');
  6951.  
  6952.       if (!isUninstall)
  6953.         app.replaceAppData(data);
  6954.       else
  6955.         app.remove(!!fromPage);
  6956.     },
  6957.  
  6958.     /**
  6959.      * @return {boolean} If the page is still starting up.
  6960.      * @private
  6961.      */
  6962.     isStartingUp_: function() {
  6963.       return document.documentElement.classList.contains('starting-up');
  6964.     },
  6965.  
  6966.     /**
  6967.      * Tracks whether apps have been loaded at least once.
  6968.      * @type {boolean}
  6969.      * @private
  6970.      */
  6971.     appsLoaded_: false,
  6972.  
  6973.     /**
  6974.      * Callback invoked by chrome with the apps available.
  6975.      *
  6976.      * Note that calls to this function can occur at any time, not just in
  6977.      * response to a getApps request. For example, when a user
  6978.      * installs/uninstalls an app on another synchronized devices.
  6979.      * @param {Object} data An object with all the data on available
  6980.      *        applications.
  6981.      */
  6982.     getAppsCallback: function(data) {
  6983.       assert(loadTimeData.getBoolean('showApps'));
  6984.  
  6985.       var startTime = Date.now();
  6986.  
  6987.       // Get the array of apps and add any special synthesized entries.
  6988.       var apps = data.apps;
  6989.  
  6990.       // Sort alphabetically.
  6991.       apps.sort(function(a, b) {
  6992.         return a.title.toLocaleLowerCase() > b.title.toLocaleLowerCase() ? 1 :
  6993.           a.title.toLocaleLowerCase() < b.title.toLocaleLowerCase() ? -1 : 0;
  6994.       });
  6995.  
  6996.       // An app to animate (in case it was just installed).
  6997.       var highlightApp;
  6998.  
  6999.       if (this.appsPage) {
  7000.         this.appsPage.removeAllTiles();
  7001.       } else {
  7002.         var page = new ntp.AppsPage();
  7003.         page.setDataList(apps);
  7004.         this.appendTilePage(page, loadTimeData.getString('appDefaultPageName'));
  7005.       }
  7006.  
  7007.       for (var i = 0; i < apps.length; i++) {
  7008.         var app = apps[i];
  7009.         if (app.id == this.highlightAppId)
  7010.           highlightApp = app;
  7011.         else
  7012.           this.appsPage.insertApp(app, false);
  7013.       }
  7014.  
  7015.       if (highlightApp)
  7016.         this.appAdded(highlightApp, true);
  7017.  
  7018.       logEvent('apps.layout: ' + (Date.now() - startTime));
  7019.  
  7020.       // Tell the slider about the pages and mark the current page.
  7021.       this.updateSliderCards();
  7022.  
  7023.       if (!this.appsLoaded_) {
  7024.         this.appsLoaded_ = true;
  7025.         cr.dispatchSimpleEvent(document, 'sectionready', true, true);
  7026.       }
  7027.     },
  7028.  
  7029.     /**
  7030.      * Called by chrome when a new app has been added to chrome or has been
  7031.      * enabled if previously disabled.
  7032.      * @param {Object} data A data structure full of relevant information for
  7033.      *     the app.
  7034.      * @param {boolean=} opt_highlight Whether the app about to be added should
  7035.      *     be highlighted.
  7036.      */
  7037.     appAdded: function(data, opt_highlight) {
  7038.       assert(loadTimeData.getBoolean('showApps'));
  7039.  
  7040.       if (data.id == this.highlightAppId) {
  7041.         opt_highlight = true;
  7042.         this.highlightAppId = null;
  7043.       }
  7044.  
  7045.       if (!this.appsLoaded_)
  7046.         opt_highlight = false;
  7047.  
  7048.       var app = $(data.id);
  7049.       if (app) {
  7050.         app.replaceAppData(data);
  7051.       } else if (opt_highlight) {
  7052.         this.appsPage.insertAndHighlightApp(data);
  7053.         this.setShownPage_(loadTimeData.getInteger('apps_page_id'),
  7054.                            data.page_index);
  7055.       } else {
  7056.         this.appsPage.insertApp(data, false);
  7057.       }
  7058.     },
  7059.  
  7060.     /**
  7061.      * Callback invoked by chrome whenever an app preference changes.
  7062.      * @param {Object} data An object with all the data on available
  7063.      *     applications.
  7064.      */
  7065.     appsPrefChangedCallback: function(data) {
  7066.       assert(loadTimeData.getBoolean('showApps'));
  7067.  
  7068.       for (var i = 0; i < data.apps.length; ++i) {
  7069.         var element = $(data.apps[i].id);
  7070.         if (element)
  7071.           element.data = data.apps[i];
  7072.       }
  7073.     },
  7074.  
  7075.     /**
  7076.      * Invoked whenever the pages in page-list have changed so that the
  7077.      * CardSlider knows about the new elements.
  7078.      */
  7079.     updateSliderCards: function() {
  7080.       var pageNo = Math.max(0, Math.min(this.cardSlider.currentCard,
  7081.                                         this.tilePages.length - 1));
  7082.       this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages),
  7083.                                pageNo);
  7084.       switch (this.shownPage) {
  7085.         case loadTimeData.getInteger('apps_page_id'):
  7086.           this.cardSlider.selectCardByValue(this.appsPage);
  7087.           break;
  7088.         case loadTimeData.getInteger('most_visited_page_id'):
  7089.           if (this.mostVisitedPage)
  7090.             this.cardSlider.selectCardByValue(this.mostVisitedPage);
  7091.           break;
  7092.       }
  7093.     },
  7094.  
  7095.     /**
  7096.      * Handler for cardSlider:card_changed events from this.cardSlider.
  7097.      * @param {Event} e The cardSlider:card_changed event.
  7098.      * @private
  7099.      */
  7100.     onCardChanged_: function(e) {
  7101.       var page = e.cardSlider.currentCardValue;
  7102.  
  7103.       // Don't change shownPage until startup is done (and page changes actually
  7104.       // reflect user actions).
  7105.       if (!this.isStartingUp_()) {
  7106.         if (page.classList.contains('apps-page')) {
  7107.           this.setShownPage_(loadTimeData.getInteger('apps_page_id'), 0);
  7108.         } else if (page.classList.contains('most-visited-page')) {
  7109.           this.setShownPage_(
  7110.               loadTimeData.getInteger('most_visited_page_id'), 0);
  7111.         } else if (page.classList.contains('recently-closed-page')) {
  7112.           this.setShownPage_(
  7113.               loadTimeData.getInteger('recently_closed_page_id'), 0);
  7114.         } else if (page.classList.contains('other-devices-page')) {
  7115.           this.setShownPage_(
  7116.               loadTimeData.getInteger('other_devices_page_id'), 0);
  7117.         } else {
  7118.           console.error('unknown page selected');
  7119.         }
  7120.       }
  7121.  
  7122.       // Update the active dot
  7123.       var curDot = this.dotList.getElementsByClassName('selected')[0];
  7124.       if (curDot)
  7125.         curDot.classList.remove('selected');
  7126.       page.navigationDot.classList.add('selected');
  7127.     },
  7128.  
  7129.     /**
  7130.      * Saves/updates the newly selected page to open when first loading the NTP.
  7131.      * @type {number} shownPage The new shown page type.
  7132.      * @type {number} shownPageIndex The new shown page index.
  7133.      * @private
  7134.      */
  7135.     setShownPage_: function(shownPage, shownPageIndex) {
  7136.       assert(shownPageIndex >= 0);
  7137.       this.shownPage = shownPage;
  7138.       this.shownPageIndex = shownPageIndex;
  7139.       chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
  7140.     },
  7141.  
  7142.     /**
  7143.      * Listen for card additions to update the current card accordingly.
  7144.      * @param {Event} e A card removed or added event.
  7145.      */
  7146.     onCardAdded_: function(e) {
  7147.       var page = e.addedCard;
  7148.       // When the second arg passed to insertBefore is falsey, it acts just like
  7149.       // appendChild.
  7150.       this.pageList.insertBefore(page, this.tilePages[e.addedIndex]);
  7151.       this.layout(false, page);
  7152.       this.onCardAddedOrRemoved_();
  7153.     },
  7154.  
  7155.     /**
  7156.      * Listen for card removals to update the current card accordingly.
  7157.      * @param {Event} e A card removed or added event.
  7158.      */
  7159.     onCardRemoved_: function(e) {
  7160.       e.removedCard.remove();
  7161.       this.onCardAddedOrRemoved_();
  7162.     },
  7163.  
  7164.     /**
  7165.      * Called when a card is removed or added.
  7166.      * @private
  7167.      */
  7168.     onCardAddedOrRemoved_: function() {
  7169.       if (this.isStartingUp_())
  7170.         return;
  7171.  
  7172.       // Without repositioning there were issues - http://crbug.com/133457.
  7173.       this.cardSlider.repositionFrame();
  7174.     },
  7175.  
  7176.     /**
  7177.      * Window resize handler.
  7178.      * @private
  7179.      */
  7180.     onWindowResize_: function(e) {
  7181.       this.cardSlider.resize(this.sliderFrame.offsetWidth);
  7182.       this.layout(true);
  7183.     },
  7184.  
  7185.     /**
  7186.      * Handler for key events on the page. Ctrl-Arrow will switch the visible
  7187.      * page.
  7188.      * @param {Event} e The KeyboardEvent.
  7189.      * @private
  7190.      */
  7191.     onDocKeyDown_: function(e) {
  7192.       if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey)
  7193.         return;
  7194.  
  7195.       var direction = 0;
  7196.       if (e.keyIdentifier == 'Left')
  7197.         direction = -1;
  7198.       else if (e.keyIdentifier == 'Right')
  7199.         direction = 1;
  7200.       else
  7201.         return;
  7202.  
  7203.       var cardIndex =
  7204.           (this.cardSlider.currentCard + direction +
  7205.            this.cardSlider.cardCount) % this.cardSlider.cardCount;
  7206.       this.cardSlider.selectCard(cardIndex, true);
  7207.  
  7208.       e.stopPropagation();
  7209.     },
  7210.  
  7211.     /**
  7212.      * Listener for offline status change events. Updates apps that are
  7213.      * not offline-enabled to be grayscale if the browser is offline.
  7214.      * @private
  7215.      */
  7216.     updateOfflineEnabledApps_: function() {
  7217.       var apps = document.querySelectorAll('.app');
  7218.       for (var i = 0; i < apps.length; ++i) {
  7219.         if (apps[i].data.enabled && !apps[i].data.offline_enabled) {
  7220.           apps[i].setIcon();
  7221.           apps[i].loadIcon();
  7222.         }
  7223.       }
  7224.     },
  7225.  
  7226.     /**
  7227.      * Returns the index of a given tile page.
  7228.      * @param {TilePage} page The TilePage we wish to find.
  7229.      * @return {number} The index of |page| or -1 if it is not in the
  7230.      *    collection.
  7231.      */
  7232.     getTilePageIndex: function(page) {
  7233.       return Array.prototype.indexOf.call(this.tilePages, page);
  7234.     },
  7235.  
  7236.     /**
  7237.      * Removes a page and navigation dot (if the navdot exists).
  7238.      * @param {TilePage} page The page to be removed.
  7239.      */
  7240.     removeTilePageAndDot_: function(page) {
  7241.       if (page.navigationDot)
  7242.         page.navigationDot.remove();
  7243.       this.cardSlider.removeCard(page);
  7244.     },
  7245.  
  7246.     /**
  7247.      * The width of the Bottom Panel's content.
  7248.      * @type {number}
  7249.      */
  7250.     contentWidth_: 0,
  7251.  
  7252.     /**
  7253.      * Calculates the layout of the NTP's Bottom Panel. This method will resize
  7254.      * and position all container elements in the Bottom Panel. At the end of
  7255.      * the layout process it will dispatch the layout method to the current
  7256.      * selected TilePage. Alternatively, you can pass a specific TilePage in
  7257.      * the |opt_page| parameter, which is useful for initializing the layout
  7258.      * of a recently created TilePage.
  7259.      *
  7260.      * The |NewTabView.layout| deals with the global layout state while the
  7261.      * |TilePage.layout| deals with the per-page layout state. A general rule
  7262.      * would be: if you need to resize any element which is outside the
  7263.      * card-slider-frame, it should be handled here in NewTabView. Otherwise,
  7264.      * it should be handled in TilePage.
  7265.      *
  7266.      * @param {boolean=} opt_animate Whether the layout should be animated.
  7267.      * @param {ntp.TilePage=} opt_page Alternative TilePage to calculate layout.
  7268.      */
  7269.     layout: function(opt_animate, opt_page) {
  7270.       opt_animate = typeof opt_animate == 'undefined' ? false : opt_animate;
  7271.  
  7272.       var viewHeight = cr.doc.documentElement.clientHeight;
  7273.       var isBottomPanelVisible = viewHeight >= HEIGHT_FOR_BOTTOM_PANEL;
  7274.       // Toggles the visibility of the Bottom Panel when there is (or there
  7275.       // is not) space to show the entire panel.
  7276.       this.showBottomPanel_(isBottomPanelVisible);
  7277.  
  7278.       // The layout calculation can be skipped if Bottom Panel is not visible.
  7279.       if (!isBottomPanelVisible && !opt_page)
  7280.         return;
  7281.  
  7282.       // Calculates the width of the Bottom Panel's Content.
  7283.       var width = this.calculateContentWidth_();
  7284.       if (width != this.contentWidth_) {
  7285.         this.contentWidth_ = width;
  7286.         $('bottom-panel-footer').style.width = width + 'px';
  7287.       }
  7288.  
  7289.       // Finally, dispatch the layout method to the current page.
  7290.       var currentPage = opt_page || this.cardSlider.currentCardValue;
  7291.  
  7292.       var contentHeight = TILE_ROW_HEIGHT;
  7293.       if (!opt_page && currentPage.config.scrollable) {
  7294.         contentHeight = viewHeight - bottomPanelOffsetTop -
  7295.             headerHeight - $('bottom-panel-footer').offsetHeight;
  7296.         contentHeight = Math.max(TILE_ROW_HEIGHT, contentHeight);
  7297.       }
  7298.       this.contentHeight_ = contentHeight;
  7299.  
  7300.       $('card-slider-frame').style.height = contentHeight + 'px';
  7301.  
  7302.       currentPage.layout(opt_animate);
  7303.     },
  7304.  
  7305.     /**
  7306.      * @return {number} The height of the Bottom Panel's content.
  7307.      */
  7308.     get contentHeight() {
  7309.       return this.contentHeight_;
  7310.     },
  7311.  
  7312.     /**
  7313.      * @return {number} The width of the Bottom Panel's content.
  7314.      */
  7315.     get contentWidth() {
  7316.       return this.contentWidth_;
  7317.     },
  7318.  
  7319.     /**
  7320.      * @return {number} The width of the Bottom Panel's content.
  7321.      * @private
  7322.      */
  7323.     calculateContentWidth_: function() {
  7324.       var windowWidth = cr.doc.documentElement.clientWidth;
  7325.       var margin = 2 * BOTTOM_PANEL_HORIZONTAL_MARGIN;
  7326.  
  7327.       var width;
  7328.       if (windowWidth >= MAX_BOTTOM_PANEL_WIDTH) {
  7329.         width = MAX_BOTTOM_PANEL_WIDTH - margin;
  7330.       } else if (windowWidth >= NORMAL_BOTTOM_PANEL_WIDTH) {
  7331.         width = windowWidth - margin;
  7332.       } else if (windowWidth >= MIN_BOTTOM_PANEL_WIDTH) {
  7333.         // Interpolation between the previous and next states.
  7334.         var minMargin = MIN_BOTTOM_PANEL_WIDTH - MIN_BOTTOM_PANEL_CONTENT_WIDTH;
  7335.         var factor = (windowWidth - MIN_BOTTOM_PANEL_WIDTH) /
  7336.             (NORMAL_BOTTOM_PANEL_WIDTH - MIN_BOTTOM_PANEL_WIDTH);
  7337.         var interpolatedMargin = minMargin + factor * (margin - minMargin);
  7338.         width = windowWidth - interpolatedMargin;
  7339.       } else {
  7340.         width = MIN_BOTTOM_PANEL_CONTENT_WIDTH;
  7341.       }
  7342.  
  7343.       return width;
  7344.     },
  7345.  
  7346.     /**
  7347.      * Animates the display of the Bottom Panel.
  7348.      * @param {boolean} show Whether or not to show the Bottom Panel.
  7349.      */
  7350.     showBottomPanel_: function(show) {
  7351.       $('bottom-panel').classList.toggle('hide-bottom-panel', !show);
  7352.     },
  7353.   };
  7354.  
  7355.   /**
  7356.    * Invoked at startup once the DOM is available to initialize the app.
  7357.    */
  7358.   function onLoad() {
  7359.  
  7360.     if (!loadTimeData.getBoolean('showApps'))
  7361.       cr.dispatchSimpleEvent(document, 'sectionready', true, true);
  7362.  
  7363.     // Load the current theme colors.
  7364.     themeChanged();
  7365.  
  7366.     newTabView = new NewTabView();
  7367.  
  7368.     bottomPanelOffsetTop = $('bottom-panel').offsetTop;
  7369.     headerHeight = $('bottom-panel-header').offsetHeight;
  7370.  
  7371.     notificationContainer = getRequiredElement('notification-container');
  7372.  
  7373.     var mostVisited = new ntp.MostVisitedPage();
  7374.     newTabView.appendTilePage(mostVisited,
  7375.                               loadTimeData.getString('mostvisited'));
  7376.     chrome.send('getMostVisited');
  7377.  
  7378.     if (loadTimeData.valueExists('bubblePromoText')) {
  7379.       promoBubble = new cr.ui.Bubble;
  7380.       promoBubble.anchorNode = getRequiredElement('promo-bubble-anchor');
  7381.       promoBubble.arrowLocation = cr.ui.ArrowLocation.BOTTOM_START;
  7382.       promoBubble.bubbleAlignment = cr.ui.BubbleAlignment.ENTIRELY_VISIBLE;
  7383.       promoBubble.deactivateToDismissDelay = 2000;
  7384.       promoBubble.content = parseHtmlSubset(
  7385.           loadTimeData.getString('bubblePromoText'), ['BR']);
  7386.  
  7387.       var bubbleLink = promoBubble.querySelector('a');
  7388.       if (bubbleLink) {
  7389.         bubbleLink.addEventListener('click', function(e) {
  7390.           chrome.send('bubblePromoLinkClicked');
  7391.         });
  7392.       }
  7393.  
  7394.       promoBubble.handleCloseEvent = function() {
  7395.         promoBubble.hide();
  7396.         chrome.send('bubblePromoClosed');
  7397.       };
  7398.       promoBubble.show();
  7399.       chrome.send('bubblePromoViewed');
  7400.     }
  7401.  
  7402.     doWhenAllSectionsReady(function() {
  7403.       // Tell the slider about the pages.
  7404.       newTabView.updateSliderCards();
  7405.       newTabView.onReady();
  7406.  
  7407.       // Restore the visibility only after calling updateSliderCards to avoid
  7408.       // flickering, otherwise for a small fraction of a second the Page List is
  7409.       // partially rendered.
  7410.       $('bottom-panel').style.visibility = 'visible';
  7411.  
  7412.       if (loadTimeData.valueExists('notificationPromoText')) {
  7413.         var promoText = loadTimeData.getString('notificationPromoText');
  7414.         var tags = ['IMG'];
  7415.         var attrs = {
  7416.           src: function(node, value) {
  7417.             return node.tagName == 'IMG' &&
  7418.                    /^data\:image\/(?:png|gif|jpe?g)/.test(value);
  7419.           },
  7420.         };
  7421.  
  7422.         var promo = parseHtmlSubset(promoText, tags, attrs);
  7423.         var promoLink = promo.querySelector('a');
  7424.         if (promoLink) {
  7425.           promoLink.addEventListener('click', function(e) {
  7426.             chrome.send('notificationPromoLinkClicked');
  7427.           });
  7428.         }
  7429.  
  7430.         showNotification(promo, [], function() {
  7431.           chrome.send('notificationPromoClosed');
  7432.         }, 60000);
  7433.         chrome.send('notificationPromoViewed');
  7434.       }
  7435.  
  7436.       cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true);
  7437.       document.documentElement.classList.remove('starting-up');
  7438.  
  7439.       startTime = Date.now();
  7440.     });
  7441.   }
  7442.  
  7443.   /*
  7444.    * The number of sections to wait on.
  7445.    * @type {number}
  7446.    */
  7447.   var sectionsToWaitFor = 2;
  7448.  
  7449.   /**
  7450.    * Queued callbacks which lie in wait for all sections to be ready.
  7451.    * @type {!Array}
  7452.    */
  7453.   var readyCallbacks = [];
  7454.  
  7455.   /**
  7456.    * Fired as each section of pages becomes ready.
  7457.    * @param {Event} e Each page's synthetic DOM event.
  7458.    */
  7459.   document.addEventListener('sectionready', function(e) {
  7460.     if (--sectionsToWaitFor <= 0) {
  7461.       while (readyCallbacks.length) {
  7462.         readyCallbacks.shift()();
  7463.       }
  7464.     }
  7465.   });
  7466.  
  7467.   /**
  7468.    * This is used to simulate a fire-once event (i.e. $(document).ready() in
  7469.    * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback
  7470.    * is fired right away. If all pages are not ready yet, the function is queued
  7471.    * for later execution.
  7472.    * @param {function} callback The work to be done when ready.
  7473.    */
  7474.   function doWhenAllSectionsReady(callback) {
  7475.     assert(typeof callback == 'function');
  7476.     if (sectionsToWaitFor > 0)
  7477.       readyCallbacks.push(callback);
  7478.     else
  7479.       window.setTimeout(callback, 0);  // Do soon after, but asynchronously.
  7480.   }
  7481.  
  7482.   function themeChanged(opt_hasAttribution) {
  7483.     $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now();
  7484.  
  7485.     if (typeof opt_hasAttribution != 'undefined') {
  7486.       document.documentElement.setAttribute('hasattribution',
  7487.                                             opt_hasAttribution);
  7488.     }
  7489.  
  7490.     updateAttribution();
  7491.   }
  7492.  
  7493.   function setBookmarkBarAttached(attached) {
  7494.     document.documentElement.setAttribute('bookmarkbarattached', attached);
  7495.   }
  7496.  
  7497.   /**
  7498.    * Attributes the attribution image at the bottom left.
  7499.    */
  7500.   function updateAttribution() {
  7501.     var attribution = $('attribution');
  7502.     if (document.documentElement.getAttribute('hasattribution') == 'true') {
  7503.       $('attribution-img').src =
  7504.           'chrome://theme/IDR_THEME_NTP_ATTRIBUTION?' + Date.now();
  7505.       attribution.hidden = false;
  7506.     } else {
  7507.       attribution.hidden = true;
  7508.     }
  7509.   }
  7510.  
  7511.   /**
  7512.    * Timeout ID.
  7513.    * @type {number}
  7514.    */
  7515.   var notificationTimeout = 0;
  7516.  
  7517.   /**
  7518.    * Shows the notification bubble.
  7519.    * @param {string|Node} message The notification message or node to use as
  7520.    *     message.
  7521.    * @param {Array.<{text: string, action: function()}>} links An array of
  7522.    *     records describing the links in the notification. Each record should
  7523.    *     have a 'text' attribute (the display string) and an 'action' attribute
  7524.    *     (a function to run when the link is activated).
  7525.    * @param {Function=} opt_closeHandler The callback invoked if the user
  7526.    *     manually dismisses the notification.
  7527.    */
  7528.   function showNotification(message, links, opt_closeHandler, opt_timeout) {
  7529.     window.clearTimeout(notificationTimeout);
  7530.  
  7531.     var span = document.querySelector('#notification > span');
  7532.     if (typeof message == 'string') {
  7533.       span.textContent = message;
  7534.     } else {
  7535.       span.textContent = '';  // Remove all children.
  7536.       span.appendChild(message);
  7537.     }
  7538.  
  7539.     var linksBin = $('notificationLinks');
  7540.     linksBin.textContent = '';
  7541.     for (var i = 0; i < links.length; i++) {
  7542.       var link = linksBin.ownerDocument.createElement('div');
  7543.       link.textContent = links[i].text;
  7544.       link.action = links[i].action;
  7545.       link.onclick = function() {
  7546.         this.action();
  7547.         hideNotification();
  7548.       };
  7549.       link.setAttribute('role', 'button');
  7550.       link.setAttribute('tabindex', 0);
  7551.       link.className = 'link-button';
  7552.       linksBin.appendChild(link);
  7553.     }
  7554.  
  7555.     function closeFunc(e) {
  7556.       if (opt_closeHandler)
  7557.         opt_closeHandler();
  7558.       hideNotification();
  7559.     }
  7560.  
  7561.     document.querySelector('#notification button').onclick = closeFunc;
  7562.     document.addEventListener('dragstart', closeFunc);
  7563.  
  7564.     notificationContainer.hidden = false;
  7565.  
  7566.     var timeout = opt_timeout || 10000;
  7567.     notificationTimeout = window.setTimeout(hideNotification, timeout);
  7568.  
  7569.     layout();
  7570.   }
  7571.  
  7572.   /**
  7573.    * Hide the notification bubble.
  7574.    */
  7575.   function hideNotification() {
  7576.     notificationContainer.hidden = true;
  7577.  
  7578.     layout();
  7579.   }
  7580.  
  7581.   function setMostVisitedPages(dataList, hasBlacklistedUrls) {
  7582.     var page = newTabView.mostVisitedPage;
  7583.     var state = page.getTileRepositioningState();
  7584.     if (state) {
  7585.       if (state.isRemoving)
  7586.         page.animateTileRemoval(state.index, dataList);
  7587.       else
  7588.         page.animateTileRestoration(state.index, dataList);
  7589.  
  7590.       page.resetTileRepositioningState();
  7591.     } else {
  7592.       page.setDataList(dataList);
  7593.       cr.dispatchSimpleEvent(document, 'sectionready', true, true);
  7594.     }
  7595.   }
  7596.  
  7597.   /**
  7598.    * Set the dominant color for a node. This will be called in response to
  7599.    * getFaviconDominantColor. The node represented by |id| better have a setter
  7600.    * for stripeColor.
  7601.    * @param {string} id The ID of a node.
  7602.    * @param {string} color The color represented as a CSS string.
  7603.    */
  7604.   function setFaviconDominantColor(id, color) {
  7605.     var node = $(id);
  7606.     var prop = Object.getOwnPropertyDescriptor(node.__proto__, 'stripeColor');
  7607.     assert(prop && prop.set, 'Node doesn\'t have a stripeColor setter');
  7608.     if (node)
  7609.       node.stripeColor = color;
  7610.   }
  7611.  
  7612.   function getThumbnailUrl(url) {
  7613.     return 'chrome://thumb/' + url;
  7614.   }
  7615.  
  7616.   /**
  7617.    * Increments the parameter used to log the total number of thumbnail hovered
  7618.    * over.
  7619.    */
  7620.   function incrementHoveredThumbnailCount() {
  7621.     hoveredThumbnailCount++;
  7622.   }
  7623.  
  7624.   /**
  7625.    * Logs the time to click for the specified item and the total number of
  7626.    * thumbnails hovered over.
  7627.    * @param {string} item The item to log the time-to-click.
  7628.    */
  7629.   function logTimeToClickAndHoverCount(item) {
  7630.     var timeToClick = Date.now() - startTime;
  7631.     chrome.send('logTimeToClick',
  7632.         ['ExtendedNewTabPage.TimeToClick' + item, timeToClick]);
  7633.     chrome.send('metricsHandler:recordInHistogram',
  7634.         ['ExtendedNewTabPage.hoveredThumbnailCount',
  7635.          hoveredThumbnailCount, 40]);
  7636.   }
  7637.  
  7638.   /**
  7639.    * Wrappers to forward the callback to corresponding NewTabView member.
  7640.    */
  7641.   function appAdded() {
  7642.     return newTabView.appAdded.apply(newTabView, arguments);
  7643.   }
  7644.  
  7645.   function appMoved() {
  7646.     return newTabView.appMoved.apply(newTabView, arguments);
  7647.   }
  7648.  
  7649.   function appRemoved() {
  7650.     return newTabView.appRemoved.apply(newTabView, arguments);
  7651.   }
  7652.  
  7653.   function appsPrefChangeCallback() {
  7654.     return newTabView.appsPrefChangedCallback.apply(newTabView, arguments);
  7655.   }
  7656.  
  7657.   function getAppsCallback() {
  7658.     return newTabView.getAppsCallback.apply(newTabView, arguments);
  7659.   }
  7660.  
  7661.   function getCardSlider() {
  7662.     return newTabView.cardSlider;
  7663.   }
  7664.  
  7665.   function setAppToBeHighlighted(appId) {
  7666.     newTabView.highlightAppId = appId;
  7667.   }
  7668.  
  7669.   function layout() {
  7670.     newTabView.layout.apply(newTabView, arguments);
  7671.   }
  7672.  
  7673.   function getContentHeight() {
  7674.     return newTabView.contentHeight;
  7675.   }
  7676.  
  7677.   function getContentWidth() {
  7678.     return newTabView.contentWidth;
  7679.   }
  7680.  
  7681.   function noop() {
  7682.     // Ignore some NTP4 callbacks for backwards compatibility purposes.
  7683.   }
  7684.  
  7685.   // Return an object with all the exports
  7686.   return {
  7687.     APP_LAUNCH: APP_LAUNCH,
  7688.     TILE_ROW_HEIGHT: TILE_ROW_HEIGHT,
  7689.     appAdded: appAdded,
  7690.     appMoved: appMoved,
  7691.     appRemoved: appRemoved,
  7692.     appsPrefChangeCallback: appsPrefChangeCallback,
  7693.     getAppsCallback: getAppsCallback,
  7694.     getCardSlider: getCardSlider,
  7695.     getContentHeight: getContentHeight,
  7696.     getContentWidth: getContentWidth,
  7697.     getThumbnailUrl: getThumbnailUrl,
  7698.     incrementHoveredThumbnailCount: incrementHoveredThumbnailCount,
  7699.     layout: layout,
  7700.     logTimeToClickAndHoverCount: logTimeToClickAndHoverCount,
  7701.     NtpFollowAction: NtpFollowAction,
  7702.     onLoad: onLoad,
  7703.     setAppToBeHighlighted: setAppToBeHighlighted,
  7704.     setBookmarkBarAttached: setBookmarkBarAttached,
  7705.     setFaviconDominantColor: setFaviconDominantColor,
  7706.     setForeignSessions: noop,
  7707.     setMostVisitedPages: setMostVisitedPages,
  7708.     setRecentlyClosedTabs: noop,
  7709.     showNotification: showNotification,
  7710.     themeChanged: themeChanged,
  7711.     updateLogin: noop,
  7712.   };
  7713. });
  7714.  
  7715. document.addEventListener('DOMContentLoaded', ntp.onLoad);
  7716.  
  7717. var toCssPx = cr.ui.toCssPx;
  7718. </script>
  7719. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7720. // Use of this source code is governed by a BSD-style license that can be
  7721. // found in the LICENSE file.
  7722.  
  7723. /**
  7724.  * @fileoverview DotList implementation
  7725.  */
  7726.  
  7727. cr.define('ntp', function() {
  7728.   'use strict';
  7729.  
  7730.   /**
  7731.    * Live list of the navigation dots.
  7732.    * @type {!NodeList|undefined}
  7733.    */
  7734.   var navDots;
  7735.  
  7736.   /**
  7737.    * Creates a new DotList object.
  7738.    * @constructor
  7739.    * @extends {HTMLUListElement}
  7740.    */
  7741.   var DotList = cr.ui.define('ul');
  7742.  
  7743.   DotList.prototype = {
  7744.     __proto__: HTMLUListElement.prototype,
  7745.  
  7746.     /** @override */
  7747.     decorate: function() {
  7748.       this.addEventListener('keydown', this.onKeyDown_.bind(this));
  7749.       navDots = this.getElementsByClassName('dot');
  7750.     },
  7751.  
  7752.     /**
  7753.      * Live list of the navigation dots.
  7754.      * @type {!NodeList|undefined}
  7755.      */
  7756.     get dots() {
  7757.       return navDots;
  7758.     },
  7759.  
  7760.     /**
  7761.      * Handler for key events on the dot list. These keys will change the focus
  7762.      * element.
  7763.      * @param {Event} e The KeyboardEvent.
  7764.      */
  7765.     onKeyDown_: function(e) {
  7766.       if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey)
  7767.         return;
  7768.  
  7769.       var direction = 0;
  7770.       if (e.keyIdentifier == 'Left')
  7771.         direction = -1;
  7772.       else if (e.keyIdentifier == 'Right')
  7773.         direction = 1;
  7774.       else
  7775.         return;
  7776.  
  7777.       var focusDot = this.querySelector('.dot:focus');
  7778.       if (!focusDot)
  7779.         return;
  7780.       var focusIndex = Array.prototype.indexOf.call(navDots, focusDot);
  7781.       var newFocusIndex = focusIndex + direction;
  7782.       if (focusIndex == newFocusIndex)
  7783.         return;
  7784.  
  7785.       newFocusIndex = (newFocusIndex + navDots.length) % navDots.length;
  7786.       navDots[newFocusIndex].tabIndex = 3;
  7787.       navDots[newFocusIndex].focus();
  7788.       focusDot.tabIndex = -1;
  7789.  
  7790.       e.stopPropagation();
  7791.       e.preventDefault();
  7792.     }
  7793.   };
  7794.  
  7795.   return {
  7796.     DotList: DotList
  7797.   };
  7798. });
  7799. </script>
  7800. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7801. // Use of this source code is governed by a BSD-style license that can be
  7802. // found in the LICENSE file.
  7803.  
  7804. /**
  7805.  * @fileoverview Nav dot
  7806.  * This is the class for the navigation controls that appear along the bottom
  7807.  * of the NTP.
  7808.  */
  7809.  
  7810. cr.define('ntp', function() {
  7811.   'use strict';
  7812.  
  7813.   /**
  7814.    * Creates a new navigation dot.
  7815.    * @param {TilePage} page The associated TilePage.
  7816.    * @param {string} title The title of the navigation dot.
  7817.    * @constructor
  7818.    * @extends {HTMLLIElement}
  7819.    */
  7820.   function NavDot(page, title) {
  7821.     var dot = cr.doc.createElement('li');
  7822.     dot.__proto__ = NavDot.prototype;
  7823.     dot.initialize(page, title);
  7824.  
  7825.     return dot;
  7826.   }
  7827.  
  7828.   NavDot.prototype = {
  7829.     __proto__: HTMLLIElement.prototype,
  7830.  
  7831.     initialize: function(page, title) {
  7832.       this.className = 'dot';
  7833.       this.page_ = page;
  7834.       this.textContent = title;
  7835.  
  7836.       this.addEventListener('keydown', this.onKeyDown_);
  7837.       this.addEventListener('click', this.onClick_);
  7838.     },
  7839.  
  7840.     /**
  7841.      * @return {TilePage} The associated TilePage.
  7842.      */
  7843.     get page() {
  7844.       return this.page_;
  7845.     },
  7846.  
  7847.     /**
  7848.      * Removes the dot from the page.
  7849.      */
  7850.     remove: function() {
  7851.       this.parentNode.removeChild(this);
  7852.     },
  7853.  
  7854.     /**
  7855.      * Navigates the card slider to the page for this dot.
  7856.      */
  7857.     switchToPage: function() {
  7858.       ntp.getCardSlider().selectCardByValue(this.page_, true);
  7859.     },
  7860.  
  7861.     /**
  7862.      * Handler for keydown event on the dot.
  7863.      * @param {Event} e The KeyboardEvent.
  7864.      */
  7865.     onKeyDown_: function(e) {
  7866.       if (e.keyIdentifier == 'Enter') {
  7867.         this.onClick_(e);
  7868.         e.stopPropagation();
  7869.       }
  7870.     },
  7871.  
  7872.     /**
  7873.      * Clicking causes the associated page to show.
  7874.      * @param {Event} e The click event.
  7875.      * @private
  7876.      */
  7877.     onClick_: function(e) {
  7878.       this.switchToPage();
  7879.       e.stopPropagation();
  7880.     },
  7881.  
  7882.   };
  7883.  
  7884.   return {
  7885.     NavDot: NavDot,
  7886.   };
  7887. });
  7888. </script>
  7889. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7890. // Use of this source code is governed by a BSD-style license that can be
  7891. // found in the LICENSE file.
  7892.  
  7893. cr.define('ntp', function() {
  7894.   'use strict';
  7895.  
  7896.   /**
  7897.    * The maximum gap from the edge of the scrolling area which will display
  7898.    * the shadow with transparency. After this point the shadow will become
  7899.    * 100% opaque.
  7900.    * @type {number}
  7901.    * @const
  7902.    */
  7903.   var MAX_SCROLL_SHADOW_GAP = 16;
  7904.  
  7905.   /**
  7906.    * @type {number}
  7907.    * @const
  7908.    */
  7909.   var SCROLL_BAR_WIDTH = 12;
  7910.  
  7911.   //----------------------------------------------------------------------------
  7912.   // Tile
  7913.   //----------------------------------------------------------------------------
  7914.  
  7915.   /**
  7916.    * A virtual Tile class. Each TilePage subclass should have its own Tile
  7917.    * subclass implemented too (e.g. MostVisitedPage contains MostVisited
  7918.    * tiles, and MostVisited is a Tile subclass).
  7919.    * @constructor
  7920.    */
  7921.   function Tile() {
  7922.     console.error('Tile is a virtual class and is not supposed to be ' +
  7923.         'instantiated');
  7924.   }
  7925.  
  7926.   /**
  7927.    * Creates a Tile subclass. We need to use this function to create a Tile
  7928.    * subclass because a Tile must also subclass a HTMLElement (which can be
  7929.    * any HTMLElement), so we need to individually add methods and getters here.
  7930.    * @param {Object} Subclass The prototype object of the class we want to be
  7931.    *     a Tile subclass.
  7932.    * @param {Object} The extended Subclass object.
  7933.    */
  7934.   Tile.subclass = function(Subclass) {
  7935.     var Base = Tile.prototype;
  7936.     for (var name in Base) {
  7937.       if (!Subclass.hasOwnProperty(name))
  7938.         Subclass[name] = Base[name];
  7939.     }
  7940.     for (var name in TileGetters) {
  7941.       if (!Subclass.hasOwnProperty(name))
  7942.         Subclass.__defineGetter__(name, TileGetters[name]);
  7943.     }
  7944.     return Subclass;
  7945.   };
  7946.  
  7947.   Tile.prototype = {
  7948.     // Tile data object.
  7949.     data_: null,
  7950.  
  7951.     /**
  7952.      * Initializes a Tile.
  7953.      */
  7954.     initialize: function() {
  7955.       this.classList.add('tile');
  7956.       this.reset();
  7957.     },
  7958.  
  7959.     /**
  7960.      * Resets the tile DOM.
  7961.      */
  7962.     reset: function() {
  7963.     },
  7964.  
  7965.     /**
  7966.      * The data for this Tile.
  7967.      * @param {Object} data A dictionary of relevant data for the page.
  7968.      */
  7969.     set data(data) {
  7970.       // TODO(pedrosimonetti): Remove data.filler usage everywhere.
  7971.       if (!data || data.filler) {
  7972.         if (this.data_)
  7973.           this.reset();
  7974.         return;
  7975.       }
  7976.  
  7977.       this.data_ = data;
  7978.     },
  7979.   };
  7980.  
  7981.   var TileGetters = {
  7982.     /**
  7983.      * The TileCell associated to this Tile.
  7984.      * @type {TileCell}
  7985.      */
  7986.     'tileCell': function() {
  7987.       return findAncestorByClass(this, 'tile-cell');
  7988.     },
  7989.  
  7990.     /**
  7991.      * The index of the Tile.
  7992.      * @type {number}
  7993.      */
  7994.     'index': function() {
  7995.       assert(this.tileCell);
  7996.       return this.tileCell.index;
  7997.     },
  7998.   };
  7999.  
  8000.   //----------------------------------------------------------------------------
  8001.   // TileCell
  8002.   //----------------------------------------------------------------------------
  8003.  
  8004.   /**
  8005.    * Creates a new TileCell object. A TileCell represents a cell in the
  8006.    * TilePage's grid. A TilePage uses TileCells to position Tiles in the proper
  8007.    * place and to animate them individually. Each TileCell is associated to
  8008.    * one Tile at a time (or none if it is a filler object), and that association
  8009.    * might change when the grid is resized. When that happens, the grid is
  8010.    * updated and the Tiles are moved to the proper TileCell. We cannot move the
  8011.    * the TileCell itself during the resize because this transition is animated
  8012.    * with CSS and there's no way to stop CSS animations, and we really want to
  8013.    * animate with CSS to take advantage of hardware acceleration.
  8014.    * @constructor
  8015.    * @extends {HTMLDivElement}
  8016.    * @param {HTMLElement} tile Tile element that will be associated to the cell.
  8017.    */
  8018.   function TileCell(tile) {
  8019.     var tileCell = cr.doc.createElement('div');
  8020.     tileCell.__proto__ = TileCell.prototype;
  8021.     tileCell.initialize(tile);
  8022.  
  8023.     return tileCell;
  8024.   }
  8025.  
  8026.   TileCell.prototype = {
  8027.     __proto__: HTMLDivElement.prototype,
  8028.  
  8029.     /**
  8030.      * Initializes a TileCell.
  8031.      * @param {Tile} tile The Tile that will be assigned to this TileCell.
  8032.      */
  8033.     initialize: function(tile) {
  8034.       this.className = 'tile-cell';
  8035.       this.assign(tile);
  8036.     },
  8037.  
  8038.     /**
  8039.      * The index of the TileCell.
  8040.      * @type {number}
  8041.      */
  8042.     get index() {
  8043.       return Array.prototype.indexOf.call(this.tilePage.tiles_,
  8044.           this.tile);
  8045.     },
  8046.  
  8047.     /**
  8048.      * The Tile associated to this TileCell.
  8049.      * @type {Tile}
  8050.      */
  8051.     get tile() {
  8052.       return this.firstElementChild;
  8053.     },
  8054.  
  8055.     /**
  8056.      * The TilePage associated to this TileCell.
  8057.      * @type {TilePage}
  8058.      */
  8059.     get tilePage() {
  8060.       return findAncestorByClass(this, 'tile-page');
  8061.     },
  8062.  
  8063.     /**
  8064.      * Assigns a Tile to the this TileCell.
  8065.      * @type {TilePage}
  8066.      */
  8067.     assign: function(tile) {
  8068.       if (this.tile)
  8069.         this.replaceChild(tile, this.tile);
  8070.       else
  8071.         this.appendChild(tile);
  8072.     },
  8073.  
  8074.     /**
  8075.      * Called when an app is removed from Chrome. Animates its disappearance.
  8076.      * @param {boolean=} opt_animate Whether the animation should be animated.
  8077.      */
  8078.     doRemove: function(opt_animate) {
  8079.       this.tilePage.removeTile(this.tile, false);
  8080.     },
  8081.   };
  8082.  
  8083.   //----------------------------------------------------------------------------
  8084.   // TilePage
  8085.   //----------------------------------------------------------------------------
  8086.  
  8087.   /**
  8088.    * Creates a new TilePage object. This object contains tiles and controls
  8089.    * their layout.
  8090.    * @constructor
  8091.    * @extends {HTMLDivElement}
  8092.    */
  8093.   function TilePage() {
  8094.     var el = cr.doc.createElement('div');
  8095.     el.__proto__ = TilePage.prototype;
  8096.  
  8097.     return el;
  8098.   }
  8099.  
  8100.   TilePage.prototype = {
  8101.     __proto__: HTMLDivElement.prototype,
  8102.  
  8103.     /**
  8104.      * Reference to the Tile subclass that will be used to create the tiles.
  8105.      * @constructor
  8106.      * @extends {Tile}
  8107.      */
  8108.     TileClass: Tile,
  8109.  
  8110.     // The config object should be defined by a TilePage subclass if it
  8111.     // wants the non-default behavior.
  8112.     config: {
  8113.       // The width of a cell.
  8114.       cellWidth: 110,
  8115.       // The start margin of a cell (left or right according to text direction).
  8116.       cellMarginStart: 12,
  8117.       // The maximum number of Tiles to be displayed.
  8118.       maxTileCount: 6,
  8119.       // Whether the TilePage content will be scrollable.
  8120.       scrollable: false,
  8121.     },
  8122.  
  8123.     /**
  8124.      * Initializes a TilePage.
  8125.      */
  8126.     initialize: function() {
  8127.       this.className = 'tile-page';
  8128.  
  8129.       // The div that wraps the scrollable element.
  8130.       this.frame_ = this.ownerDocument.createElement('div');
  8131.       this.frame_.className = 'tile-page-frame';
  8132.       this.appendChild(this.frame_);
  8133.  
  8134.       // The content/scrollable element.
  8135.       this.content_ = this.ownerDocument.createElement('div');
  8136.       this.content_.className = 'tile-page-content';
  8137.       this.frame_.appendChild(this.content_);
  8138.  
  8139.       if (this.config.scrollable) {
  8140.         this.content_.classList.add('scrollable');
  8141.  
  8142.         // The scrollable shadow top.
  8143.         this.shadowTop_ = this.ownerDocument.createElement('div');
  8144.         this.shadowTop_.className = 'shadow-top';
  8145.         this.content_.appendChild(this.shadowTop_);
  8146.  
  8147.         // The scrollable shadow bottom.
  8148.         this.shadowBottom_ = this.ownerDocument.createElement('div');
  8149.         this.shadowBottom_.className = 'shadow-bottom';
  8150.         this.content_.appendChild(this.shadowBottom_);
  8151.       }
  8152.  
  8153.       // The div that defines the tile grid viewport.
  8154.       this.tileGrid_ = this.ownerDocument.createElement('div');
  8155.       this.tileGrid_.className = 'tile-grid';
  8156.       this.content_.appendChild(this.tileGrid_);
  8157.  
  8158.       // The tile grid contents, which can be scrolled.
  8159.       this.tileGridContent_ = this.ownerDocument.createElement('div');
  8160.       this.tileGridContent_.className = 'tile-grid-content';
  8161.       this.tileGrid_.appendChild(this.tileGridContent_);
  8162.  
  8163.       // The list of Tile elements which is used to fill the TileGrid cells.
  8164.       this.tiles_ = [];
  8165.  
  8166.       // TODO(pedrosimonetti): Check duplication of these methods.
  8167.       this.addEventListener('cardselected', this.handleCardSelection_);
  8168.       this.addEventListener('carddeselected', this.handleCardDeselection_);
  8169.  
  8170.       this.tileGrid_.addEventListener('webkitTransitionEnd',
  8171.           this.onTileGridTransitionEnd_.bind(this));
  8172.  
  8173.       this.content_.addEventListener('scroll', this.onScroll.bind(this));
  8174.     },
  8175.  
  8176.     /**
  8177.      * The list of Tile elements.
  8178.      * @type {Array<Tile>}
  8179.      */
  8180.     get tiles() {
  8181.       return this.tiles_;
  8182.     },
  8183.  
  8184.     /**
  8185.      * The number of Tiles in this TilePage.
  8186.      * @type {number}
  8187.      */
  8188.     get tileCount() {
  8189.       return this.tiles_.length;
  8190.     },
  8191.  
  8192.     /**
  8193.      * Whether or not this TilePage is selected.
  8194.      * @type {boolean}
  8195.      */
  8196.     get selected() {
  8197.       return Array.prototype.indexOf.call(this.parentNode.children, this) ==
  8198.           ntp.getCardSlider().currentCard;
  8199.     },
  8200.  
  8201.     /**
  8202.      * Removes the tilePage from the DOM and cleans up event handlers.
  8203.      */
  8204.     remove: function() {
  8205.       // This checks arguments.length as most remove functions have a boolean
  8206.       // |opt_animate| argument, but that's not necesarilly applicable to
  8207.       // removing a tilePage. Selecting a different card in an animated way and
  8208.       // deleting the card afterward is probably a better choice.
  8209.       assert(typeof arguments[0] != 'boolean',
  8210.              'This function takes no |opt_animate| argument.');
  8211.       this.parentNode.removeChild(this);
  8212.     },
  8213.  
  8214.     /**
  8215.      * Notify interested subscribers that a tile has been removed from this
  8216.      * page.
  8217.      * @param {Tile} tile The newly added tile.
  8218.      * @param {number} index The index of the tile that was added.
  8219.      * @param {boolean} wasAnimated Whether the removal was animated.
  8220.      */
  8221.     fireAddedEvent: function(tile, index, wasAnimated) {
  8222.       var e = document.createEvent('Event');
  8223.       e.initEvent('tilePage:tile_added', true, true);
  8224.       e.addedIndex = index;
  8225.       e.addedTile = tile;
  8226.       e.wasAnimated = wasAnimated;
  8227.       this.dispatchEvent(e);
  8228.     },
  8229.  
  8230.     /**
  8231.      * Removes the given tile and animates the repositioning of the other tiles.
  8232.      * @param {boolean=} opt_animate Whether the removal should be animated.
  8233.      * @param {boolean=} opt_dontNotify Whether a page should be removed if the
  8234.      *     last tile is removed from it.
  8235.      */
  8236.     removeTile: function(tile, opt_animate, opt_dontNotify) {
  8237.       var tiles = this.tiles;
  8238.       var index = tiles.indexOf(tile);
  8239.       tile.parentNode.removeChild(tile);
  8240.       tiles.splice(index, 1);
  8241.       this.renderGrid();
  8242.  
  8243.       if (!opt_dontNotify)
  8244.         this.fireRemovedEvent(tile, index, !!opt_animate);
  8245.     },
  8246.  
  8247.     /**
  8248.      * Notify interested subscribers that a tile has been removed from this
  8249.      * page.
  8250.      * @param {TileCell} tile The tile that was removed.
  8251.      * @param {number} oldIndex Where the tile was positioned before removal.
  8252.      * @param {boolean} wasAnimated Whether the removal was animated.
  8253.      */
  8254.     fireRemovedEvent: function(tile, oldIndex, wasAnimated) {
  8255.       var e = document.createEvent('Event');
  8256.       e.initEvent('tilePage:tile_removed', true, true);
  8257.       e.removedIndex = oldIndex;
  8258.       e.removedTile = tile;
  8259.       e.wasAnimated = wasAnimated;
  8260.       this.dispatchEvent(e);
  8261.     },
  8262.  
  8263.     /**
  8264.      * Removes all tiles from the page.
  8265.      */
  8266.     removeAllTiles: function() {
  8267.       while (this.tiles.length > 0) {
  8268.         this.removeTile(this.tiles[this.tiles.length - 1]);
  8269.       }
  8270.     },
  8271.  
  8272.     /**
  8273.      * Called when the page is selected (in the card selector).
  8274.      * @param {Event} e A custom cardselected event.
  8275.      * @private
  8276.      */
  8277.     handleCardSelection_: function(e) {
  8278.       ntp.layout();
  8279.     },
  8280.  
  8281.     /**
  8282.      * Called when the page loses selection (in the card selector).
  8283.      * @param {Event} e A custom carddeselected event.
  8284.      * @private
  8285.      */
  8286.     handleCardDeselection_: function(e) {
  8287.     },
  8288.  
  8289.     // #########################################################################
  8290.     // Extended Chrome Instant
  8291.     // #########################################################################
  8292.  
  8293.  
  8294.     // properties
  8295.     // -------------------------------------------------------------------------
  8296.  
  8297.     // The number of columns.
  8298.     colCount_: 0,
  8299.     // The number of rows.
  8300.     rowCount_: 0,
  8301.     // The number of visible rows. We initialize this value with zero so
  8302.     // we can detect when the first time the page is rendered.
  8303.     numOfVisibleRows_: 1,
  8304.     // The number of the last column being animated. We initialize this value
  8305.     // with zero so we can detect when the first time the page is rendered.
  8306.     animatingColCount_: 0,
  8307.     // The index of the topmost row visible.
  8308.     pageOffset_: 0,
  8309.     // Data object representing the tiles.
  8310.     dataList_: null,
  8311.  
  8312.     /**
  8313.      * Appends a tile to the end of the tile grid.
  8314.      * @param {Tile} tile The tile to be added.
  8315.      * @param {number} index The location in the tile grid to insert it at.
  8316.      * @protected
  8317.      */
  8318.     appendTile: function(tile) {
  8319.       var index = this.tiles_.length;
  8320.       this.addTileAt(tile, index);
  8321.     },
  8322.  
  8323.     /**
  8324.      * Adds the given element to the tile grid.
  8325.      * @param {Tile} tile The tile to be added.
  8326.      * @param {number} index The location in the tile grid to insert it at.
  8327.      * @protected
  8328.      */
  8329.     addTileAt: function(tile, index) {
  8330.       this.tiles_.splice(index, 0, tile);
  8331.       this.fireAddedEvent(tile, index, false);
  8332.       this.renderGrid();
  8333.     },
  8334.  
  8335.     /**
  8336.      * Create a blank tile.
  8337.      * @protected
  8338.      */
  8339.     createTile_: function() {
  8340.       return new this.TileClass();
  8341.     },
  8342.  
  8343.     /**
  8344.      * Create blank tiles.
  8345.      * @param {number} count The desired number of Tiles to be created. If this
  8346.      *   value the maximum value defined in |config.maxTileCount|, the maximum
  8347.      *   value will be used instead.
  8348.      * @protected
  8349.      */
  8350.     createTiles_: function(count) {
  8351.       count = Math.min(count, this.config.maxTileCount);
  8352.       for (var i = 0; i < count; i++) {
  8353.         this.appendTile(this.createTile_());
  8354.       }
  8355.     },
  8356.  
  8357.     /**
  8358.      * This method will create/remove necessary/unnecessary tiles, render the
  8359.      * grid when the number of tiles has changed, and finally will call
  8360.      * |updateTiles_| which will in turn render the individual tiles.
  8361.      * @protected
  8362.      */
  8363.     updateGrid: function() {
  8364.       var dataListLength = this.dataList_.length;
  8365.       var tileCount = this.tileCount;
  8366.       // Create or remove tiles if necessary.
  8367.       if (tileCount < dataListLength) {
  8368.         this.createTiles_(dataListLength - tileCount);
  8369.       } else if (tileCount > dataListLength) {
  8370.         var tiles = this.tiles_;
  8371.         while (tiles.length > dataListLength) {
  8372.           var previousLength = tiles.length;
  8373.           // It doesn't matter which tiles are being removed here because
  8374.           // they're going to be reconstructed below when calling updateTiles_
  8375.           // method, so the first tiles are being removed here.
  8376.           this.removeTile(tiles[0]);
  8377.           assert(tiles.length < previousLength);
  8378.         }
  8379.       }
  8380.  
  8381.       this.updateTiles_();
  8382.     },
  8383.  
  8384.     /**
  8385.      * Update the tiles after a change to |dataList_|.
  8386.      */
  8387.     updateTiles_: function() {
  8388.       var maxTileCount = this.config.maxTileCount;
  8389.       var dataList = this.dataList_;
  8390.       var tiles = this.tiles;
  8391.       for (var i = 0; i < maxTileCount; i++) {
  8392.         var data = dataList[i];
  8393.         var tile = tiles[i];
  8394.  
  8395.         // TODO(pedrosimonetti): What do we do when there's no tile here?
  8396.         if (!tile)
  8397.           return;
  8398.  
  8399.         if (i >= dataList.length)
  8400.           tile.reset();
  8401.         else
  8402.           tile.data = data;
  8403.       }
  8404.     },
  8405.  
  8406.     /**
  8407.      * Sets the dataList that will be used to create Tiles.
  8408.      * TODO(pedrosimonetti): Use setters and getters instead.
  8409.      */
  8410.     setDataList: function(dataList) {
  8411.       this.dataList_ = dataList.slice(0, this.config.maxTileCount);
  8412.     },
  8413.  
  8414.     // internal helpers
  8415.     // -------------------------------------------------------------------------
  8416.  
  8417.     /**
  8418.      * Gets the required width for a Tile.
  8419.      * @private
  8420.      */
  8421.     getTileRequiredWidth_: function() {
  8422.       var config = this.config;
  8423.       return config.cellWidth + config.cellMarginStart;
  8424.     },
  8425.  
  8426.     /**
  8427.      * Gets the the maximum number of columns that can fit in a given width.
  8428.      * @param {number} width The width in pixels.
  8429.      * @private
  8430.      */
  8431.     getColCountForWidth_: function(width) {
  8432.       var scrollBarIsVisible = this.config.scrollable &&
  8433.           this.content_.scrollHeight > this.content_.clientHeight;
  8434.       var scrollBarWidth = scrollBarIsVisible ? SCROLL_BAR_WIDTH : 0;
  8435.       var availableWidth = width + this.config.cellMarginStart - scrollBarWidth;
  8436.  
  8437.       var requiredWidth = this.getTileRequiredWidth_();
  8438.       var colCount = Math.floor(availableWidth / requiredWidth);
  8439.       return colCount;
  8440.     },
  8441.  
  8442.     /**
  8443.      * Gets the width for a given number of columns.
  8444.      * @param {number} colCount The number of columns.
  8445.      * @private
  8446.      */
  8447.     getWidthForColCount_: function(colCount) {
  8448.       var requiredWidth = this.getTileRequiredWidth_();
  8449.       var width = colCount * requiredWidth - this.config.cellMarginStart;
  8450.       return width;
  8451.     },
  8452.  
  8453.     /**
  8454.      * Returns the position of the tile at |index|.
  8455.      * @param {number} index Tile index.
  8456.      * @private
  8457.      * @return {!{top: number, left: number}} Position.
  8458.      */
  8459.     getTilePosition_: function(index) {
  8460.       var colCount = this.colCount_;
  8461.       var row = Math.floor(index / colCount);
  8462.       var col = index % colCount;
  8463.       if (isRTL())
  8464.         col = colCount - col - 1;
  8465.       var config = this.config;
  8466.       var top = ntp.TILE_ROW_HEIGHT * row;
  8467.       var left = col * (config.cellWidth + config.cellMarginStart);
  8468.       return {top: top, left: left};
  8469.     },
  8470.  
  8471.     // rendering
  8472.     // -------------------------------------------------------------------------
  8473.  
  8474.     /**
  8475.      * Renders the tile grid, and the individual tiles. Rendering the grid
  8476.      * consists of adding/removing tile rows and tile cells according to the
  8477.      * specified size (defined by the number of columns in the grid). While
  8478.      * rendering the grid, the tiles are rendered in order in their respective
  8479.      * cells and tile fillers are rendered when needed. This method sets the
  8480.      * private properties colCount_ and rowCount_.
  8481.      *
  8482.      * This method should be called every time the contents of the grid changes,
  8483.      * that is, when the number, contents or order of the tiles has changed.
  8484.      * @param {number=} opt_colCount The number of columns.
  8485.      * @param {number=} opt_tileCount Forces a particular number of tiles to
  8486.      *   be drawn. This is useful for cases like the restoration/insertion
  8487.      *   of tiles when you need to place a tile in a place of the grid that
  8488.      *   is not rendered at the moment.
  8489.      * @protected
  8490.      */
  8491.     renderGrid: function(opt_colCount, opt_tileCount) {
  8492.       var colCount = opt_colCount || this.colCount_;
  8493.  
  8494.       var tileGridContent = this.tileGridContent_;
  8495.       var tiles = this.tiles_;
  8496.       var tileCount = opt_tileCount || tiles.length;
  8497.  
  8498.       var rowCount = Math.ceil(tileCount / colCount);
  8499.       var tileRows = tileGridContent.getElementsByClassName('tile-row');
  8500.  
  8501.       for (var tile = 0, row = 0; row < rowCount; row++) {
  8502.         var tileRow = tileRows[row];
  8503.  
  8504.         // Create tile row if there's no one yet.
  8505.         if (!tileRow) {
  8506.           tileRow = cr.doc.createElement('div');
  8507.           tileRow.className = 'tile-row';
  8508.           tileGridContent.appendChild(tileRow);
  8509.         }
  8510.  
  8511.         // The tiles inside the current row.
  8512.         var tileRowTiles = tileRow.childNodes;
  8513.  
  8514.         // Remove excessive columns from a particular tile row.
  8515.         var maxColCount = Math.min(colCount, tileCount - tile);
  8516.         maxColCount = Math.max(0, maxColCount);
  8517.         while (tileRowTiles.length > maxColCount) {
  8518.           tileRow.removeChild(tileRow.lastElementChild);
  8519.         }
  8520.  
  8521.         // For each column in the current row.
  8522.         for (var col = 0; col < colCount; col++, tile++) {
  8523.           var tileCell;
  8524.           var tileElement;
  8525.           if (tileRowTiles[col]) {
  8526.             tileCell = tileRowTiles[col];
  8527.           } else {
  8528.             var span = cr.doc.createElement('span');
  8529.             tileCell = new TileCell(span);
  8530.           }
  8531.  
  8532.           // Render Tiles.
  8533.           tileElement = tiles[tile];
  8534.           if (tile < tileCount && tileElement) {
  8535.             tileCell.classList.remove('filler');
  8536.             if (!tileCell.tile)
  8537.               tileCell.appendChild(tileElement);
  8538.             else if (tileElement != tileCell.tile)
  8539.               tileCell.replaceChild(tileElement, tileCell.tile);
  8540.           } else if (!tileCell.classList.contains('filler')) {
  8541.             tileCell.classList.add('filler');
  8542.             tileElement = cr.doc.createElement('span');
  8543.             tileElement.className = 'tile';
  8544.             if (tileCell.tile)
  8545.               tileCell.replaceChild(tileElement, tileCell.tile);
  8546.             else
  8547.               tileCell.appendChild(tileElement);
  8548.           }
  8549.  
  8550.           if (!tileRowTiles[col])
  8551.             tileRow.appendChild(tileCell);
  8552.         }
  8553.       }
  8554.  
  8555.       // Remove excessive tile rows from the tile grid.
  8556.       while (tileRows.length > rowCount) {
  8557.         tileGridContent.removeChild(tileGridContent.lastElementChild);
  8558.       }
  8559.  
  8560.       this.colCount_ = colCount;
  8561.       this.rowCount_ = rowCount;
  8562.  
  8563.       // If we are manually changing the tile count (which can happen during
  8564.       // the restoration/insertion animation) we should not fire the scroll
  8565.       // event once some cells might contain dummy tiles which will cause
  8566.       // an error.
  8567.       if (!opt_tileCount)
  8568.         this.onScroll();
  8569.     },
  8570.  
  8571.     // layout
  8572.     // -------------------------------------------------------------------------
  8573.  
  8574.     /**
  8575.      * Calculates the layout of the tile page according to the current Bottom
  8576.      * Panel's size. This method will resize the containers of the tile page,
  8577.      * and re-render the grid when its dimension changes (number of columns or
  8578.      * visible rows changes). This method also sets the private properties
  8579.      * |numOfVisibleRows_| and |animatingColCount_|.
  8580.      *
  8581.      * This method should be called every time the dimension of the grid changes
  8582.      * or when you need to reinforce its dimension.
  8583.      * @param {boolean=} opt_animate Whether the layout be animated.
  8584.      */
  8585.     layout: function(opt_animate) {
  8586.       var contentHeight = ntp.getContentHeight();
  8587.       this.content_.style.height = contentHeight + 'px';
  8588.  
  8589.       var contentWidth = ntp.getContentWidth();
  8590.       var colCount = this.getColCountForWidth_(contentWidth);
  8591.       var lastColCount = this.colCount_;
  8592.       var animatingColCount = this.animatingColCount_;
  8593.       if (colCount != animatingColCount) {
  8594.         if (opt_animate)
  8595.           this.tileGrid_.classList.add('animate-grid-width');
  8596.  
  8597.         if (colCount > animatingColCount) {
  8598.           // If the grid is expanding, it needs to be rendered first so the
  8599.           // revealing tiles are visible as soon as the animation starts.
  8600.           if (colCount != lastColCount)
  8601.             this.renderGrid(colCount);
  8602.  
  8603.           // Hides affected columns and forces the reflow.
  8604.           this.showTileCols_(animatingColCount, false);
  8605.           // Trigger reflow, making the tiles completely hidden.
  8606.           this.tileGrid_.offsetTop;
  8607.           // Fades in the affected columns.
  8608.           this.showTileCols_(animatingColCount, true);
  8609.         } else {
  8610.           // Fades out the affected columns.
  8611.           this.showTileCols_(colCount, false);
  8612.         }
  8613.  
  8614.         var newWidth = this.getWidthForColCount_(colCount);
  8615.         this.tileGrid_.style.width = newWidth + 'px';
  8616.  
  8617.         // TODO(pedrosimonetti): move to handler below.
  8618.         var self = this;
  8619.         this.onTileGridTransitionEndHandler_ = function() {
  8620.           if (colCount < lastColCount)
  8621.             self.renderGrid(colCount);
  8622.           else
  8623.             self.showTileCols_(0, true);
  8624.         };
  8625.       }
  8626.  
  8627.       this.animatingColCount_ = colCount;
  8628.  
  8629.       this.frame_.style.width = contentWidth + 'px';
  8630.  
  8631.       this.onScroll();
  8632.     },
  8633.  
  8634.     // tile repositioning animation
  8635.     // -------------------------------------------------------------------------
  8636.  
  8637.     /**
  8638.      * Tile repositioning state.
  8639.      * @type {{index: number, isRemoving: number}}
  8640.      */
  8641.     tileRepositioningState_: null,
  8642.  
  8643.     /**
  8644.      * Gets the repositioning state.
  8645.      * @return {{index: number, isRemoving: number}} The repositioning data.
  8646.      */
  8647.     getTileRepositioningState: function() {
  8648.       return this.tileRepositioningState_;
  8649.     },
  8650.  
  8651.     /**
  8652.      * Sets the repositioning state that will be used to animate the tiles.
  8653.      * @param {number} index The tile's index.
  8654.      * @param {boolean} isRemoving Whether the tile is being removed.
  8655.      */
  8656.     setTileRepositioningState: function(index, isRemoving) {
  8657.       this.tileRepositioningState_ = {
  8658.         index: index,
  8659.         isRemoving: isRemoving
  8660.       };
  8661.     },
  8662.  
  8663.     /**
  8664.      * Resets the repositioning state.
  8665.      */
  8666.     resetTileRepositioningState: function() {
  8667.       this.tileRepositioningState_ = null;
  8668.     },
  8669.  
  8670.     /**
  8671.      * Animates a tile removal.
  8672.      * @param {number} index The index of the tile to be removed.
  8673.      * @param {Object} newDataList The new data list.
  8674.      */
  8675.     animateTileRemoval: function(index, newDataList) {
  8676.       var tiles = this.tiles_;
  8677.       var tileCount = tiles.length;
  8678.       assert(tileCount > 0);
  8679.  
  8680.       var tileCells = this.querySelectorAll('.tile-cell');
  8681.       var extraTileIndex = tileCount - 1;
  8682.       var extraCell = tileCells[extraTileIndex];
  8683.       var extraTileData = newDataList[extraTileIndex];
  8684.  
  8685.       var repositioningStartIndex = index + 1;
  8686.       var repositioningEndIndex = tileCount;
  8687.  
  8688.       this.initializeRepositioningAnimation_(index, repositioningEndIndex,
  8689.           true);
  8690.  
  8691.       var tileBeingRemoved = tiles[index];
  8692.       tileBeingRemoved.scrollTop;
  8693.  
  8694.       // The extra tile is the new one that will appear. It can be a normal
  8695.       // tile (when there's extra data for it), or a filler tile.
  8696.       var extraTile = createTile(this, extraTileData);
  8697.       if (!extraTileData)
  8698.         extraCell.classList.add('filler');
  8699.       // The extra tile is being assigned in order to put it in the right spot.
  8700.       extraCell.assign(extraTile);
  8701.  
  8702.       this.executeRepositioningAnimation_(tileBeingRemoved, extraTile,
  8703.           repositioningStartIndex, repositioningEndIndex, true);
  8704.  
  8705.       // Cleans up the animation.
  8706.       var onPositioningTransitionEnd = function(e) {
  8707.         var propertyName = e.propertyName;
  8708.         if (!(propertyName == '-webkit-transform' ||
  8709.               propertyName == 'opacity')) {
  8710.           return;
  8711.         }
  8712.  
  8713.         lastAnimatingTile.removeEventListener('webkitTransitionEnd',
  8714.             onPositioningTransitionEnd);
  8715.  
  8716.         this.finalizeRepositioningAnimation_(tileBeingRemoved,
  8717.             repositioningStartIndex, repositioningEndIndex, true);
  8718.  
  8719.         this.removeTile(tileBeingRemoved);
  8720.  
  8721.         // If the extra tile is a real one (not a filler), then it needs to be
  8722.         // added to the tile list. The tile has been placed in the right spot
  8723.         // but the tile page still doesn't know about this new tile.
  8724.         if (extraTileData)
  8725.           this.appendTile(extraTile);
  8726.  
  8727.       }.bind(this);
  8728.  
  8729.       // Listens to the animation end.
  8730.       var lastAnimatingTile = extraTile;
  8731.       lastAnimatingTile.addEventListener('webkitTransitionEnd',
  8732.           onPositioningTransitionEnd);
  8733.     },
  8734.  
  8735.     /**
  8736.      * Animates a tile restoration.
  8737.      * @param {number} index The index of the tile to be restored.
  8738.      * @param {Object} newDataList The new data list.
  8739.      */
  8740.     animateTileRestoration: function(index, newDataList) {
  8741.       var tiles = this.tiles_;
  8742.       var tileCount = tiles.length;
  8743.  
  8744.       var tileCells = this.getElementsByClassName('tile-cell');
  8745.  
  8746.       // If the desired position is outside the grid, then the grid must be
  8747.       // expanded so there will be a cell in the desired position.
  8748.       if (index >= tileCells.length)
  8749.         this.renderGrid(null, index + 1);
  8750.  
  8751.       var extraTileIndex = Math.min(tileCount, this.config.maxTileCount - 1);
  8752.       var extraCell = tileCells[extraTileIndex];
  8753.       var extraTileData = newDataList[extraTileIndex + 1];
  8754.  
  8755.       var repositioningStartIndex = index;
  8756.       var repositioningEndIndex = tileCount - (extraTileData ? 1 : 0);
  8757.  
  8758.       this.initializeRepositioningAnimation_(index, repositioningEndIndex);
  8759.  
  8760.       var restoredData = newDataList[index];
  8761.       var tileBeingRestored = createTile(this, restoredData);
  8762.  
  8763.       // Temporarily assume the |index| cell so the tile can be animated in
  8764.       // the right spot.
  8765.       tileCells[index].appendChild(tileBeingRestored);
  8766.  
  8767.       if (this.config.scrollable)
  8768.         this.content_.scrollTop = tileCells[index].offsetTop;
  8769.  
  8770.       var extraTile;
  8771.       if (extraCell)
  8772.         extraTile = extraCell.tile;
  8773.  
  8774.       this.executeRepositioningAnimation_(tileBeingRestored, extraTile,
  8775.           repositioningStartIndex, repositioningEndIndex, false);
  8776.  
  8777.       // Cleans up the animation.
  8778.       var onPositioningTransitionEnd = function(e) {
  8779.         var propertyName = e.propertyName;
  8780.         if (!(propertyName == '-webkit-transform' ||
  8781.               propertyName == 'opacity')) {
  8782.           return;
  8783.         }
  8784.  
  8785.         lastAnimatingTile.removeEventListener('webkitTransitionEnd',
  8786.             onPositioningTransitionEnd);
  8787.  
  8788.         // When there's an extra data, it means the tile is a real one (not a
  8789.         // filler), and therefore it needs to be removed from the tile list.
  8790.         if (extraTileData)
  8791.           this.removeTile(extraTile);
  8792.  
  8793.         this.finalizeRepositioningAnimation_(tileBeingRestored,
  8794.             repositioningStartIndex, repositioningEndIndex, false);
  8795.  
  8796.         this.addTileAt(tileBeingRestored, index);
  8797.  
  8798.       }.bind(this);
  8799.  
  8800.       // Listens to the animation end.
  8801.       var lastAnimatingTile = tileBeingRestored;
  8802.       lastAnimatingTile.addEventListener('webkitTransitionEnd',
  8803.           onPositioningTransitionEnd);
  8804.     },
  8805.  
  8806.     // animation helpers
  8807.     // -------------------------------------------------------------------------
  8808.  
  8809.     /**
  8810.      * Moves a tile to a new position.
  8811.      * @param {Tile} tile A tile.
  8812.      * @param {number} left Left coordinate.
  8813.      * @param {number} top Top coordinate.
  8814.      * @private
  8815.      */
  8816.     moveTileTo_: function(tile, left, top) {
  8817.       tile.style.left = left + 'px';
  8818.       tile.style.top = top + 'px';
  8819.     },
  8820.  
  8821.     /**
  8822.      * Resets a tile's position.
  8823.      * @param {Tile} tile A tile.
  8824.      * @private
  8825.      */
  8826.     resetTilePosition_: function(tile) {
  8827.       tile.style.left = '';
  8828.       tile.style.top = '';
  8829.     },
  8830.  
  8831.     /**
  8832.      * Initializes the repositioning animation.
  8833.      * @param {number} startIndex Index of the first tile to be repositioned.
  8834.      * @param {number} endIndex Index of the last tile to be repositioned.
  8835.      * @param {boolean} isRemoving Whether the tile is being removed.
  8836.      * @private
  8837.      */
  8838.     initializeRepositioningAnimation_: function(startIndex, endIndex,
  8839.         isRemoving) {
  8840.       // Move tiles from relative to absolute position.
  8841.       var tiles = this.tiles_;
  8842.       var tileGridContent = this.tileGridContent_;
  8843.       for (var i = startIndex; i < endIndex; i++) {
  8844.         var tile = tiles[i];
  8845.         var position = this.getTilePosition_(i);
  8846.         this.moveTileTo_(tile, position.left, position.top);
  8847.         tile.style.zIndex = endIndex - i;
  8848.         tileGridContent.appendChild(tile);
  8849.       }
  8850.  
  8851.       tileGridContent.classList.add('animate-tile-repositioning');
  8852.  
  8853.       if (!isRemoving)
  8854.         tileGridContent.classList.add('undo-removal');
  8855.     },
  8856.  
  8857.     /**
  8858.      * Executes the repositioning animation.
  8859.      * @param {Tile} targetTile The tile that is being removed/restored.
  8860.      * @param {Tile} extraTile The extra tile that is going to appear/disappear.
  8861.      * @param {number} startIndex Index of the first tile to be repositioned.
  8862.      * @param {number} endIndex Index of the last tile to be repositioned.
  8863.      * @param {boolean} isRemoving Whether the tile is being removed.
  8864.      * @private
  8865.      */
  8866.     executeRepositioningAnimation_: function(targetTile, extraTile, startIndex,
  8867.         endIndex, isRemoving) {
  8868.       targetTile.classList.add('target-tile');
  8869.  
  8870.       // Alternate the visualization of the target and extra tiles.
  8871.       fadeTile(targetTile, !isRemoving);
  8872.       if (extraTile)
  8873.         fadeTile(extraTile, isRemoving);
  8874.  
  8875.       // Move tiles to the new position.
  8876.       var tiles = this.tiles_;
  8877.       var positionDiff = isRemoving ? -1 : 1;
  8878.       for (var i = startIndex; i < endIndex; i++) {
  8879.         var position = this.getTilePosition_(i + positionDiff);
  8880.         this.moveTileTo_(tiles[i], position.left, position.top);
  8881.       }
  8882.     },
  8883.  
  8884.     /**
  8885.      * Finalizes the repositioning animation.
  8886.      * @param {Tile} targetTile The tile that is being removed/restored.
  8887.      * @param {number} startIndex Index of the first tile to be repositioned.
  8888.      * @param {number} endIndex Index of the last tile to be repositioned.
  8889.      * @param {boolean} isRemoving Whether the tile is being removed.
  8890.      * @private
  8891.      */
  8892.     finalizeRepositioningAnimation_: function(targetTile, startIndex, endIndex,
  8893.         isRemoving) {
  8894.       // Remove temporary class names.
  8895.       var tileGridContent = this.tileGridContent_;
  8896.       tileGridContent.classList.remove('animate-tile-repositioning');
  8897.       tileGridContent.classList.remove('undo-removal');
  8898.       targetTile.classList.remove('target-tile');
  8899.  
  8900.       // Move tiles back to relative position.
  8901.       var tiles = this.tiles_;
  8902.       var tileCells = this.querySelectorAll('.tile-cell');
  8903.       var positionDiff = isRemoving ? -1 : 1;
  8904.       for (var i = startIndex; i < endIndex; i++) {
  8905.         var tile = tiles[i];
  8906.         this.resetTilePosition_(tile);
  8907.         tile.style.zIndex = '';
  8908.         var tileCell = tileCells[i + positionDiff];
  8909.         if (tileCell)
  8910.           tileCell.assign(tile);
  8911.       }
  8912.     },
  8913.  
  8914.     /**
  8915.      * Animates the display of columns.
  8916.      * @param {number} col The column number.
  8917.      * @param {boolean} show Whether or not to show the row.
  8918.      */
  8919.     showTileCols_: function(col, show) {
  8920.       var prop = show ? 'remove' : 'add';
  8921.       var max = 10;  // TODO(pedrosimonetti): Add const?
  8922.       var tileGridContent = this.tileGridContent_;
  8923.       for (var i = col; i < max; i++) {
  8924.         tileGridContent.classList[prop]('hide-col-' + i);
  8925.       }
  8926.     },
  8927.  
  8928.     // event handlers
  8929.     // -------------------------------------------------------------------------
  8930.  
  8931.     /**
  8932.      * Handles the scroll event.
  8933.      * @protected
  8934.      */
  8935.     onScroll: function() {
  8936.       // If the TilePage is scrollable, then the opacity of shadow top and
  8937.       // bottom must adjusted, indicating when there's an overflow content.
  8938.       if (this.config.scrollable) {
  8939.         var content = this.content_;
  8940.         var topGap = Math.min(MAX_SCROLL_SHADOW_GAP, content.scrollTop);
  8941.         var bottomGap = Math.min(MAX_SCROLL_SHADOW_GAP, content.scrollHeight -
  8942.             content.scrollTop - content.clientHeight);
  8943.  
  8944.         this.shadowTop_.style.opacity = topGap / MAX_SCROLL_SHADOW_GAP;
  8945.         this.shadowBottom_.style.opacity = bottomGap / MAX_SCROLL_SHADOW_GAP;
  8946.       }
  8947.     },
  8948.  
  8949.     /**
  8950.      * Handles the end of the horizontal tile grid transition.
  8951.      * @param {Event} e The tile grid webkitTransitionEnd event.
  8952.      */
  8953.     onTileGridTransitionEnd_: function(e) {
  8954.       if (!this.selected)
  8955.         return;
  8956.  
  8957.       // We should remove the classes that control transitions when the
  8958.       // transition ends so when the text is resized (Ctrl + '+'), no other
  8959.       // transition should happen except those defined in the specification.
  8960.       // For example, the tile has a transition for its 'width' property which
  8961.       // is used when the tile is being hidden. But when you resize the text,
  8962.       // and therefore the tile changes its 'width', this change should not be
  8963.       // animated.
  8964.  
  8965.       // When the tile grid width transition ends, we need to remove the class
  8966.       // 'animate-grid-width' which handles the tile grid width transition, and
  8967.       // individual tile transitions. TODO(pedrosimonetti): Investigate if we
  8968.       // can improve the performance here by using a more efficient selector.
  8969.       var tileGrid = this.tileGrid_;
  8970.       if (e.target == tileGrid &&
  8971.           tileGrid.classList.contains('animate-grid-width')) {
  8972.         tileGrid.classList.remove('animate-grid-width');
  8973.  
  8974.         if (this.onTileGridTransitionEndHandler_)
  8975.           this.onTileGridTransitionEndHandler_();
  8976.       }
  8977.     },
  8978.   };
  8979.  
  8980.   /**
  8981.    * Creates a new tile given a particular data. If there's no data, then
  8982.    * a tile filler will be created.
  8983.    * @param {TilePage} tilePage A TilePage.
  8984.    * @param {Object=} opt_data The data that will be used to create the tile.
  8985.    * @return {Tile} The new tile.
  8986.    */
  8987.   function createTile(tilePage, opt_data) {
  8988.     var tile;
  8989.     if (opt_data) {
  8990.       // If there's data, the new tile will be a real one (not a filler).
  8991.       tile = new tilePage.TileClass(opt_data);
  8992.     } else {
  8993.       // Otherwise, it will be a fake filler tile.
  8994.       tile = cr.doc.createElement('span');
  8995.       tile.className = 'tile';
  8996.     }
  8997.     return tile;
  8998.   }
  8999.  
  9000.   /**
  9001.    * Fades a tile.
  9002.    * @param {Tile} tile A Tile.
  9003.    * @param {boolean} isFadeIn Whether to fade-in the tile. If |isFadeIn| is
  9004.    * false, then the tile is going to fade-out.
  9005.    */
  9006.   function fadeTile(tile, isFadeIn) {
  9007.     var className = 'animate-hide-tile';
  9008.     tile.classList.add(className);
  9009.     if (isFadeIn) {
  9010.       // Forces a reflow to ensure that the fade-out animation will work.
  9011.       tile.scrollTop;
  9012.       tile.classList.remove(className);
  9013.     }
  9014.   }
  9015.  
  9016.   return {
  9017.     Tile: Tile,
  9018.     TilePage: TilePage,
  9019.   };
  9020. });
  9021. </script>
  9022. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9023. // Use of this source code is governed by a BSD-style license that can be
  9024. // found in the LICENSE file.
  9025.  
  9026. cr.define('ntp', function() {
  9027.   'use strict';
  9028.  
  9029.   var Tile = ntp.Tile;
  9030.   var TilePage = ntp.TilePage;
  9031.  
  9032.   /**
  9033.    * Creates a new Thumbnail object for tiling.
  9034.    * @param {Object=} opt_data The data representing the thumbnail.
  9035.    * @constructor
  9036.    * @extends {Tile}
  9037.    * @extends {HTMLAnchorElement}
  9038.    */
  9039.   function Thumbnail(opt_data) {
  9040.     var el = cr.doc.createElement('a');
  9041.     el.__proto__ = Thumbnail.prototype;
  9042.     el.initialize();
  9043.  
  9044.     if (opt_data)
  9045.       el.data = opt_data;
  9046.  
  9047.     return el;
  9048.   }
  9049.  
  9050.   Thumbnail.prototype = Tile.subclass({
  9051.     __proto__: HTMLAnchorElement.prototype,
  9052.  
  9053.     /**
  9054.      * Initializes a Thumbnail.
  9055.      */
  9056.     initialize: function() {
  9057.       Tile.prototype.initialize.apply(this, arguments);
  9058.       this.classList.add('thumbnail');
  9059.       this.addEventListener('mouseover', this.handleMouseOver_);
  9060.     },
  9061.  
  9062.     /**
  9063.      * Clears the DOM hierarchy for this node, setting it back to the default
  9064.      * for a blank thumbnail.
  9065.      */
  9066.     reset: function() {
  9067.       this.innerHTML =
  9068.           '<span class="thumbnail-wrapper">' +
  9069.             '<span class="thumbnail-image thumbnail-card"></span>' +
  9070.           '</span>' +
  9071.           '<span class="title"></span>';
  9072.  
  9073.       this.tabIndex = -1;
  9074.       this.data_ = null;
  9075.       this.title = '';
  9076.     },
  9077.  
  9078.     /**
  9079.      * Update the appearance of this tile according to |data|.
  9080.      * @param {Object} data A dictionary of relevant data for the page.
  9081.      */
  9082.     set data(data) {
  9083.       Object.getOwnPropertyDescriptor(Tile.prototype, 'data').set.apply(this,
  9084.           arguments);
  9085.  
  9086.       this.formatThumbnail_(data);
  9087.     },
  9088.  
  9089.     /**
  9090.      * Formats this tile according to |data|.
  9091.      * @param {Object} data A dictionary of relevant data for the page.
  9092.      * @private
  9093.      */
  9094.     formatThumbnail_: function(data) {
  9095.       var title = this.querySelector('.title');
  9096.       title.textContent = data.title;
  9097.       title.dir = data.direction;
  9098.  
  9099.       // Sets the tooltip.
  9100.       this.title = data.title;
  9101.  
  9102.       var dataUrl = data.url;
  9103.       // Allow an empty string href (e.g. for a multiple tab thumbnail).
  9104.       this.href = typeof data.href != 'undefined' ? data.href : dataUrl;
  9105.  
  9106.       var thumbnailImage = this.querySelector('.thumbnail-image');
  9107.  
  9108.       var banner = thumbnailImage.querySelector('.thumbnail-banner');
  9109.       if (banner)
  9110.         thumbnailImage.removeChild(banner);
  9111.  
  9112.       var favicon = this.querySelector('.thumbnail-favicon') ||
  9113.                     this.ownerDocument.createElement('div');
  9114.       favicon.className = 'thumbnail-favicon';
  9115.       favicon.style.backgroundImage =
  9116.           url('chrome://favicon/size/16/' + dataUrl);
  9117.       this.appendChild(favicon);
  9118.  
  9119.       var self = this;
  9120.       var image = new Image();
  9121.  
  9122.       // If the thumbnail image fails to load, show the favicon and URL instead.
  9123.       // TODO(jeremycho): Move to a separate function?
  9124.       image.onerror = function() {
  9125.         banner = thumbnailImage.querySelector('.thumbnail-banner') ||
  9126.             self.ownerDocument.createElement('div');
  9127.         banner.className = 'thumbnail-banner';
  9128.  
  9129.         // For now, just strip leading http://www and trailing backslash.
  9130.         // TODO(jeremycho): Consult with UX on URL truncation.
  9131.         banner.textContent = dataUrl.replace(/^(http:\/\/)?(www\.)?|\/$/gi, '');
  9132.         thumbnailImage.appendChild(banner);
  9133.       };
  9134.  
  9135.       var thumbnailUrl = ntp.getThumbnailUrl(dataUrl);
  9136.       thumbnailImage.style.backgroundImage = url(thumbnailUrl);
  9137.       image.src = thumbnailUrl;
  9138.     },
  9139.  
  9140.     /**
  9141.      * Returns true if this is a thumbnail or descendant thereof.  Used to
  9142.      * detect when the mouse has transitioned into this thumbnail from a
  9143.      * strictly non-thumbnail element.
  9144.      * @param {Object} element The element to test.
  9145.      * @return {boolean} True if this is a thumbnail or a descendant thereof.
  9146.      * @private
  9147.      */
  9148.     isInThumbnail_: function(element) {
  9149.       while (element) {
  9150.         if (element == this)
  9151.           return true;
  9152.         element = element.parentNode;
  9153.       }
  9154.       return false;
  9155.     },
  9156.  
  9157.     /**
  9158.      * Increment the hover count whenever the mouse enters a thumbnail.
  9159.      * @param {Event} e The mouse over event.
  9160.      * @private
  9161.      */
  9162.     handleMouseOver_: function(e) {
  9163.       if (this.isInThumbnail_(e.target) &&
  9164.           !this.isInThumbnail_(e.relatedTarget)) {
  9165.         ntp.incrementHoveredThumbnailCount();
  9166.       }
  9167.     },
  9168.   });
  9169.  
  9170.   /**
  9171.    * Creates a new ThumbnailPage object.
  9172.    * @constructor
  9173.    * @extends {TilePage}
  9174.    */
  9175.   function ThumbnailPage() {
  9176.     var el = new TilePage();
  9177.     el.__proto__ = ThumbnailPage.prototype;
  9178.  
  9179.     return el;
  9180.   }
  9181.  
  9182.   ThumbnailPage.prototype = {
  9183.     __proto__: TilePage.prototype,
  9184.  
  9185.     /**
  9186.      * Initializes a ThumbnailPage.
  9187.      */
  9188.     initialize: function() {
  9189.       TilePage.prototype.initialize.apply(this, arguments);
  9190.  
  9191.       this.classList.add('thumbnail-page');
  9192.     },
  9193.  
  9194.     /** @override */
  9195.     shouldAcceptDrag: function(e) {
  9196.       return false;
  9197.     },
  9198.   };
  9199.  
  9200.   return {
  9201.     Thumbnail: Thumbnail,
  9202.     ThumbnailPage: ThumbnailPage,
  9203.   };
  9204. });
  9205. </script>
  9206. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9207. // Use of this source code is governed by a BSD-style license that can be
  9208. // found in the LICENSE file.
  9209.  
  9210. cr.define('ntp', function() {
  9211.   'use strict';
  9212.  
  9213.   var Thumbnail = ntp.Thumbnail;
  9214.   var ThumbnailPage = ntp.ThumbnailPage;
  9215.  
  9216.   /**
  9217.    * Creates a new Most Visited object for tiling.
  9218.    * @param {Object=} opt_data The data representing the most visited page.
  9219.    * @constructor
  9220.    * @extends {Thumbnail}
  9221.    * @extends {HTMLAnchorElement}
  9222.    */
  9223.   function MostVisited(opt_data) {
  9224.     var el = cr.doc.createElement('a');
  9225.     el.__proto__ = MostVisited.prototype;
  9226.     el.initialize();
  9227.  
  9228.     if (opt_data)
  9229.       el.data = opt_data;
  9230.  
  9231.     return el;
  9232.   }
  9233.  
  9234.   MostVisited.prototype = {
  9235.     __proto__: Thumbnail.prototype,
  9236.  
  9237.     /**
  9238.      * Initializes a MostVisited Thumbnail.
  9239.      */
  9240.     initialize: function() {
  9241.       Thumbnail.prototype.initialize.apply(this, arguments);
  9242.  
  9243.       this.addEventListener('click', this.handleClick_);
  9244.       this.addEventListener('keydown', this.handleKeyDown_);
  9245.       this.addEventListener('carddeselected', this.handleCardDeselected_);
  9246.       this.addEventListener('cardselected', this.handleCardSelected_);
  9247.     },
  9248.  
  9249.     /**
  9250.      * Clears the DOM hierarchy for this node, setting it back to the default
  9251.      * for a blank thumbnail.
  9252.      */
  9253.     reset: function() {
  9254.       Thumbnail.prototype.reset.apply(this, arguments);
  9255.  
  9256.       var closeButton = cr.doc.createElement('div');
  9257.       closeButton.className = 'close-button';
  9258.       closeButton.title = loadTimeData.getString('removethumbnailtooltip');
  9259.       this.appendChild(closeButton);
  9260.     },
  9261.  
  9262.     /**
  9263.      * Update the appearance of this tile according to |data|.
  9264.      * @param {Object} data A dictionary of relevant data for the page.
  9265.      */
  9266.     set data(data) {
  9267.       Object.getOwnPropertyDescriptor(Thumbnail.prototype, 'data').set.apply(
  9268.           this, arguments);
  9269.  
  9270.       if (this.classList.contains('blacklisted') && data) {
  9271.         // Animate appearance of new tile.
  9272.         this.classList.add('new-tile-contents');
  9273.       }
  9274.       this.classList.remove('blacklisted');
  9275.     },
  9276.     get data() {
  9277.       return this.data_;
  9278.     },
  9279.  
  9280.     /**
  9281.      * Handles a click on the tile.
  9282.      * @param {Event} e The click event.
  9283.      * @private
  9284.      */
  9285.     handleClick_: function(e) {
  9286.       if (e.target.classList.contains('close-button')) {
  9287.         this.blacklist_();
  9288.         e.preventDefault();
  9289.       } else {
  9290.         ntp.logTimeToClickAndHoverCount('MostVisited');
  9291.         // Records an app launch from the most visited page (Chrome will decide
  9292.         // whether the url is an app). TODO(estade): this only works for clicks;
  9293.         // other actions like "open in new tab" from the context menu won't be
  9294.         // recorded. Can this be fixed?
  9295.         chrome.send('recordAppLaunchByURL',
  9296.                     [encodeURIComponent(this.href),
  9297.                      ntp.APP_LAUNCH.NTP_MOST_VISITED]);
  9298.         // Records the index of this tile.
  9299.         chrome.send('metricsHandler:recordInHistogram',
  9300.                     ['NewTabPage.MostVisited', this.index, 8]);
  9301.         chrome.send('mostVisitedAction',
  9302.                     [ntp.NtpFollowAction.CLICKED_TILE]);
  9303.       }
  9304.     },
  9305.  
  9306.     /**
  9307.      * Allow blacklisting most visited site using the keyboard.
  9308.      * @private
  9309.      */
  9310.     handleKeyDown_: function(e) {
  9311.       if (!cr.isMac && e.keyCode == 46 || // Del
  9312.           cr.isMac && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
  9313.         this.blacklist_();
  9314.       }
  9315.     },
  9316.  
  9317.     /**
  9318.      * Permanently removes a page from Most Visited.
  9319.      * @private
  9320.      */
  9321.     blacklist_: function() {
  9322.       this.tileCell.tilePage.setTileRepositioningState(this.index, true);
  9323.       this.showUndoNotification_();
  9324.       chrome.send('blacklistURLFromMostVisited', [this.data_.url]);
  9325.       this.classList.add('blacklisted');
  9326.     },
  9327.  
  9328.     /**
  9329.      * Shows the undo notification when blacklisting a most visited site.
  9330.      * @private
  9331.      */
  9332.     showUndoNotification_: function() {
  9333.       var data = this.data_;
  9334.       var tilePage = this.tileCell.tilePage;
  9335.       var index = this.index;
  9336.       var doUndo = function() {
  9337.         tilePage.setTileRepositioningState(index, false);
  9338.         chrome.send('removeURLsFromMostVisitedBlacklist', [data.url]);
  9339.       };
  9340.  
  9341.       var undo = {
  9342.         action: doUndo,
  9343.         text: loadTimeData.getString('undothumbnailremove'),
  9344.       };
  9345.  
  9346.       var undoAll = {
  9347.         action: function() {
  9348.           chrome.send('clearMostVisitedURLsBlacklist');
  9349.         },
  9350.         text: loadTimeData.getString('restoreThumbnailsShort'),
  9351.       };
  9352.  
  9353.       ntp.showNotification(
  9354.           loadTimeData.getString('thumbnailremovednotification'),
  9355.           [undo, undoAll]);
  9356.     },
  9357.  
  9358.     /**
  9359.      * Returns whether this element can be 'removed' from chrome.
  9360.      * @return {boolean} True, since most visited pages can always be
  9361.      *     blacklisted.
  9362.      */
  9363.     canBeRemoved: function() {
  9364.       return true;
  9365.     },
  9366.   };
  9367.  
  9368.   /**
  9369.    * Creates a new MostVisitedPage object.
  9370.    * @constructor
  9371.    * @extends {ThumbnailPage}
  9372.    */
  9373.   function MostVisitedPage() {
  9374.     var el = new ThumbnailPage();
  9375.     el.__proto__ = MostVisitedPage.prototype;
  9376.     el.initialize();
  9377.  
  9378.     return el;
  9379.   }
  9380.  
  9381.   MostVisitedPage.prototype = {
  9382.     __proto__: ThumbnailPage.prototype,
  9383.  
  9384.     TileClass: MostVisited,
  9385.  
  9386.     /**
  9387.      * Initializes a MostVisitedPage.
  9388.      */
  9389.     initialize: function() {
  9390.       ThumbnailPage.prototype.initialize.apply(this, arguments);
  9391.  
  9392.       this.classList.add('most-visited-page');
  9393.     },
  9394.  
  9395.     /**
  9396.      * Handles the 'card deselected' event (i.e. the user clicked to another
  9397.      * pane).
  9398.      * @private
  9399.      * @param {Event} e The CardChanged event.
  9400.      */
  9401.     handleCardDeselected_: function(e) {
  9402.       if (!document.documentElement.classList.contains('starting-up')) {
  9403.         chrome.send('mostVisitedAction',
  9404.                     [ntp.NtpFollowAction.CLICKED_OTHER_NTP_PANE]);
  9405.       }
  9406.     },
  9407.  
  9408.     /**
  9409.      * Handles the 'card selected' event (i.e. the user clicked to select the
  9410.      * this page's pane).
  9411.      * @private
  9412.      * @param {Event} e The CardChanged event.
  9413.      */
  9414.     handleCardSelected_: function(e) {
  9415.       if (!document.documentElement.classList.contains('starting-up'))
  9416.         chrome.send('mostVisitedSelected');
  9417.     },
  9418.  
  9419.     /** @override */
  9420.     setDataList: function(dataList) {
  9421.       var startTime = Date.now();
  9422.       ThumbnailPage.prototype.setDataList.apply(this, arguments);
  9423.       this.updateGrid();
  9424.       logEvent('mostVisited.layout: ' + (Date.now() - startTime));
  9425.     },
  9426.   };
  9427.  
  9428.   /**
  9429.    * Executed once the NTP has loaded. Checks if the Most Visited pane is
  9430.    * shown or not. If it is shown, the 'mostVisitedSelected' message is sent
  9431.    * to the C++ code, to record the fact that the user has seen this pane.
  9432.    */
  9433.   MostVisitedPage.onLoaded = function() {
  9434.     if (ntp.getCardSlider() &&
  9435.         ntp.getCardSlider().currentCardValue &&
  9436.         ntp.getCardSlider().currentCardValue.classList
  9437.         .contains('most-visited-page')) {
  9438.       chrome.send('mostVisitedSelected');
  9439.     }
  9440.   };
  9441.  
  9442.   return {
  9443.     MostVisitedPage: MostVisitedPage,
  9444.   };
  9445. });
  9446.  
  9447. document.addEventListener('ntpLoaded', ntp.MostVisitedPage.onLoaded);
  9448. </script>
  9449. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9450. // Use of this source code is governed by a BSD-style license that can be
  9451. // found in the LICENSE file.
  9452.  
  9453. cr.define('ntp', function() {
  9454.   'use strict';
  9455.  
  9456.   var Tile = ntp.Tile;
  9457.   var TilePage = ntp.TilePage;
  9458.   var APP_LAUNCH = ntp.APP_LAUNCH;
  9459.  
  9460.   // Histogram buckets for UMA tracking of where a DnD drop came from.
  9461.   var DRAG_SOURCE = {
  9462.     SAME_APPS_PANE: 0,
  9463.     OTHER_APPS_PANE: 1,
  9464.     MOST_VISITED_PANE: 2,
  9465.     BOOKMARKS_PANE: 3,
  9466.     OUTSIDE_NTP: 4
  9467.   };
  9468.   var DRAG_SOURCE_LIMIT = DRAG_SOURCE.OUTSIDE_NTP + 1;
  9469.  
  9470.   /**
  9471.    * App context menu. The class is designed to be used as a singleton with
  9472.    * the app that is currently showing a context menu stored in this.app_.
  9473.    * @constructor
  9474.    */
  9475.   function AppContextMenu() {
  9476.     this.__proto__ = AppContextMenu.prototype;
  9477.     this.initialize();
  9478.   }
  9479.   cr.addSingletonGetter(AppContextMenu);
  9480.  
  9481.   AppContextMenu.prototype = {
  9482.     initialize: function() {
  9483.       var menu = new cr.ui.Menu;
  9484.       cr.ui.decorate(menu, cr.ui.Menu);
  9485.       menu.classList.add('app-context-menu');
  9486.       this.menu = menu;
  9487.  
  9488.       this.launch_ = this.appendMenuItem_();
  9489.       this.launch_.addEventListener('activate', this.onLaunch_.bind(this));
  9490.  
  9491.       menu.appendChild(cr.ui.MenuItem.createSeparator());
  9492.       this.launchRegularTab_ = this.appendMenuItem_('applaunchtyperegular');
  9493.       this.launchPinnedTab_ = this.appendMenuItem_('applaunchtypepinned');
  9494.       if (!cr.isMac)
  9495.         this.launchNewWindow_ = this.appendMenuItem_('applaunchtypewindow');
  9496.       this.launchFullscreen_ = this.appendMenuItem_('applaunchtypefullscreen');
  9497.  
  9498.       var self = this;
  9499.       this.forAllLaunchTypes_(function(launchTypeButton, id) {
  9500.         launchTypeButton.addEventListener('activate',
  9501.             self.onLaunchTypeChanged_.bind(self));
  9502.       });
  9503.  
  9504.       menu.appendChild(cr.ui.MenuItem.createSeparator());
  9505.       this.options_ = this.appendMenuItem_('appoptions');
  9506.       this.details_ = this.appendMenuItem_('appdetails');
  9507.       this.disableNotifications_ =
  9508.           this.appendMenuItem_('appdisablenotifications');
  9509.       this.uninstall_ = this.appendMenuItem_('appuninstall');
  9510.       this.options_.addEventListener('activate',
  9511.                                      this.onShowOptions_.bind(this));
  9512.       this.details_.addEventListener('activate',
  9513.                                      this.onShowDetails_.bind(this));
  9514.       this.disableNotifications_.addEventListener(
  9515.           'activate', this.onDisableNotifications_.bind(this));
  9516.       this.uninstall_.addEventListener('activate',
  9517.                                        this.onUninstall_.bind(this));
  9518.  
  9519.       if (!cr.isMac && !cr.isChromeOS) {
  9520.         menu.appendChild(cr.ui.MenuItem.createSeparator());
  9521.         this.createShortcut_ = this.appendMenuItem_('appcreateshortcut');
  9522.         this.createShortcut_.addEventListener(
  9523.             'activate', this.onCreateShortcut_.bind(this));
  9524.       }
  9525.  
  9526.       document.body.appendChild(menu);
  9527.     },
  9528.  
  9529.     /**
  9530.      * Appends a menu item to |this.menu|.
  9531.      * @param {?String} textId If non-null, the ID for the localized string
  9532.      *     that acts as the item's label.
  9533.      */
  9534.     appendMenuItem_: function(textId) {
  9535.       var button = cr.doc.createElement('button');
  9536.       this.menu.appendChild(button);
  9537.       cr.ui.decorate(button, cr.ui.MenuItem);
  9538.       if (textId)
  9539.         button.textContent = loadTimeData.getString(textId);
  9540.       return button;
  9541.     },
  9542.  
  9543.     /**
  9544.      * Iterates over all the launch type menu items.
  9545.      * @param {function(cr.ui.MenuItem, number)} f The function to call for each
  9546.      *     menu item. The parameters to the function include the menu item and
  9547.      *     the associated launch ID.
  9548.      */
  9549.     forAllLaunchTypes_: function(f) {
  9550.       // Order matters: index matches launchType id.
  9551.       var launchTypes = [this.launchPinnedTab_,
  9552.                          this.launchRegularTab_,
  9553.                          this.launchFullscreen_,
  9554.                          this.launchNewWindow_];
  9555.  
  9556.       for (var i = 0; i < launchTypes.length; ++i) {
  9557.         if (!launchTypes[i])
  9558.           continue;
  9559.  
  9560.         f(launchTypes[i], i);
  9561.       }
  9562.     },
  9563.  
  9564.     /**
  9565.      * Does all the necessary setup to show the menu for the given app.
  9566.      * @param {App} app The App object that will be showing a context menu.
  9567.      */
  9568.     setupForApp: function(app) {
  9569.       this.app_ = app;
  9570.  
  9571.       this.launch_.textContent = app.data.title;
  9572.  
  9573.       this.forAllLaunchTypes_(function(launchTypeButton, id) {
  9574.         launchTypeButton.disabled = false;
  9575.         launchTypeButton.checked = app.data.launch_type == id;
  9576.       });
  9577.  
  9578.       this.options_.disabled = !app.data.optionsUrl || !app.data.enabled;
  9579.       this.details_.disabled = !app.data.detailsUrl;
  9580.       this.uninstall_.disabled = !app.data.mayDisable;
  9581.  
  9582.       this.disableNotifications_.hidden = true;
  9583.       var notificationsDisabled = app.data.notifications_disabled;
  9584.       if (typeof notificationsDisabled != 'undefined') {
  9585.         this.disableNotifications_.hidden = false;
  9586.         this.disableNotifications_.checked = notificationsDisabled;
  9587.       }
  9588.     },
  9589.  
  9590.     /**
  9591.      * Handlers for menu item activation.
  9592.      * @param {Event} e The activation event.
  9593.      * @private
  9594.      */
  9595.     onLaunch_: function(e) {
  9596.       chrome.send('launchApp', [this.app_.appId, APP_LAUNCH.NTP_APPS_MENU]);
  9597.     },
  9598.     onLaunchTypeChanged_: function(e) {
  9599.       var pressed = e.currentTarget;
  9600.       var app = this.app_;
  9601.       this.forAllLaunchTypes_(function(launchTypeButton, id) {
  9602.         if (launchTypeButton == pressed) {
  9603.           chrome.send('setLaunchType', [app.appId, id]);
  9604.           // Manually update the launch type. We will only get
  9605.           // appsPrefChangeCallback calls after changes to other NTP instances.
  9606.           app.data.launch_type = id;
  9607.         }
  9608.       });
  9609.     },
  9610.     onShowOptions_: function(e) {
  9611.       window.location = this.app_.data.optionsUrl;
  9612.     },
  9613.     onShowDetails_: function(e) {
  9614.       var url = this.app_.data.detailsUrl;
  9615.       url = appendParam(url, 'utm_source', 'chrome-ntp-launcher');
  9616.       window.location = url;
  9617.     },
  9618.     onDisableNotifications_: function(e) {
  9619.       var app = this.app_;
  9620.       app.removeBubble();
  9621.       // Toggle the current disable setting.
  9622.       var newSetting = !this.disableNotifications_.checked;
  9623.       app.data.notifications_disabled = newSetting;
  9624.       chrome.send('setNotificationsDisabled', [app.data.id, newSetting]);
  9625.     },
  9626.     onUninstall_: function(e) {
  9627.       chrome.send('uninstallApp', [this.app_.data.id]);
  9628.     },
  9629.     onCreateShortcut_: function(e) {
  9630.       chrome.send('createAppShortcut', [this.app_.data.id]);
  9631.     },
  9632.   };
  9633.  
  9634.   /**
  9635.    * Creates a new App object.
  9636.    * @param {Object=} opt_data The data representing the app.
  9637.    * @constructor
  9638.    * @extends {HTMLDivElement}
  9639.    */
  9640.   function App(opt_data) {
  9641.     var el = cr.doc.createElement('div');
  9642.     el.__proto__ = App.prototype;
  9643.     el.initialize_();
  9644.  
  9645.     if (opt_data)
  9646.       el.data = opt_data;
  9647.  
  9648.     return el;
  9649.   }
  9650.  
  9651.   App.prototype = Tile.subclass({
  9652.     __proto__: HTMLDivElement.prototype,
  9653.  
  9654.     /**
  9655.      * Initialize the app object.
  9656.      * @private
  9657.      */
  9658.     initialize_: function() {
  9659.       Tile.prototype.initialize.apply(this, arguments);
  9660.  
  9661.       this.classList.add('app');
  9662.       this.classList.add('focusable');
  9663.     },
  9664.  
  9665.     /**
  9666.      * Formats this app according to |data|.
  9667.      * @param {Object} data The data object that describes the app.
  9668.      * @private
  9669.      */
  9670.     formatApp_: function(data) {
  9671.       assert(this.data_.id, 'Got an app without an ID');
  9672.       this.id = this.data_.id;
  9673.       this.setAttribute('role', 'menuitem');
  9674.  
  9675.       if (!this.data_.icon_big_exists && this.data_.icon_small_exists)
  9676.         this.useSmallIcon_ = true;
  9677.  
  9678.       // TODO(pedrosimonetti): Fix crbug.com/165612
  9679.       if (!this.appContents_) {
  9680.         this.appContents_ = this.useSmallIcon_ ?
  9681.             $('app-small-icon-template').cloneNode(true) :
  9682.             $('app-large-icon-template').cloneNode(true);
  9683.         this.appContents_.id = '';
  9684.         this.appendChild(this.appContents_);
  9685.       }
  9686.  
  9687.       this.appImgContainer_ = this.querySelector('.app-img-container');
  9688.       this.appImg_ = this.appImgContainer_.querySelector('img');
  9689.       this.setIcon();
  9690.  
  9691.       var appTitle;
  9692.       if (this.useSmallIcon_) {
  9693.         this.classList.add('small-icon');
  9694.         this.imgDiv_ = this.querySelector('.app-icon-div');
  9695.         this.addLaunchClickTarget_(this.imgDiv_);
  9696.         this.imgDiv_.title = this.data_.title;
  9697.         appTitle = formatTitle(this.data_.title);
  9698.         chrome.send('getAppIconDominantColor', [this.id]);
  9699.       } else {
  9700.         this.classList.remove('small-icon');
  9701.         this.addLaunchClickTarget_(this.appImgContainer_);
  9702.         this.appImgContainer_.title = this.data_.title;
  9703.         appTitle = this.data_.title;
  9704.       }
  9705.  
  9706.       var appSpan = this.appContents_.querySelector('.title');
  9707.       appSpan.textContent = appTitle;
  9708.       appSpan.title = this.data_.title;
  9709.       this.addLaunchClickTarget_(appSpan);
  9710.  
  9711.       var notification = this.data_.notification;
  9712.       var hasNotification = typeof notification != 'undefined' &&
  9713.                             typeof notification['title'] != 'undefined' &&
  9714.                             typeof notification['body'] != 'undefined' &&
  9715.                             !this.data_.notifications_disabled;
  9716.       if (hasNotification)
  9717.         this.setupNotification_(notification);
  9718.  
  9719.       this.addEventListener('keydown', cr.ui.contextMenuHandler);
  9720.       this.addEventListener('keyup', cr.ui.contextMenuHandler);
  9721.  
  9722.       // This hack is here so that appContents.contextMenu will be the same as
  9723.       // this.contextMenu.
  9724.       var self = this;
  9725.       this.appContents_.__defineGetter__('contextMenu', function() {
  9726.         return self.contextMenu;
  9727.       });
  9728.       this.appContents_.addEventListener('contextmenu',
  9729.                                          cr.ui.contextMenuHandler);
  9730.  
  9731.       this.addEventListener('mousedown', this.onMousedown_, true);
  9732.       this.addEventListener('keydown', this.onKeydown_);
  9733.       this.addEventListener('keyup', this.onKeyup_);
  9734.     },
  9735.  
  9736.     /**
  9737.      * Sets the color of the favicon dominant color bar.
  9738.      * @param {string} color The css-parsable value for the color.
  9739.      */
  9740.     set stripeColor(color) {
  9741.       this.querySelector('.color-stripe').style.backgroundColor = color;
  9742.     },
  9743.  
  9744.     /**
  9745.      * Removes the app tile from the page. Should be called after the app has
  9746.      * been uninstalled.
  9747.      */
  9748.     remove: function(opt_animate) {
  9749.       // Unset the ID immediately, because the app is already gone. But leave
  9750.       // the tile on the page as it animates out.
  9751.       this.id = '';
  9752.  
  9753.       if (opt_animate) {
  9754.         var cell = this.tileCell;
  9755.         var tilePage = cell.tilePage;
  9756.         tilePage.dataList_.splice(cell.index, 1);
  9757.         tilePage.animateTileRemoval(cell.index, tilePage.dataList_);
  9758.       } else {
  9759.         this.tileCell.doRemove(opt_animate);
  9760.       }
  9761.     },
  9762.  
  9763.     /**
  9764.      * Set the URL of the icon from |this.data_|. This won't actually show the
  9765.      * icon until loadIcon() is called (for performance reasons; we don't want
  9766.      * to load icons until we have to).
  9767.      */
  9768.     setIcon: function() {
  9769.       var src = this.useSmallIcon_ ? this.data_.icon_small :
  9770.                                      this.data_.icon_big;
  9771.       if (!this.data_.enabled ||
  9772.           (!this.data_.offlineEnabled && !navigator.onLine)) {
  9773.         src += '?grayscale=true';
  9774.       }
  9775.  
  9776.       this.appImgSrc_ = src;
  9777.       this.classList.add('icon-loading');
  9778.     },
  9779.  
  9780.     /**
  9781.      * Shows the icon for the app. That is, it causes chrome to load the app
  9782.      * icon resource.
  9783.      */
  9784.     loadIcon: function() {
  9785.       if (this.appImgSrc_) {
  9786.         this.appImg_.src = this.appImgSrc_;
  9787.         this.appImg_.classList.remove('invisible');
  9788.         this.appImgSrc_ = null;
  9789.       }
  9790.  
  9791.       this.classList.remove('icon-loading');
  9792.     },
  9793.  
  9794.     /**
  9795.      * Creates a bubble node.
  9796.      * @param {Object} notification The notification to show in the bubble.
  9797.      * @param {boolean} full Whether we want the headline or just the content.
  9798.      * @private
  9799.      */
  9800.     createBubbleNode_: function(notification, full) {
  9801.       if (!full) {
  9802.         var titleItem = this.ownerDocument.createElement('span');
  9803.         titleItem.textContent = notification['title'];
  9804.         return titleItem;
  9805.       } else {
  9806.         var container = this.ownerDocument.createElement('div');
  9807.  
  9808.         var messageItem = this.ownerDocument.createElement('div');
  9809.         messageItem.textContent = notification['body'];
  9810.         container.appendChild(messageItem);
  9811.  
  9812.         if (notification['linkUrl'] && notification['linkText']) {
  9813.           var anchor = this.ownerDocument.createElement('a');
  9814.           anchor.href = notification['linkUrl'];
  9815.           anchor.textContent = notification['linkText'];
  9816.           container.appendChild(anchor);
  9817.         }
  9818.  
  9819.         return container;
  9820.       }
  9821.     },
  9822.  
  9823.     /**
  9824.      * Sets up a notification for the app icon.
  9825.      * @param {Object} notification The notification to show in the bubble.
  9826.      * @private
  9827.      */
  9828.     setupNotification_: function(notification) {
  9829.       if (notification) {
  9830.         var infoBubble;
  9831.         if (!this.currentBubbleShowing_) {
  9832.           // Create a new bubble.
  9833.           infoBubble = new cr.ui.ExpandableBubble;
  9834.           infoBubble.anchorNode = this;
  9835.           infoBubble.appId = this.data_.id;
  9836.           infoBubble.handleCloseEvent = function() {
  9837.             chrome.send('closeNotification', [this.appId]);
  9838.             infoBubble.hide();
  9839.           };
  9840.         } else {
  9841.           // Reuse the old bubble instead of popping up a new bubble over
  9842.           // the old one.
  9843.           infoBubble = this.currentBubbleShowing_;
  9844.           infoBubble.collapseBubble_();
  9845.         }
  9846.         infoBubble.contentTitle = this.createBubbleNode_(notification, false);
  9847.         infoBubble.content = this.createBubbleNode_(notification, true);
  9848.         infoBubble.show();
  9849.         infoBubble.resizeAndReposition();
  9850.  
  9851.         this.currentBubbleShowing_ = infoBubble;
  9852.       }
  9853.     },
  9854.  
  9855.     /**
  9856.      *  Removes the info bubble if there is one.
  9857.      */
  9858.     removeBubble: function() {
  9859.       if (this.currentBubbleShowing_) {
  9860.         this.currentBubbleShowing_.hide();
  9861.         this.currentBubbleShowing_ = null;
  9862.       }
  9863.     },
  9864.  
  9865.     /**
  9866.      * Invoked when an app is clicked.
  9867.      * @param {Event} e The click event.
  9868.      * @private
  9869.      */
  9870.     onClick_: function(e) {
  9871.       var url = !this.data_.is_webstore ? '' :
  9872.           appendParam(this.data_.url,
  9873.                       'utm_source',
  9874.                       'chrome-ntp-icon');
  9875.  
  9876.       chrome.send('launchApp',
  9877.                   [this.appId, APP_LAUNCH.NTP_APPS_MAXIMIZED, url,
  9878.                    e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
  9879.  
  9880.       // Don't allow the click to trigger a link or anything
  9881.       e.preventDefault();
  9882.     },
  9883.  
  9884.     /**
  9885.      * Invoked when the user presses a key while the app is focused.
  9886.      * @param {Event} e The key event.
  9887.      * @private
  9888.      */
  9889.     onKeydown_: function(e) {
  9890.       if (e.keyIdentifier == 'Enter') {
  9891.         chrome.send('launchApp',
  9892.                     [this.appId, APP_LAUNCH.NTP_APPS_MAXIMIZED, '',
  9893.                      0, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
  9894.         e.preventDefault();
  9895.         e.stopPropagation();
  9896.       }
  9897.       this.onKeyboardUsed_(e.keyCode);
  9898.     },
  9899.  
  9900.     /**
  9901.      * Invoked when the user releases a key while the app is focused.
  9902.      * @param {Event} e The key event.
  9903.      * @private
  9904.      */
  9905.     onKeyup_: function(e) {
  9906.       this.onKeyboardUsed_(e.keyCode);
  9907.     },
  9908.  
  9909.     /**
  9910.      * Called when the keyboard has been used (key down or up). The .click-focus
  9911.      * hack is removed if the user presses a key that can change focus.
  9912.      * @param {number} keyCode The key code of the keyboard event.
  9913.      * @private
  9914.      */
  9915.     onKeyboardUsed_: function(keyCode) {
  9916.       switch (keyCode) {
  9917.         case 9:  // Tab.
  9918.         case 37:  // Left arrow.
  9919.         case 38:  // Up arrow.
  9920.         case 39:  // Right arrow.
  9921.         case 40:  // Down arrow.
  9922.           this.classList.remove('click-focus');
  9923.       }
  9924.     },
  9925.  
  9926.     /**
  9927.      * Adds a node to the list of targets that will launch the app. This list
  9928.      * is also used in onMousedown to determine whether the app contents should
  9929.      * be shown as active (if we don't do this, then clicking anywhere in
  9930.      * appContents, even a part that is outside the ideally clickable region,
  9931.      * will cause the app icon to look active).
  9932.      * @param {HTMLElement} node The node that should be clickable.
  9933.      */
  9934.     addLaunchClickTarget_: function(node) {
  9935.       node.classList.add('launch-click-target');
  9936.       node.addEventListener('click', this.onClick_.bind(this));
  9937.     },
  9938.  
  9939.     /**
  9940.      * Handler for mousedown on the App. Adds a class that allows us to
  9941.      * not display as :active for right clicks and clicks on app notifications
  9942.      * (specifically, don't pulse on these occasions). Also, we don't pulse
  9943.      * for clicks that aren't within the clickable regions.
  9944.      * @param {Event} e The mousedown event.
  9945.      */
  9946.     onMousedown_: function(e) {
  9947.       if (e.button == 2 ||
  9948.           !findAncestorByClass(e.target, 'launch-click-target')) {
  9949.         this.appContents_.classList.add('suppress-active');
  9950.       } else {
  9951.         this.appContents_.classList.remove('suppress-active');
  9952.       }
  9953.  
  9954.       // This class is here so we don't show the focus state for apps that
  9955.       // gain keyboard focus via mouse clicking.
  9956.       this.classList.add('click-focus');
  9957.     },
  9958.  
  9959.     /**
  9960.      * Change the data and update the appearance of the app.
  9961.      * @param {Object} data The new data object that describes the app.
  9962.      */
  9963.     replaceAppData: function(data) {
  9964.       assert(data);
  9965.       this.data = data;
  9966.       this.setIcon();
  9967.       this.loadIcon();
  9968.     },
  9969.  
  9970.     /**
  9971.      * The data and preferences for this app.
  9972.      * @type {Object}
  9973.      */
  9974.     set data(data) {
  9975.       Object.getOwnPropertyDescriptor(Tile.prototype, 'data').set.apply(this,
  9976.           arguments);
  9977.  
  9978.       this.formatApp_(data);
  9979.     },
  9980.     get data() {
  9981.       return this.data_;
  9982.     },
  9983.  
  9984.     get appId() {
  9985.       return this.data_.id;
  9986.     },
  9987.  
  9988.     /**
  9989.      * Returns a pointer to the context menu for this app. All apps share the
  9990.      * singleton AppContextMenu. This function is called by the
  9991.      * ContextMenuHandler in response to the 'contextmenu' event.
  9992.      * @type {cr.ui.Menu}
  9993.      */
  9994.     get contextMenu() {
  9995.       var menu = AppContextMenu.getInstance();
  9996.       menu.setupForApp(this);
  9997.       return menu.menu;
  9998.     },
  9999.  
  10000.     /**
  10001.      * Returns whether this element can be 'removed' from chrome (i.e. whether
  10002.      * the user can drag it onto the trash and expect something to happen).
  10003.      * @return {boolean} True if the app can be uninstalled.
  10004.      */
  10005.     canBeRemoved: function() {
  10006.       return this.data_.mayDisable;
  10007.     },
  10008.  
  10009.     /**
  10010.      * Uninstalls the app after it's been dropped on the trash.
  10011.      */
  10012.     removeFromChrome: function() {
  10013.       chrome.send('uninstallApp', [this.data_.id, true]);
  10014.       this.tile.tilePage.removeTile(this.tile, true);
  10015.       if (this.currentBubbleShowing_)
  10016.         this.currentBubbleShowing_.hide();
  10017.     },
  10018.   });
  10019.  
  10020.   /**
  10021.    * Creates a new AppsPage object.
  10022.    * @constructor
  10023.    * @extends {TilePage}
  10024.    */
  10025.   function AppsPage() {
  10026.     var el = new TilePage();
  10027.     el.__proto__ = AppsPage.prototype;
  10028.     el.initialize();
  10029.  
  10030.     return el;
  10031.   }
  10032.  
  10033.   AppsPage.prototype = {
  10034.     __proto__: TilePage.prototype,
  10035.  
  10036.     /**
  10037.      * Reference to the Tile subclass that will be used to create the tiles.
  10038.      * @constructor
  10039.      * @extends {Tile}
  10040.      */
  10041.     TileClass: App,
  10042.  
  10043.     // The config object should be defined by a TilePage subclass if it
  10044.     // wants the non-default behavior.
  10045.     config: {
  10046.       // The width of a cell.
  10047.       cellWidth: 70,
  10048.       // The start margin of a cell (left or right according to text direction).
  10049.       cellMarginStart: 20,
  10050.       // The maximum number of Tiles to be displayed.
  10051.       maxTileCount: 512,
  10052.       // Whether the TilePage content will be scrollable.
  10053.       scrollable: true,
  10054.     },
  10055.  
  10056.     initialize: function() {
  10057.       TilePage.prototype.initialize.apply(this, arguments);
  10058.  
  10059.       this.classList.add('apps-page');
  10060.  
  10061.       this.addEventListener('cardselected', this.onCardSelected_);
  10062.       // Add event listeners for two events, so we can temporarily suppress
  10063.       // the app notification bubbles when the app card slides in and out of
  10064.       // view.
  10065.       this.addEventListener('carddeselected', this.onCardDeselected_);
  10066.       this.addEventListener('cardSlider:card_change_ended',
  10067.                             this.onCardChangeEnded_);
  10068.  
  10069.       this.addEventListener('tilePage:tile_added', this.onTileAdded_);
  10070.     },
  10071.  
  10072.     /**
  10073.      * Highlight a newly installed app as it's added to the NTP.
  10074.      * @param {Object} data The data object that describes the app.
  10075.      */
  10076.     insertAndHighlightApp: function(data) {
  10077.       ntp.getCardSlider().selectCardByValue(this);
  10078.       this.insertApp(data, true);
  10079.     },
  10080.  
  10081.     /**
  10082.      * Inserts an App into the TilePage, preserving the alphabetical order.
  10083.      * @param {Object} data The data that describes the app.
  10084.      * @param {boolean} animate Whether to animate the insertion.
  10085.      */
  10086.     insertApp: function(data, animate) {
  10087.       var index = this.tiles_.length;
  10088.       for (var i = 0; i < this.tiles_.length; i++) {
  10089.         if (data.title.toLocaleLowerCase() <
  10090.             this.tiles_[i].data.title.toLocaleLowerCase()) {
  10091.           index = i;
  10092.           break;
  10093.         }
  10094.       }
  10095.  
  10096.       if (animate) {
  10097.         this.dataList_.splice(index, 0, data);
  10098.         this.animateTileRestoration(index, this.dataList_);
  10099.       } else {
  10100.         var app = new App(data);
  10101.         this.addTileAt(app, index);
  10102.       }
  10103.     },
  10104.  
  10105.     /**
  10106.      * Handler for 'cardselected' event, fired when |this| is selected. The
  10107.      * first time this is called, we load all the app icons.
  10108.      * @private
  10109.      */
  10110.     onCardSelected_: function(e) {
  10111.       var apps = this.querySelectorAll('.app.icon-loading');
  10112.       for (var i = 0; i < apps.length; i++) {
  10113.         apps[i].loadIcon();
  10114.         if (apps[i].currentBubbleShowing_)
  10115.           apps[i].currentBubbleShowing_.suppressed = false;
  10116.       }
  10117.     },
  10118.  
  10119.     /**
  10120.      * Handler for tile additions to this page.
  10121.      * @param {Event} e The tilePage:tile_added event.
  10122.      */
  10123.     onTileAdded_: function(e) {
  10124.       assert(e.currentTarget == this);
  10125.       assert(e.addedTile instanceof App);
  10126.       if (this.classList.contains('selected-card'))
  10127.         e.addedTile.loadIcon();
  10128.     },
  10129.  
  10130.     /**
  10131.      * Handler for the when this.cardSlider ends change its card. If animated,
  10132.      * this happens when the -webkit-transition is done, otherwise happens
  10133.      * immediately (but after cardSlider:card_changed).
  10134.      * @private
  10135.      */
  10136.     onCardChangeEnded_: function(e) {
  10137.       for (var i = 0; i < this.tiles_.length; i++) {
  10138.         var app = this.tiles_[i];
  10139.         assert(app instanceof App);
  10140.         if (app.currentBubbleShowing_)
  10141.           app.currentBubbleShowing_.suppressed = false;
  10142.       }
  10143.     },
  10144.  
  10145.     /**
  10146.      * Handler for the 'carddeselected' event, fired when the user switches
  10147.      * to another 'card' than the App 'card' on the NTP (|this| gets
  10148.      * deselected).
  10149.      * @private
  10150.      */
  10151.     onCardDeselected_: function(e) {
  10152.       for (var i = 0; i < this.tiles_.length; i++) {
  10153.         var app = this.tiles_[i];
  10154.         assert(app instanceof App);
  10155.         if (app.currentBubbleShowing_)
  10156.           app.currentBubbleShowing_.suppressed = true;
  10157.       }
  10158.     },
  10159.  
  10160.     /** @override */
  10161.     onScroll: function() {
  10162.       TilePage.prototype.onScroll.apply(this, arguments);
  10163.  
  10164.       for (var i = 0; i < this.tiles_.length; i++) {
  10165.         var app = this.tiles_[i];
  10166.         assert(app instanceof App);
  10167.         if (app.currentBubbleShowing_)
  10168.           app.currentBubbleShowing_.resizeAndReposition();
  10169.         }
  10170.     },
  10171.  
  10172.     /**
  10173.      * Creates a new crx-less app manifest and installs it.
  10174.      * @param {Object} data The data object describing the link. Must have |url|
  10175.      *     and |title| members.
  10176.      */
  10177.     generateAppForLink: function(data) {
  10178.       assert(data.url != undefined);
  10179.       assert(data.title != undefined);
  10180.       chrome.send('generateAppForLink', [data.url, data.title, 0]);
  10181.     },
  10182.   };
  10183.  
  10184.   /**
  10185.    * Launches the specified app using the APP_LAUNCH_NTP_APP_RE_ENABLE
  10186.    * histogram. This should only be invoked from the AppLauncherHandler.
  10187.    * @param {String} appID The ID of the app.
  10188.    */
  10189.   function launchAppAfterEnable(appId) {
  10190.     chrome.send('launchApp', [appId, APP_LAUNCH.NTP_APP_RE_ENABLE]);
  10191.   }
  10192.  
  10193.   function appNotificationChanged(id, notification) {
  10194.     var app = $(id);
  10195.     // The app might have been uninstalled, or notifications might be disabled.
  10196.     if (app && !app.data.notifications_disabled)
  10197.       app.setupNotification_(notification);
  10198.   }
  10199.  
  10200.   /**
  10201.    * Formats titles by removing the leading 'http://www.' part of the URL,
  10202.    * and the last slash, so 'http://www.test.com/' becomes 'test.com'.
  10203.    * @param {string} title Page's title.
  10204.    * @return {string} The formatted title.
  10205.    */
  10206.   function formatTitle(title) {
  10207.     return title.replace(/^(https?\:\/\/)?(www\.)?|\/$/gi, '');
  10208.   }
  10209.  
  10210.   return {
  10211.     appNotificationChanged: appNotificationChanged,
  10212.     AppsPage: AppsPage,
  10213.     launchAppAfterEnable: launchAppAfterEnable,
  10214.   };
  10215. });
  10216. </script>
  10217. </head>
  10218.  
  10219. <body id="ntp5" i18n-values=".style.fontSize:fontsize">
  10220.   <div id="bottom-panel">
  10221.     <div id="bottom-panel-header">
  10222.       <ul id="dot-list" hidden></ul>
  10223.     </div>
  10224.     <div id="card-slider-frame">
  10225.       <div id="page-list"></div>
  10226.       <div id="attribution">
  10227.         <span i18n-content="attributionintro"></span>
  10228.         <img id="attribution-img">
  10229.       </div>
  10230.     </div>
  10231.     <div id="bottom-panel-footer">
  10232.       <div id="bottom-panel-toolbar">
  10233.         <div id="notification-container" hidden>
  10234.           <div id="notification">
  10235.             <span></span>
  10236.             <div id="notificationLinks"></div>
  10237.             <button class="close-button custom-appearance"></button>
  10238.           </div>
  10239.         </div>
  10240.       </div>
  10241.       <div id="bookmark-bar-spacer"></div>
  10242.     </div>
  10243.   </div>
  10244.   <div id="promo-bubble-anchor"></div>
  10245. </body>
  10246.  
  10247. <!-- A div to hold all the templates, and in the darkness bind them. -->
  10248. <div hidden>
  10249.  
  10250. <!-- NTP4 intro bubble -->
  10251. <div id="ntp4-intro-bubble-contents">
  10252.   <div></div>
  10253.   <a i18n-content="learn_more" target="_blank"></a>
  10254. </div>
  10255.  
  10256. <!-- App Contents w/ Large Icon -->
  10257. <div id="app-large-icon-template" class="app-contents">
  10258.   <div class="app-img-container">
  10259.     <img class="invisible">
  10260.   </div>
  10261.   <span class="title"></span>
  10262. </div>
  10263.  
  10264. <!-- App Contents w/ Small Icon -->
  10265. <div id="app-small-icon-template" class="app-contents">
  10266.   <div class="app-icon-div" aria-hidden="true">
  10267.     <div class="app-img-container">
  10268.       <img class="invisible" alt="">
  10269.     </div>
  10270.     <div class="color-stripe"></div>
  10271.   </div>
  10272.   <span class="title"></span>
  10273. </div>
  10274.  
  10275. </div>
  10276.  
  10277. </html>
  10278.