home *** CD-ROM | disk | FTP | other *** search
/ Freelog 115 / FreelogNo115-MaiJuin2013.iso / Internet / AvantBrowser / asetup.exe / _data / webkit / chrome.dll / 0 / BINDATA / 763 < prev    next >
Encoding:
Text File  |  2013-04-03  |  376.7 KB  |  12,262 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. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  56.  * Use of this source code is governed by a BSD-style license that can be
  57.  * found in the LICENSE file. */
  58.  
  59. .bubble {
  60.   position: absolute;
  61.   white-space: normal;
  62.   /* Height is dynamic, width fixed. */
  63.   width: 300px;
  64.   z-index: 9999;
  65. }
  66.  
  67. .bubble-content {
  68.   color: black;
  69.   left: 1px;
  70.   line-height: 150%;
  71.   padding: 8px 11px 12px;
  72.   position: relative;
  73.   right: 1px;
  74.   top: 1px;
  75.   width: 298px;
  76.   z-index: 3;
  77. }
  78.  
  79. /* When the close button is there, we need more padding on the right of the
  80.  * bubble. */
  81. .bubble-close:not([hidden]) ~ .bubble-content {
  82.   -webkit-padding-end: 22px;
  83. }
  84.  
  85. .bubble-close {
  86.   background-image: no-repeat 50% 50%;
  87.   height: 16px;
  88.   position: absolute;
  89.   right: 6px;
  90.   top: 6px;
  91.   width: 16px;
  92.   z-index: 4;
  93. }
  94.  
  95. html[dir='rtl'] .bubble-close {
  96.   left: 6px;
  97.   right: auto;
  98. }
  99.  
  100. .bubble-close {
  101.   background-image: -webkit-image-set(
  102.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAiElEQVR42r2RsQrDMAxEBRdl8SDcX8lQPGg1GBI6lvz/h7QyRRXV0qUULwfvwZ1tenw5PxToRPWMC52eA9+WDnlh3HFQ/xBQl86NFYJqeGflkiogrOvVlIFhqURFVho3x1moGAa3deMs+LS30CAhBN5nNxeT5hbJ1zwmji2k+aF6NENIPf/hs54f0sZFUVAMigAAAABJRU5ErkJggg==') 1x);
  103. }
  104.  
  105. .bubble-close:hover {
  106.   background-image: -webkit-image-set(
  107.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAqklEQVR4XqWRMQ6DMAxF/1Fyilyj2SmIBUG5QcTCyJA5Z8jGhlBPgRi4TmoDraVmKFJlWYrlp/g5QfwRlwEVNWVa4WzfH9jK6kCkEkBjwxOhLghheMWMELUAqqwQ4OCbnE4LJnhr5IYdqQt4DJQjhe9u4vBBmnxHHNzRFkDGjHDo0VuTAqy2vAG4NkvXXDHxbGsIGlj3e835VFNtdugma/Jk0eXq0lP//5svi4PtO01oFfYAAAAASUVORK5CYII=')
  108.           1x,
  109.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAB4UlEQVR42u2VsWoCQRBAh+MUFP0C1V9QD4NEOxs9xBQHQVCwSJFWVBAtBNXCxk6wTkBJYUTwEwQLC61E8QP0NzZzt5g5726DkC7EYWHZ8T3WndkV2C/jLwn4hwVYBIdLn9vkLp79QcBCTDMiy3w2gQ9XeTYkEHA8vqj2rworXu3HF1YFfSWgp5QFnKVLvYvzDEKEZ5hW70oXOCtcEbQLIkx7+IQtfMBSOjU6XEF4oyOdYInZbXyOuajjDlpNeQgleIUJKUz4BDMledhqOu/AzVSmzZ49CUjCC0yvim98iqtJT2L2jKsqczsdok9XrHNexaww415lnTNwn6CM/KxJIR8bnUZHPhLO6yMoIyk2pNjLewFuE5AiY1KMMQx8Q7hQYFek4AkjxXFe1rsF84I/BTFQMGL+1Lxwl4DwdtM1gjwKohgxyLtG7SYpxALqugOMcfOKN+bFXeBsLB1uulNcRqq7/tt36k41zoL6QlxGjtd6lrahiqCi1iOFYyvXuxY8yzK33VnvUivbLlOlj/jktm0s3YnXrNIXXufHNxuOGasi8S68zkwrlnV8ZcJJsTIUxbLgQcFZWE8N0gau2p40VVcM0gYeFpSRK6445UhBuKiRgiyKw+34rLt59nb1/7+RwReVkaFtqvNBuwAAAABJRU5ErkJggg==')
  110.           2x);
  111. }
  112.  
  113. .bubble-close:active {
  114.   background-image: -webkit-image-set(
  115.     url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARElEQVQoz2P4z4AfMlBLAYMdwxkghgEwD1XBGTC0g0sDIaYJECVwFqoChBK4WegKkJWArSJZAQErCDqSKG/iCyhaRhYA9LDIbULDzlIAAAAASUVORK5CYII=')
  116.         1x,
  117.     url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAA/ElEQVR4Xu3UsWrCUBiG4efGlIBoIMFbcnYolYJ3pg4iKGrGYFTRwaUFhYAekiDt0EG++X2W83N8/3J/DbwBMJJSsdQItcDY1VlCOImzq3Ed8OmicHASB3ns5KBw8VUNpDJrW7uAiJ3sbK1l0mqArpmFTUlQ5jYWZrrUAUSmT0SZm4qoA56JvVhs/5g3A7RLolA85A1ASOTye65NMxASK6syfxGITMzvMxG9CvRkliWwlOm9AsSOcitzU1NzK7mjuBkQvHtLK7iLBiB5PhttJSGpB8I8vM6kDuiHeUjoVwMfYR4SRtUAw1veIZzOjRhSBzCoyKFjgH/3K7+BHzg+Cgw0eSW3AAAAAElFTkSuQmCC')
  118.         2x);
  119. }
  120.  
  121. .bubble-shadow {
  122.   bottom: -2px;
  123.   box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
  124.   left: 0;
  125.   position: absolute;
  126.   right: 0;
  127.   top: 0;
  128.   z-index: 1;
  129. }
  130.  
  131. .bubble-arrow {
  132.   -webkit-transform: rotate(45deg);
  133.   box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.15);
  134.   height: 15px;
  135.   position: absolute;
  136.   width: 15px;
  137.   z-index: 2;
  138. }
  139.  
  140. .bubble-content,
  141. .bubble-arrow {
  142.   background: white;
  143. }
  144.  
  145. .bubble-shadow,
  146. .bubble-arrow {
  147.   border: 1px solid rgba(0, 0, 0, 0.3);
  148. }
  149.  
  150. .bubble-shadow,
  151. .bubble-content {
  152.   border-radius: 6px;
  153.   box-sizing: border-box;
  154. }
  155. </style>
  156. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  157.  * Use of this source code is governed by a BSD-style license that can be
  158.  * found in the LICENSE file. */
  159.  
  160. .expandable-bubble {
  161.   -webkit-border-image: url('chrome://theme/IDR_APP_NOTIFICATION_SMALL_BUBBLE')
  162.                         5 5 7 6 stretch;
  163.   -webkit-box-sizing: border-box;
  164.   -webkit-user-select: none;
  165.   border-width: 5px 5px 7px 6px;
  166.   color: #444;
  167.   cursor: pointer;
  168.   display: inline-block;
  169.   font-size: 12px;
  170.   position: absolute;
  171.   z-index: 1;
  172. }
  173.  
  174. .expandable-bubble::after {
  175.   bottom: -1px;
  176.   content: url('chrome://theme/IDR_APP_NOTIFICATION_NUB');
  177.   display: block;
  178.   height: 7px;
  179.   position: absolute;
  180.   right: 5px;  /* TODO(finnur): Need to handle RTL properly. */
  181.   width: 9px;
  182. }
  183.  
  184. .expandable-bubble > .expandable-bubble-contents > .expandable-bubble-title {
  185.   display: inline-block;
  186.   margin-left: 1px;
  187.   margin-top : -3px;
  188.   overflow: hidden;
  189.   white-space: nowrap;
  190. }
  191.  
  192. .expandable-bubble[masked] > .expandable-bubble-contents >
  193.     .expandable-bubble-title::after {
  194.   content: url('chrome://theme/IDR_APP_NOTIFICATION_NUB_MASK');
  195.   display: block;
  196.   height: 15px;
  197.   overflow: hidden;
  198.   position: absolute;
  199.   right: 0;
  200.   top: 0;
  201.   width: 12px;
  202. }
  203.  
  204. .expandable-bubble[expanded] > .expandable-bubble-contents >
  205.     .expandable-bubble-title {
  206.   font-size: 13px;
  207.   margin-bottom: 3px;
  208.   margin-left: 0;
  209. }
  210.  
  211. .expandable-bubble-close {
  212.   background-image: no-repeat 50% 50%;
  213.   height: 16px;
  214.   position: absolute;
  215.   right: 0;
  216.   top: 0;
  217.   width: 16px;
  218.   z-index: 2;
  219. }
  220.  
  221. .expandable-bubble[expanded] {
  222.   padding: 3px;
  223.   z-index: 3;  /* One higher then the close button on an unexpanded bubble. */
  224. }
  225.  
  226. .expandable-bubble[expanded] > .expandable-bubble-close {
  227.   z-index: 4;
  228. }
  229.  
  230. .expandable-bubble-close {
  231.   background-image: -webkit-image-set(
  232.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAiElEQVR42r2RsQrDMAxEBRdl8SDcX8lQPGg1GBI6lvz/h7QyRRXV0qUULwfvwZ1tenw5PxToRPWMC52eA9+WDnlh3HFQ/xBQl86NFYJqeGflkiogrOvVlIFhqURFVho3x1moGAa3deMs+LS30CAhBN5nNxeT5hbJ1zwmji2k+aF6NENIPf/hs54f0sZFUVAMigAAAABJRU5ErkJggg==') 1x);
  233. }
  234.  
  235. .expandable-bubble-close:hover {
  236.   background-image: -webkit-image-set(
  237.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAqklEQVR4XqWRMQ6DMAxF/1Fyilyj2SmIBUG5QcTCyJA5Z8jGhlBPgRi4TmoDraVmKFJlWYrlp/g5QfwRlwEVNWVa4WzfH9jK6kCkEkBjwxOhLghheMWMELUAqqwQ4OCbnE4LJnhr5IYdqQt4DJQjhe9u4vBBmnxHHNzRFkDGjHDo0VuTAqy2vAG4NkvXXDHxbGsIGlj3e835VFNtdugma/Jk0eXq0lP//5svi4PtO01oFfYAAAAASUVORK5CYII=')
  238.           1x,
  239.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAB4UlEQVR42u2VsWoCQRBAh+MUFP0C1V9QD4NEOxs9xBQHQVCwSJFWVBAtBNXCxk6wTkBJYUTwEwQLC61E8QP0NzZzt5g5726DkC7EYWHZ8T3WndkV2C/jLwn4hwVYBIdLn9vkLp79QcBCTDMiy3w2gQ9XeTYkEHA8vqj2rworXu3HF1YFfSWgp5QFnKVLvYvzDEKEZ5hW70oXOCtcEbQLIkx7+IQtfMBSOjU6XEF4oyOdYInZbXyOuajjDlpNeQgleIUJKUz4BDMledhqOu/AzVSmzZ49CUjCC0yvim98iqtJT2L2jKsqczsdok9XrHNexaww415lnTNwn6CM/KxJIR8bnUZHPhLO6yMoIyk2pNjLewFuE5AiY1KMMQx8Q7hQYFek4AkjxXFe1rsF84I/BTFQMGL+1Lxwl4DwdtM1gjwKohgxyLtG7SYpxALqugOMcfOKN+bFXeBsLB1uulNcRqq7/tt36k41zoL6QlxGjtd6lrahiqCi1iOFYyvXuxY8yzK33VnvUivbLlOlj/jktm0s3YnXrNIXXufHNxuOGasi8S68zkwrlnV8ZcJJsTIUxbLgQcFZWE8N0gau2p40VVcM0gYeFpSRK6445UhBuKiRgiyKw+34rLt59nb1/7+RwReVkaFtqvNBuwAAAABJRU5ErkJggg==')
  240.           2x);
  241. }
  242.  
  243. .expandable-bubble-close:active {
  244.   background-image: -webkit-image-set(
  245.     url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARElEQVQoz2P4z4AfMlBLAYMdwxkghgEwD1XBGTC0g0sDIaYJECVwFqoChBK4WegKkJWArSJZAQErCDqSKG/iCyhaRhYA9LDIbULDzlIAAAAASUVORK5CYII=')
  246.         1x,
  247.     url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAA/ElEQVR4Xu3UsWrCUBiG4efGlIBoIMFbcnYolYJ3pg4iKGrGYFTRwaUFhYAekiDt0EG++X2W83N8/3J/DbwBMJJSsdQItcDY1VlCOImzq3Ed8OmicHASB3ns5KBw8VUNpDJrW7uAiJ3sbK1l0mqArpmFTUlQ5jYWZrrUAUSmT0SZm4qoA56JvVhs/5g3A7RLolA85A1ASOTye65NMxASK6syfxGITMzvMxG9CvRkliWwlOm9AsSOcitzU1NzK7mjuBkQvHtLK7iLBiB5PhttJSGpB8I8vM6kDuiHeUjoVwMfYR4SRtUAw1veIZzOjRhSBzCoyKFjgH/3K7+BHzg+Cgw0eSW3AAAAAElFTkSuQmCC')
  248.         2x);
  249. }
  250. </style>
  251. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  252.  * Use of this source code is governed by a BSD-style license that can be
  253.  * found in the LICENSE file. */
  254.  
  255. menu {
  256.   -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, .50);
  257.   background: white;
  258.   color: black;
  259.   cursor: default;
  260.   left: 0;
  261.   margin: 0;
  262.   outline: 1px solid rgba(0, 0, 0, 0.2);
  263.   padding: 8px 0;
  264.   position: fixed;
  265.   white-space: nowrap;
  266.   z-index: 3;
  267. }
  268.  
  269. menu:not(.decorated) {
  270.   display: none;
  271. }
  272.  
  273. menu > * {
  274.   box-sizing: border-box;
  275.   display: block;
  276.   margin: 0;
  277.   text-align: start;
  278.   width: 100%;
  279. }
  280.  
  281. menu > :not(hr) {
  282.   -webkit-appearance: none;
  283.   background: transparent;
  284.   border: 0;
  285.   font: inherit;
  286.   line-height: 18px;
  287.   overflow: hidden;
  288.   padding: 0 19px;
  289.   text-overflow: ellipsis;
  290. }
  291.  
  292. menu > hr {
  293.   background: -webkit-linear-gradient(left,
  294.                                       rgba(0, 0, 0, .10),
  295.                                       rgba(0, 0, 0, .02) 96%);
  296.   border: 0;
  297.   height: 1px;
  298.   margin: 8px 0;
  299. }
  300.  
  301. menu > [disabled] {
  302.   color: rgba(0, 0, 0, .3);
  303. }
  304.  
  305. menu > [hidden] {
  306.   display: none;
  307. }
  308.  
  309. menu > :not(hr)[selected] {
  310.   background-color: rgb(220, 229, 250);
  311. }
  312.  
  313. menu > :not(hr)[selected]:active {
  314.   background-color: rgb(66, 109, 201);
  315.   color: #fff;
  316. }
  317.  
  318. menu > [checked]::before {
  319.   content: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAARklEQVQY02NgwA+MgViQkIJ3QKzEAFVpjEPBf5giJaiAMRYF72DWKSEJlKMpgNsgiCTxH5sCBhxWGOPzzV2sCv7//08QAwAUfjKK4sDXvQAAAABJRU5ErkJggg==');
  320.   display: inline-block;
  321.   height: 9px;
  322.   margin: 0 5px;
  323.   vertical-align: 50%;
  324.   width: 9px;
  325. }
  326.  
  327. menu > [checked] {
  328.   -webkit-padding-start: 0;
  329. }
  330.  
  331. menu > [selected][checked]:active::before {
  332.   content: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAATUlEQVQYV2P4//8/Ax5sDMSChBS8A2IlEEcQKoBNwX+YIiWoAEwhsgIQLQhTBBMoR1MA1gizDiYBA8gmM2BzA4oCZEUwhXfRFaArwokBBR4JwRMmHQoAAAAASUVORK5CYII=');
  333. }
  334.  
  335. /* TODO(zvorygin) menu > [shortcutText]::after - this selector is much better,
  336.  * but it's buggy in current webkit revision, so I have to use [showShortcuts].
  337.  */
  338. menu[showShortcuts] > ::after {
  339.   -webkit-padding-start: 30px;
  340.   color: #999;
  341.   content: attr(shortcutText);
  342.   float: right;
  343. }
  344. </style>
  345. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  346.  * Use of this source code is governed by a BSD-style license that can be
  347.  * found in the LICENSE file. */
  348.  
  349. /* NOTE: If you are using the drop-down style, you must first call
  350.  * MenuButton.createDropDownArrows() to initialize the CSS canvases that
  351.  * contain the arrow images. */
  352.  
  353. button.menu-button.drop-down {
  354.   background: white -webkit-canvas(drop-down-arrow) no-repeat center 4px;
  355.   border: 1px solid rgb(192, 195, 198);
  356.   border-radius: 2px;
  357.   height: 12px;
  358.   margin: 0 5px;
  359.   padding: 0;
  360.   position: relative;
  361.   top: 1px;
  362.   width: 12px;
  363. }
  364.  
  365. button.menu-button.drop-down:hover {
  366.   background-image: -webkit-canvas(drop-down-arrow-hover);
  367.   border-color: rgb(48, 57, 66);
  368. }
  369.  
  370. button.menu-button.drop-down[menu-shown],
  371. button.menu-button.drop-down:focus {
  372.   background-color: rgb(48, 57, 66);
  373.   background-image: -webkit-canvas(drop-down-arrow-active);
  374.   border-color: rgb(48, 57, 66);
  375. }
  376. </style>
  377. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  378.  * Use of this source code is governed by a BSD-style license that can be
  379.  * found in the LICENSE file. */
  380.  
  381. .trash {
  382.   -webkit-appearance: none;
  383.   background: none;
  384.   border: none;
  385.   cursor: pointer;
  386.   display: inline-block;
  387.   outline: none;
  388.   padding: 0;
  389.   position: relative;
  390.   width: 30px;
  391. }
  392.  
  393. .trash > span {
  394.   display: inline-block;
  395. }
  396.  
  397. .trash > .can,
  398. .trash > .lid {
  399.   background: url('chrome://resources/images/trash.png') 0 0 no-repeat;
  400.   left: 8px;
  401.   position: absolute;
  402.   right: 8px;
  403.   top: 2px;
  404. }
  405.  
  406. .trash > .lid {
  407.   -webkit-transform-origin: -7% 100%;
  408.   -webkit-transition: -webkit-transform 150ms;
  409.   height: 6px;
  410.   width: 14px;
  411. }
  412.  
  413. html[dir='rtl'] .trash > .lid {
  414.   -webkit-transform-origin: 107% 100%;
  415. }
  416.  
  417. .trash:focus > .lid,
  418. .trash:hover > .lid {
  419.   -webkit-transform: rotate(-45deg);
  420.   -webkit-transition: -webkit-transform 250ms;
  421. }
  422.  
  423. html[dir='rtl'] .trash:focus > .lid,
  424. html[dir='rtl'] .trash:hover > .lid {
  425.   -webkit-transform: rotate(45deg);
  426. }
  427.  
  428. .trash > .can {
  429.   background-position: -1px -4px;
  430.   height: 12px;
  431.   /* The margins match the background position offsets. */
  432.   margin-left: 1px;
  433.   /* The right margin is one greater due to a shadow on the trash image. */
  434.   margin-right: 2px;
  435.   margin-top: 4px;
  436.   width: 11px;
  437. }
  438. </style>
  439. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  440.  * Use of this source code is governed by a BSD-style license that can be
  441.  * found in the LICENSE file. */
  442.  
  443. /* This file defines styles for form controls. The order of rule blocks is
  444.  * important as there are some rules with equal specificity that rely on order
  445.  * as a tiebreaker. These are marked with OVERRIDE. */
  446.  
  447. /* Default state **************************************************************/
  448.  
  449. :-webkit-any(button,
  450.              input[type='button'],
  451.              input[type='submit']):not(.custom-appearance):not(.link-button),
  452. select,
  453. input[type='checkbox'],
  454. input[type='radio'] {
  455.   -webkit-appearance: none;
  456.   -webkit-user-select: none;
  457.   background-image: -webkit-linear-gradient(#ededed, #ededed 38%, #dedede);
  458.   border: 1px solid rgba(0, 0, 0, 0.25);
  459.   border-radius: 2px;
  460.   box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
  461.       inset 0 1px 2px rgba(255, 255, 255, 0.75);
  462.   color: #444;
  463.   font: inherit;
  464.   margin: 0 1px 0 0;
  465.   text-shadow: 0 1px 0 rgb(240, 240, 240);
  466. }
  467.  
  468. :-webkit-any(button,
  469.              input[type='button'],
  470.              input[type='submit']):not(.custom-appearance):not(.link-button),
  471. select {
  472.   min-height: 2em;
  473.   min-width: 4em;
  474. /* The following platform-specific rule is necessary to get adjacent
  475.    * buttons, text inputs, and so forth to align on their borders while also
  476.    * aligning on the text's baselines. */
  477.   padding-bottom: 1px;
  478. }
  479.  
  480. :-webkit-any(button,
  481.              input[type='button'],
  482.              input[type='submit']):not(.custom-appearance):not(.link-button) {
  483.   -webkit-padding-end: 10px;
  484.   -webkit-padding-start: 10px;
  485. }
  486.  
  487. select {
  488.   -webkit-appearance: none;
  489.   -webkit-padding-end: 20px;
  490.   -webkit-padding-start: 6px;
  491.   /* OVERRIDE */
  492.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAaUlEQVQoz2P4//8/A7UwdkEGhiggTsODo4g2LBEImJmZvwE1/UfHIHGQPNGGAbHCggULFrKxsf1ENgjEB4mD5EnxJoaByAZB5Yk3DNlAPj6+L8gGkWUYzMC3b982IRtEtmFQjaxYxDAwAGi4TwMYKNLfAAAAAElFTkSuQmCC'),
  493.       -webkit-linear-gradient(#ededed, #ededed 38%, #dedede);
  494.   background-position: right center;
  495.   background-repeat: no-repeat;
  496. }
  497.  
  498. html[dir='rtl'] select {
  499.   background-position: center left;
  500. }
  501.  
  502. input[type='checkbox'] {
  503.   bottom: 2px;
  504.   height: 13px;
  505.   position: relative;
  506.   vertical-align: middle;
  507.   width: 13px;
  508. }
  509.  
  510. input[type='radio'] {
  511.   /* OVERRIDE */
  512.   border-radius: 100%;
  513.   bottom: 3px;
  514.   height: 15px;
  515.   position: relative;
  516.   vertical-align: middle;
  517.   width: 15px;
  518. }
  519.  
  520. /* TODO(estade): add more types here? */
  521. input[type='password'],
  522. input[type='search'],
  523. input[type='text'],
  524. input[type='url'],
  525. input:not([type]),
  526. textarea {
  527.   border: 1px solid #bfbfbf;
  528.   border-radius: 2px;
  529.   box-sizing: border-box;
  530.   color: #444;
  531.   font: inherit;
  532.   margin: 0;
  533.   /* Use min-height to accommodate addditional padding for touch as needed. */
  534.   min-height: 2em;
  535.   padding: 3px;
  536. /* For better alignment between adjacent buttons and inputs. */
  537.   padding-bottom: 4px;
  538. }
  539.  
  540. input[type='search'] {
  541.   -webkit-appearance: textfield;
  542.   /* NOTE: Keep a relatively high min-width for this so we don't obscure the end
  543.    * of the default text in relatively spacious languages (i.e. German). */
  544.   min-width: 160px;
  545. }
  546.  
  547. /* Remove when https://bugs.webkit.org/show_bug.cgi?id=51499 is fixed.
  548.  * TODO(dbeam): are there more types that would benefit from this? */
  549. input[type='search']::-webkit-textfield-decoration-container {
  550.   direction: inherit;
  551. }
  552.  
  553. /* Checked ********************************************************************/
  554.  
  555. input[type='checkbox']:checked::before {
  556.   -webkit-user-select: none;
  557.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAcklEQVQY02NgwA/YoJgoEA/Es4DYgJBCJSBeD8SboRinBiYg7kZS2IosyQ/Eakh8LySFq4FYHFlxGRBvBOJYqMRqJMU+yApNkSRAeC0Sux3dfSCTetE0wKyXxOWhMKhTYIr9CAUXyJMzgLgBagBBgDPGAI2LGdNt0T1AAAAAAElFTkSuQmCC');
  558.   background-size: 100% 100%;
  559.   content: '';
  560.   display: block;
  561.   height: 100%;
  562.   width: 100%;
  563. }
  564.  
  565. html[dir='rtl'] input[type='checkbox']:checked::before {
  566.   -webkit-transform: scaleX(-1);
  567. }
  568.  
  569. input[type='radio']:checked::before {
  570.   background-color: #666;
  571.   border-radius: 100%;
  572.   bottom: 3px;
  573.   content: '';
  574.   display: block;
  575.   left: 3px;
  576.   position: absolute;
  577.   right: 3px;
  578.   top: 3px;
  579. }
  580.  
  581. /* Hover **********************************************************************/
  582.  
  583. :enabled:hover:-webkit-any(
  584.     select,
  585.     input[type='checkbox'],
  586.     input[type='radio'],
  587.     :-webkit-any(
  588.         button,
  589.         input[type='button'],
  590.         input[type='submit']):not(.custom-appearance):not(.link-button)) {
  591.   background-image: -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
  592.   border-color: rgba(0, 0, 0, 0.3);
  593.   box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12),
  594.       inset 0 1px 2px rgba(255, 255, 255, 0.95);
  595.   color: black;
  596. }
  597.  
  598. :enabled:hover:-webkit-any(select) {
  599.   /* OVERRIDE */
  600.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAaUlEQVQoz2P4//8/A7UwdkEGhiggTsODo4g2LBEImJmZvwE1/UfHIHGQPNGGAbHCggULFrKxsf1ENgjEB4mD5EnxJoaByAZB5Yk3DNlAPj6+L8gGkWUYzMC3b982IRtEtmFQjaxYxDAwAGi4TwMYKNLfAAAAAElFTkSuQmCC'),
  601.       -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
  602. }
  603.  
  604. /* Active *********************************************************************/
  605.  
  606. :enabled:active:-webkit-any(
  607.     select,
  608.     input[type='checkbox'],
  609.     input[type='radio'],
  610.     :-webkit-any(
  611.         button,
  612.         input[type='button'],
  613.         input[type='submit']):not(.custom-appearance):not(.link-button)) {
  614.   background-image: -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
  615.   box-shadow: none;
  616.   text-shadow: none;
  617. }
  618.  
  619. :enabled:active:-webkit-any(select) {
  620.   /* OVERRIDE */
  621.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAaUlEQVQoz2P4//8/A7UwdkEGhiggTsODo4g2LBEImJmZvwE1/UfHIHGQPNGGAbHCggULFrKxsf1ENgjEB4mD5EnxJoaByAZB5Yk3DNlAPj6+L8gGkWUYzMC3b982IRtEtmFQjaxYxDAwAGi4TwMYKNLfAAAAAElFTkSuQmCC'),
  622.       -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
  623. }
  624.  
  625. /* Disabled *******************************************************************/
  626.  
  627. :disabled:-webkit-any(
  628.     button,
  629.     input[type='button'],
  630.     input[type='submit']):not(.custom-appearance):not(.link-button),
  631. select:disabled {
  632.   background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
  633.   border-color: rgba(80, 80, 80, 0.2);
  634.   box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08),
  635.       inset 0 1px 2px rgba(255, 255, 255, 0.75);
  636.   color: #aaa;
  637. }
  638.  
  639. select:disabled {
  640.   /* OVERRIDE */
  641.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAWklEQVQoz2P4//8/A7UwdkEGhiggTsODo4g2LBEIGhoa/uPCIHmiDQNihQULFizEZhBIHCRPijexGggzCCpPvGHoBiIbRJZhMAPfvn3bhGwQ2YZBNbJiEcPAAIgGZrTRc1ZLAAAAAElFTkSuQmCC'),
  642.       -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
  643. }
  644.  
  645. input:disabled:-webkit-any([type='checkbox'],
  646.                            [type='radio']) {
  647.   opacity: .75;
  648. }
  649.  
  650. input:disabled:-webkit-any([type='password'],
  651.                            [type='search'],
  652.                            [type='text'],
  653.                            [type='url'],
  654.                            :not([type])) {
  655.   color: #999;
  656. }
  657.  
  658. /* Focus **********************************************************************/
  659.  
  660. :enabled:focus:-webkit-any(
  661.     select,
  662.     input[type='checkbox'],
  663.     input[type='password'],
  664.     input[type='radio'],
  665.     input[type='search'],
  666.     input[type='text'],
  667.     input[type='url'],
  668.     input:not([type]),
  669.     :-webkit-any(
  670.          button,
  671.          input[type='button'],
  672.          input[type='submit']):not(.custom-appearance):not(.link-button)) {
  673.   /* OVERRIDE */
  674.   -webkit-transition: border-color 200ms;
  675.   /* We use border color because it follows the border radius (unlike outline).
  676.    * This is particularly noticeable on mac. */
  677.   border-color: rgb(77, 144, 254);
  678.   outline: none;
  679. }
  680.  
  681. /* Link buttons ***************************************************************/
  682.  
  683. .link-button {
  684.   -webkit-box-shadow: none;
  685.   background: transparent none;
  686.   border: none;
  687.   color: rgb(17, 85, 204);
  688.   cursor: pointer;
  689.   /* Input elements have -webkit-small-control which can override the body font.
  690.    * Resolve this by using 'inherit'. */
  691.   font: inherit;
  692.   margin: 0;
  693.   padding: 0 4px;
  694. }
  695.  
  696. .link-button:hover {
  697.   text-decoration: underline;
  698. }
  699.  
  700. .link-button:active {
  701.   color: rgb(5, 37, 119);
  702.   text-decoration: underline;
  703. }
  704.  
  705. .link-button[disabled] {
  706.   color: #999;
  707.   cursor: default;
  708.   text-decoration: none;
  709. }
  710.  
  711. /* Checkbox/radio helpers ******************************************************
  712.  *
  713.  * .checkbox and .radio classes wrap labels. Checkboxes and radios should use
  714.  * these classes with the markup structure:
  715.  *
  716.  *   <div class="checkbox">
  717.  *     <label>
  718.  *       <input type="checkbox"></input>
  719.  *       <span>
  720.  *     </label>
  721.  *   </div>
  722.  */
  723.  
  724. :-webkit-any(.checkbox, .radio) label {
  725.   /* Don't expand horizontally: <http://crbug.com/112091>. */
  726.   display: -webkit-inline-box;
  727.   padding-bottom: 7px;
  728.   padding-top: 7px;
  729. }
  730.  
  731. :-webkit-any(.checkbox, .radio) label input ~ span {
  732.   -webkit-margin-start: 0.6em;
  733.   /* Make sure long spans wrap at the same horizontal position they start. */
  734.   display: block;
  735. }
  736.  
  737. :-webkit-any(.checkbox, .radio) label:hover {
  738.   color: black;
  739. }
  740.  
  741. label > input:disabled:-webkit-any([type='checkbox'], [type='radio']) ~ span {
  742.   color: #999;
  743. }
  744. </style>
  745. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  746.  * Use of this source code is governed by a BSD-style license that can be
  747.  * found in the LICENSE file. */
  748.  
  749. .app {
  750.   outline: none;
  751.   position: absolute;
  752.   text-align: center;
  753. }
  754.  
  755. .app-contents {
  756.   -webkit-transition: -webkit-transform 100ms;
  757. }
  758.  
  759. .app-contents:active:not(.suppress-active),
  760. .app:not(.click-focus):focus .app-contents:not(.suppress-active),
  761. .drag-representation:not(.placing) .app-contents {
  762.   -webkit-transform: scale(1.1);
  763. }
  764.  
  765. /* Don't animate the initial scaling.  */
  766. .app-contents:active:not(.suppress-active),
  767. /* Active gets applied right before .suppress-active, so to avoid flicker
  768.  * we need to make the scale go back to normal without an animation. */
  769. .app-contents.suppress-active {
  770.   -webkit-transition-duration: 0;
  771. }
  772.  
  773. .app-contents > span {
  774.   display: block;
  775.   overflow: hidden;
  776.   text-overflow: ellipsis;
  777.   white-space: nowrap;
  778. }
  779.  
  780. .app-img-container {
  781.   /* -webkit-mask-image set by JavaScript to the image source. */
  782.   -webkit-mask-size: 100% 100%;
  783.   margin-left: auto;
  784.   margin-right: auto;
  785. }
  786.  
  787. .app-img-container > * {
  788.   height: 100%;
  789.   width: 100%;
  790. }
  791.  
  792. .app-icon-div {
  793.   -webkit-box-align: center;
  794.   -webkit-box-pack: center;
  795.   background-color: white;
  796.   border: 1px solid #d5d5d5;
  797.   border-radius: 5px;
  798.   display: -webkit-box;
  799.   margin-left: auto;
  800.   margin-right: auto;
  801.   position: relative;
  802.   vertical-align: middle;
  803.   z-index: 0;
  804. }
  805.  
  806. .app-icon-div .app-img-container {
  807.   bottom: 10px;
  808.   left: 10px;
  809.   position: absolute;
  810. }
  811.  
  812. .app-icon-div .color-stripe {
  813.   border-bottom-left-radius: 5px 5px;
  814.   border-bottom-right-radius: 5px 5px;
  815.   bottom: 0;
  816.   height: 3px;
  817.   opacity: 1.0;
  818.   position: absolute;
  819.   width: 100%;
  820.   z-index: 100;
  821. }
  822.  
  823. .app-context-menu > button:first-child {
  824.   font-weight: bold;
  825. }
  826.  
  827. .app-context-menu {
  828.   z-index: 1000;
  829. }
  830.  
  831. .app-context-menu > [checked]::before {
  832.   height: 5px;
  833. }
  834.  
  835. .launch-click-target {
  836.   cursor: pointer;
  837. }
  838.  
  839. /* Notifications */
  840.  
  841. .app-notification {
  842.   -webkit-transition: color 150ms linear;
  843.   color: #999;
  844.   display: block;
  845.   font-size: 0.9em;
  846.   white-space: nowrap;
  847. }
  848.  
  849. .app-notification:hover {
  850.   text-decoration: underline;
  851. }
  852.  
  853. .app-img-container > img:first-child {
  854.   display: block;
  855. }
  856.  
  857. .app .invisible {
  858.   visibility: hidden;
  859. }
  860.  
  861. /* Move the notification lower on apps pages to account for the 16px of
  862.  * transparency each app icon should have. */
  863. .apps-page #notification-container {
  864.   bottom: 15px;
  865. }
  866. </style>
  867. <link rel="stylesheet" href="chrome://newtab/suggestions_page.css">
  868. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  869.  * Use of this source code is governed by a BSD-style license that can be
  870.  * found in the LICENSE file. */
  871.  
  872. .most-visited {
  873.   position: absolute;
  874.   z-index: 0;
  875. }
  876.  
  877. .most-visited {
  878.   -webkit-box-orient: vertical;
  879.   display: -webkit-box;
  880.   position: absolute;
  881.   text-decoration: none;
  882. }
  883.  
  884. .most-visited:focus {
  885.   outline: none;
  886. }
  887.  
  888. .fills-parent {
  889.   bottom: 0;
  890.   display: -webkit-box;
  891.   left: 0;
  892.   position: absolute;
  893.   right: 0;
  894.   top: 0;
  895. }
  896.  
  897. /* filler mode: hide everything except the thumbnail --- leave a grey rectangle
  898.  * in its place. */
  899. .filler * {
  900.   visibility: hidden;
  901. }
  902.  
  903. .filler {
  904.   pointer-events: none;
  905. }
  906.  
  907. .most-visited .close-button {
  908.   -webkit-transition: opacity 150ms;
  909.   opacity: 0;
  910.   position: absolute;
  911.   right: 0;
  912.   top: 0;
  913.   z-index: 5;
  914. }
  915.  
  916. html[dir=rtl] .most-visited .close-button {
  917.   left: 0;
  918.   right: auto;
  919. }
  920.  
  921. .most-visited:hover .close-button {
  922.   -webkit-transition-delay: 500ms;
  923.   opacity: 1;
  924. }
  925.  
  926. .most-visited .close-button:hover {
  927.   -webkit-transition: none;
  928. }
  929.  
  930. .most-visited .favicon {
  931.   -webkit-margin-start: 5px;
  932.   background: no-repeat left 50%;
  933.   background-size: 16px;
  934.   bottom: 7px;
  935.   box-sizing: border-box;
  936.   display: block;
  937.   height: 16px;
  938.   position: absolute;
  939.   width: 16px;
  940. }
  941.  
  942. html[dir='rtl'] .most-visited .favicon {
  943.   background-position-x: right;
  944. }
  945.  
  946. .most-visited .color-stripe {
  947.   border-bottom-left-radius: 3px 3px;
  948.   border-bottom-right-radius: 3px 3px;
  949.   /* Matches height of title.  */
  950.   bottom: 23px;
  951.   height: 3px;
  952.   /* Matches padding-top of the title. */
  953.   margin-bottom: 8px;
  954.   position: absolute;
  955.   width: 100%;
  956.   z-index: 10;
  957. }
  958.  
  959. .most-visited .title {
  960.   display: block;
  961.   height: 23px;
  962.   overflow: hidden;
  963.   padding-top: 8px;
  964.   text-align: center;
  965.   text-overflow: ellipsis;
  966.   white-space: nowrap;
  967. }
  968.  
  969. .thumbnail {
  970.   -webkit-transition: opacity 150ms;
  971.   background: no-repeat;
  972.   /* This shows for missing thumbnails. */
  973.   background-color: #eee;
  974.   background-size: 100%;
  975.   border-radius: 3px;
  976.   /* These max dimensions are not necessary, as the sizing logic in the .js
  977.    * should be sufficient, but they're here for extra insurance. We never want
  978.    * to scale a thumbnail larger than this size. */
  979.   max-height: 132px;
  980.   max-width: 212px;
  981. }
  982.  
  983. .filler .thumbnail {
  984.   /* TODO(estade): there seems to be a webkit bug where this border is not
  985.    * always removed when it should be. Investigate. */
  986.   border: 1px solid;
  987.   visibility: visible;
  988. }
  989.  
  990. .thumbnail-shield {
  991.   background: -webkit-linear-gradient(rgba(255, 255, 255, 0),
  992.                                       rgba(255, 255, 255, 0) 50%,
  993.                                       rgba(255, 255, 255, 0.9));
  994.   border-radius: 3px;
  995. }
  996.  
  997. /* TODO(dbeam): Remove this when printing of -webkit-linear-gradient() works. */
  998. @media print {
  999.   .thumbnail-shield {
  1000.     background: none;
  1001.   }
  1002. }
  1003.  
  1004. .most-visited:focus .thumbnail,
  1005. .most-visited:hover .thumbnail {
  1006.   opacity: 0.95;
  1007. }
  1008.  
  1009. .most-visited:focus .thumbnail-shield,
  1010. .most-visited:hover .thumbnail-shield,
  1011. .most-visited:active .thumbnail-shield {
  1012.   background: -webkit-linear-gradient(rgba(255, 255, 255, 0),
  1013.                                       rgba(255, 255, 255, 0) 80%,
  1014.                                       rgba(255, 255, 255, 0.9));
  1015. }
  1016.  
  1017. /* The thumbnail gets lighter when clicked, but not when the click is on the
  1018.  * close button. */
  1019. .most-visited:active .close-button:not(:active) + .thumbnail {
  1020.   opacity: 0.9;
  1021. }
  1022.  
  1023. /* The thumbnail gets a shadow when clicked, but not when the click is on the
  1024.  * close button. */
  1025. .most-visited:active .close-button:not(:active) + .thumbnail .thumbnail-shield {
  1026.   -webkit-box-shadow: inset 0 1px 10px rgba(0, 0, 0, 0.2);
  1027. }
  1028.  
  1029. .thumbnail-wrapper {
  1030.   -webkit-box-flex: 1;
  1031.   -webkit-transition: background-color 150ms;
  1032.   border: 1px solid transparent;
  1033.   border-radius: 3px;
  1034.   display: block;
  1035.   position: relative;
  1036.   z-index: 5;
  1037. }
  1038.  
  1039. .filler .thumbnail-wrapper {
  1040.   visibility: visible;
  1041. }
  1042.  
  1043. /* 'finishing-drag' is the state we are in after dropping on the trash can.
  1044.  * Override opacity of the tile to 1, so that the new tile animation
  1045.  * occurs simultaneously with the trash animation. */
  1046. .tile.dragging.finishing-drag {
  1047.   opacity: 1;
  1048. }
  1049.  
  1050. /* Don't display the new tile until there's something to show.  */
  1051. .blacklisted {
  1052.   opacity: 0;
  1053. }
  1054. </style>
  1055. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1056.  * Use of this source code is governed by a BSD-style license that can be
  1057.  * found in the LICENSE file. */
  1058.  
  1059. /* TODO(estade): handle overflow better? I tried overflow-x: hidden and
  1060.    overflow-y: visible (for the new dot animation), but this makes a scroll
  1061.    bar appear */
  1062. #dot-list {
  1063.   /* Expand to take up all available horizontal space.  */
  1064.   -webkit-box-flex: 1;
  1065.   /* Center child dots. */
  1066.   -webkit-box-pack: center;
  1067.   display: -webkit-box;
  1068.   height: 100%;
  1069.   list-style-type: none;
  1070.   margin: 0;
  1071.   padding: 0;
  1072. }
  1073.  
  1074. html.starting-up #dot-list {
  1075.   display: none;
  1076. }
  1077.  
  1078. .dot {
  1079.   -webkit-box-flex: 1;
  1080.   -webkit-margin-end: 10px;
  1081.   -webkit-padding-start: 2px;
  1082.   -webkit-transition: max-width 250ms, -webkit-margin-end 250ms;
  1083.   box-sizing: border-box;
  1084.   cursor: pointer;
  1085.   /* max-width: Set in new_tab.js. See measureNavDots() */
  1086.   outline: none;
  1087.   text-align: left;
  1088. }
  1089.  
  1090. .dot:last-child {
  1091.   -webkit-margin-end: 0;
  1092. }
  1093.  
  1094. .dot.small {
  1095.   -webkit-margin-end: 0;
  1096.   max-width: 0;
  1097. }
  1098.  
  1099. .dot .selection-bar {
  1100.   -webkit-transition: border-color 200ms;
  1101.   border-bottom: 5px solid;
  1102.   border-color: rgba(0, 0, 0, 0.1);
  1103.   height: 10px;
  1104. }
  1105.  
  1106. .dot input {
  1107.   -webkit-appearance: caret;
  1108.   -webkit-margin-start: 2px;
  1109.   -webkit-transition: color 200ms;
  1110.   background-color: transparent;
  1111.   cursor: inherit;
  1112.   font: inherit;
  1113.   height: auto;
  1114.   margin-top: 2px;
  1115.   padding: 1px 0;
  1116.   width: 90%;
  1117. }
  1118.  
  1119. .dot input:focus {
  1120.   cursor: auto;
  1121. }
  1122.  
  1123. /* Everything below here should be themed but we don't have appropriate colors
  1124.  * yet.
  1125.  */
  1126. .dot input {
  1127.   color: #b2b2b2;
  1128. }
  1129.  
  1130. .dot:focus input,
  1131. .dot:hover input,
  1132. .dot.selected input {
  1133.   color: #7f7f7f;
  1134. }
  1135.  
  1136. .dot:focus .selection-bar,
  1137. .dot:hover .selection-bar,
  1138. .dot.drag-target .selection-bar {
  1139.   border-color: #b2b2b2;
  1140. }
  1141.  
  1142. .dot.selected .selection-bar {
  1143.   border-color: #7f7f7f;
  1144. }
  1145. </style>
  1146. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1147.  * Use of this source code is governed by a BSD-style license that can be
  1148.  * found in the LICENSE file. */
  1149.  
  1150. html {
  1151.   /* It's necessary to put this here instead of in body in order to get the
  1152.      background-size of 100% to work properly */
  1153.   height: 100%;
  1154.   overflow: hidden;
  1155. }
  1156.  
  1157. body {
  1158.   /* Don't highlight links when they're tapped. Safari has bugs here that
  1159.      show up as flicker when dragging in some situations */
  1160.   -webkit-tap-highlight-color: transparent;
  1161.   /* Don't allow selecting text - can occur when dragging */
  1162.   -webkit-user-select: none;
  1163.   background-size: auto 100%;
  1164.   margin: 0;
  1165. }
  1166.  
  1167. /* [hidden] does display:none, but its priority is too low in some cases. */
  1168. [hidden] {
  1169.   display: none !important;
  1170. }
  1171.  
  1172. #notification-container {
  1173.   -webkit-transition: opacity 200ms;
  1174.   bottom: 31px;
  1175.   display: block;
  1176.   float: left;
  1177.   position: relative;
  1178.   text-align: start;
  1179.   z-index: 15;
  1180. }
  1181.  
  1182. html[dir='rtl'] #notification-container {
  1183.   float: right;
  1184. }
  1185.  
  1186. #notification-container.card-changed {
  1187.   -webkit-transition: none;
  1188.   opacity: 0;
  1189. }
  1190.  
  1191. #notification-container.inactive {
  1192.   -webkit-transition: opacity 200ms;
  1193.   opacity: 0;
  1194. }
  1195.  
  1196. #notification {
  1197.   display: inline-block;
  1198.   font-weight: bold;
  1199.   white-space: nowrap;
  1200. }
  1201.  
  1202. #notification > * {
  1203.   display: inline-block;
  1204.   white-space: normal;
  1205. }
  1206.  
  1207. #notification > div > div,
  1208. #notification > div {
  1209.   display: inline-block;
  1210. }
  1211.  
  1212. /* NOTE: This is in the probable case that we start stuffing 16x16 data URI'd
  1213.  * icons in the promo notification responses. */
  1214. #notification > span > img {
  1215.   margin-bottom: -3px;
  1216. }
  1217.  
  1218. #notification .close-button {
  1219.   -webkit-margin-start: 8px;  /* Matching value in TilePage#repositionTile_. */
  1220.   vertical-align: top;
  1221. }
  1222.  
  1223. .close-button {
  1224.   background: no-repeat;
  1225.   background-color: transparent;
  1226.   /* TODO(estade): this should animate between states. */
  1227.   background-image: -webkit-image-set(
  1228.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAiElEQVR42r2RsQrDMAxEBRdl8SDcX8lQPGg1GBI6lvz/h7QyRRXV0qUULwfvwZ1tenw5PxToRPWMC52eA9+WDnlh3HFQ/xBQl86NFYJqeGflkiogrOvVlIFhqURFVho3x1moGAa3deMs+LS30CAhBN5nNxeT5hbJ1zwmji2k+aF6NENIPf/hs54f0sZFUVAMigAAAABJRU5ErkJggg==') 1x);
  1229.   border: 0;
  1230.   cursor: default;
  1231.   display: inline-block;
  1232.   height: 16px;
  1233.   padding: 0;
  1234.   width: 16px;
  1235. }
  1236.  
  1237. .close-button:hover,
  1238. .close-button:focus {
  1239.   background-image: -webkit-image-set(
  1240.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAqklEQVR4XqWRMQ6DMAxF/1Fyilyj2SmIBUG5QcTCyJA5Z8jGhlBPgRi4TmoDraVmKFJlWYrlp/g5QfwRlwEVNWVa4WzfH9jK6kCkEkBjwxOhLghheMWMELUAqqwQ4OCbnE4LJnhr5IYdqQt4DJQjhe9u4vBBmnxHHNzRFkDGjHDo0VuTAqy2vAG4NkvXXDHxbGsIGlj3e835VFNtdugma/Jk0eXq0lP//5svi4PtO01oFfYAAAAASUVORK5CYII=')
  1241.           1x,
  1242.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAB4UlEQVR42u2VsWoCQRBAh+MUFP0C1V9QD4NEOxs9xBQHQVCwSJFWVBAtBNXCxk6wTkBJYUTwEwQLC61E8QP0NzZzt5g5726DkC7EYWHZ8T3WndkV2C/jLwn4hwVYBIdLn9vkLp79QcBCTDMiy3w2gQ9XeTYkEHA8vqj2rworXu3HF1YFfSWgp5QFnKVLvYvzDEKEZ5hW70oXOCtcEbQLIkx7+IQtfMBSOjU6XEF4oyOdYInZbXyOuajjDlpNeQgleIUJKUz4BDMledhqOu/AzVSmzZ49CUjCC0yvim98iqtJT2L2jKsqczsdok9XrHNexaww415lnTNwn6CM/KxJIR8bnUZHPhLO6yMoIyk2pNjLewFuE5AiY1KMMQx8Q7hQYFek4AkjxXFe1rsF84I/BTFQMGL+1Lxwl4DwdtM1gjwKohgxyLtG7SYpxALqugOMcfOKN+bFXeBsLB1uulNcRqq7/tt36k41zoL6QlxGjtd6lrahiqCi1iOFYyvXuxY8yzK33VnvUivbLlOlj/jktm0s3YnXrNIXXufHNxuOGasi8S68zkwrlnV8ZcJJsTIUxbLgQcFZWE8N0gau2p40VVcM0gYeFpSRK6445UhBuKiRgiyKw+34rLt59nb1/7+RwReVkaFtqvNBuwAAAABJRU5ErkJggg==')
  1243.           2x);
  1244. }
  1245.  
  1246. .close-button:active {
  1247.   background-image: -webkit-image-set(
  1248.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARElEQVQoz2P4z4AfMlBLAYMdwxkghgEwD1XBGTC0g0sDIaYJECVwFqoChBK4WegKkJWArSJZAQErCDqSKG/iCyhaRhYA9LDIbULDzlIAAAAASUVORK5CYII=')
  1249.           1x,
  1250.       url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAA/ElEQVR4Xu3UsWrCUBiG4efGlIBoIMFbcnYolYJ3pg4iKGrGYFTRwaUFhYAekiDt0EG++X2W83N8/3J/DbwBMJJSsdQItcDY1VlCOImzq3Ed8OmicHASB3ns5KBw8VUNpDJrW7uAiJ3sbK1l0mqArpmFTUlQ5jYWZrrUAUSmT0SZm4qoA56JvVhs/5g3A7RLolA85A1ASOTye65NMxASK6syfxGITMzvMxG9CvRkliWwlOm9AsSOcitzU1NzK7mjuBkQvHtLK7iLBiB5PhttJSGpB8I8vM6kDuiHeUjoVwMfYR4SRtUAw1veIZzOjRhSBzCoyKFjgH/3K7+BHzg+Cgw0eSW3AAAAAElFTkSuQmCC')
  1251.           2x);
  1252. }
  1253.  
  1254. .link-button {
  1255.   -webkit-margin-start: 0.5em;
  1256. }
  1257.  
  1258. #card-slider-frame {
  1259.   /* Must match #footer height. */
  1260.   bottom: 50px;
  1261.   overflow: hidden;
  1262.   /* We want this to fill the window except for the region used
  1263.    * by footer. */
  1264.   position: fixed;
  1265.   top: 0;
  1266.   width: 100%;
  1267. }
  1268.  
  1269. body.bare-minimum #card-slider-frame {
  1270.   bottom: 0;
  1271. }
  1272.  
  1273. #page-list {
  1274.   /* fill the apps-frame */
  1275.   display: -webkit-box;
  1276.   height: 100%;
  1277. }
  1278.  
  1279. #attribution {
  1280.   bottom: 0;
  1281.   left: auto;
  1282.   margin-left: 8px;
  1283.   /* Leave room for the scrollbar. */
  1284.   margin-right: 13px;
  1285.   position: absolute;
  1286.   right: 0;
  1287.   text-align: left;
  1288.   z-index: -5;
  1289. }
  1290.  
  1291. /* For themes that right-align their images, we flip the attribution to the
  1292.  * left to avoid conflicts. We also do this for bare-minimum mode since there
  1293.  * can be conflicts with the recently closed menu. */
  1294. html[themegravity='right'] #attribution,
  1295. body.bare-minimum #attribution,
  1296. html[dir='rtl'] #attribution {
  1297.   left: 0;
  1298.   right: auto;
  1299.   text-align: right;
  1300. }
  1301.  
  1302. #attribution > span {
  1303.   display: block;
  1304. }
  1305.  
  1306. #footer {
  1307.   background-image: -webkit-linear-gradient(
  1308.       rgba(242, 242, 242, 0.9), rgba(222, 222, 222, 0.9));
  1309.   bottom: 0;
  1310.   color: #7F7F7F;
  1311.   font-size: 0.9em;
  1312.   font-weight: bold;
  1313.   overflow: hidden;
  1314.   position: fixed;
  1315.   width: 100%;
  1316.   z-index: 5;
  1317. }
  1318.  
  1319. /* TODO(estade): remove this border hack and replace with a webkit-gradient
  1320.  * border-image on #footer once WebKit supports border-image-slice.
  1321.  * See https://bugs.webkit.org/show_bug.cgi?id=20127 */
  1322. #footer-border {
  1323.   height: 1px;
  1324. }
  1325.  
  1326. #footer-content {
  1327.   -webkit-box-align: center;
  1328.   display: -webkit-box;
  1329.   height: 49px;
  1330. }
  1331.  
  1332. #footer-content > * {
  1333.   margin: 0 9px;
  1334. }
  1335.  
  1336. #logo-img {
  1337.   display: inline-block;
  1338.   margin-top: 4px;
  1339.   position: relative;
  1340. }
  1341.  
  1342. #promo-bubble-anchor {
  1343.   height: 1px;
  1344.   left: 0;
  1345.   position: absolute;
  1346.   top: 4px;
  1347.   visibility: hidden;
  1348.   width: 32px;
  1349. }
  1350.  
  1351. body.bare-minimum #footer {
  1352.   background: transparent;
  1353.   bottom: auto;
  1354.   font-weight: normal;
  1355.   position: absolute;
  1356.   right: 0;
  1357. }
  1358.  
  1359. html[dir='rtl'] body.bare-minimum #footer {
  1360.   left: 0;
  1361.   right: auto;
  1362. }
  1363.  
  1364. body.bare-minimum #footer-border,
  1365. body.bare-minimum #logo-img,
  1366. body.bare-minimum #dot-list {
  1367.   visibility: hidden;
  1368. }
  1369.  
  1370. .starting-up * {
  1371.   -webkit-transition: none !important;
  1372. }
  1373.  
  1374. /* Login Status. **************************************************************/
  1375.  
  1376. #login-container {
  1377.   -webkit-box-shadow: none;
  1378.   background: transparent none;
  1379.   border: none;
  1380.   color: inherit;
  1381.   cursor: pointer;
  1382.   font: inherit;
  1383.   /* Leave room for the scrollbar. */
  1384.   margin-left: 13px;
  1385.   margin-right: 13px;
  1386.   margin-top: 5px;
  1387.   padding: 0;
  1388.   position: fixed;
  1389.   right: 0;
  1390.   text-align: right;
  1391.   top: 0;
  1392.   z-index: 10;
  1393. }
  1394.  
  1395. html[dir='rtl'] #login-container {
  1396.   left: 0;
  1397.   right: auto;
  1398. }
  1399.  
  1400. .login-status-icon {
  1401.   -webkit-padding-end: 37px;
  1402.   background-position: right center;
  1403.   background-repeat: no-repeat;
  1404.   min-height: 27px;
  1405. }
  1406.  
  1407. html[dir='rtl'] .login-status-icon {
  1408.   background-position-x: left;
  1409. }
  1410.  
  1411. .profile-name:hover,
  1412. .link-span {
  1413.   text-decoration: underline;
  1414. }
  1415.  
  1416. #login-status-bubble-contents {
  1417.   font-size: 1.1em;
  1418. }
  1419.  
  1420. #login-status-message-container {
  1421.   margin-bottom: 13px;
  1422. }
  1423.  
  1424. #login-status-learn-more {
  1425.   display: inline-block;
  1426. }
  1427.  
  1428. .login-status-row {
  1429.   -webkit-box-align: center;
  1430.   -webkit-box-orient: horizontal;
  1431.   -webkit-box-pack: end;
  1432.   display: -webkit-box;
  1433. }
  1434.  
  1435. #login-status-advanced-container {
  1436.   -webkit-box-flex: 1;
  1437. }
  1438.  
  1439. #login-status-dismiss {
  1440.   min-width: 6em;
  1441. }
  1442.  
  1443. /* Trash. *********************************************************************/
  1444.  
  1445. #trash {
  1446.   -webkit-padding-start: 10px;
  1447.   -webkit-transition: top 200ms, opacity 0;
  1448.   -webkit-transition-delay: 0, 200ms;
  1449.   color: #222;
  1450.   height: 100%;
  1451.   opacity: 0;
  1452.   position: absolute;
  1453.   right: 0;
  1454.   top: 50px;
  1455.   width: auto;
  1456. }
  1457.  
  1458. html[dir='rtl'] #trash {
  1459.   left: 0;
  1460.   right: auto;
  1461. }
  1462.  
  1463. #footer.showing-trash-mode #trash {
  1464.   -webkit-transition-delay: 0, 0;
  1465.   -webkit-transition-duration: 0, 200ms;
  1466.   opacity: 0.75;
  1467.   top: 0;
  1468. }
  1469.  
  1470. #footer.showing-trash-mode #trash.drag-target {
  1471.   opacity: 1;
  1472. }
  1473.  
  1474. #trash > .trash-text {
  1475.   -webkit-padding-end: 7px;
  1476.   -webkit-padding-start: 30px;
  1477.   border: 1px dashed #7f7f7f;
  1478.   border-radius: 4px;
  1479.   display: inline-block;
  1480.   padding-bottom: 9px;
  1481.   padding-top: 10px;
  1482.   position: relative;
  1483.   top: 7px;
  1484. }
  1485.  
  1486. #trash > .lid,
  1487. #trash > .can {
  1488.   left: 18px;
  1489.   top: 18px;
  1490. }
  1491.  
  1492. html[dir='rtl'] #trash > .lid,
  1493. html[dir='rtl'] #trash > .can {
  1494.   right: 18px;
  1495. }
  1496.  
  1497. #footer.showing-trash-mode #trash.drag-target .lid {
  1498.   -webkit-transform: rotate(-45deg);
  1499. }
  1500.  
  1501. html[dir='rtl'] #footer.showing-trash-mode #trash.drag-target .lid {
  1502.   -webkit-transform: rotate(45deg);
  1503. }
  1504.  
  1505. #fontMeasuringDiv {
  1506.   /* The font attributes match the nav inputs. */
  1507.   font-size: 0.9em;
  1508.   font-weight: bold;
  1509.   pointer-events: none;
  1510.   position: absolute;
  1511.   visibility: hidden;
  1512. }
  1513.  
  1514. /* Page switcher buttons. *****************************************************/
  1515.  
  1516. .page-switcher {
  1517.   -webkit-transition: width 150ms, right 150ms, background-color 150ms;
  1518.   background-color: transparent;
  1519.   border: none;
  1520.   bottom: 0;
  1521.   font-size: 40px;
  1522.   margin: 0;
  1523.   max-width: 150px;
  1524.   min-width: 90px;
  1525.   outline: none;
  1526.   padding: 0;
  1527.   position: absolute;
  1528.   top: 0;
  1529.   z-index: 5;
  1530. }
  1531.  
  1532. #chrome-web-store-link {
  1533.   -webkit-padding-end: 12px;
  1534.   /* Match transition delay of recently closed button. */
  1535.   -webkit-transition-delay: 100ms;
  1536.   color: inherit;
  1537.   cursor: pointer;
  1538.   display: inline-block;
  1539.   margin: 0;
  1540.   text-decoration: none;
  1541.   white-space: nowrap;
  1542. }
  1543.  
  1544. #chrome-web-store-title {
  1545.   -webkit-padding-end: 36px;
  1546.   -webkit-padding-start: 15px;
  1547.   background: url('chrome://theme/IDR_WEBSTORE_ICON_24') right 50% no-repeat;
  1548.   display: inline-block;
  1549.   line-height: 49px;
  1550. }
  1551.  
  1552. #chrome-web-store-link:hover {
  1553.   color: #666;
  1554. }
  1555.  
  1556. html[dir='rtl'] #chrome-web-store-title {
  1557.   background-position-x: left;
  1558. }
  1559.  
  1560. #vertical-separator {
  1561.   background-color: rgb(178, 178, 178);
  1562.   display: none;
  1563.   height: 20px;
  1564.   margin: 0;
  1565.   vertical-align: middle;
  1566.   width: 1px;
  1567. }
  1568.  
  1569. /* Show the separator only if one of the menus is visible. */
  1570. .footer-menu-button:not([hidden]) ~ #vertical-separator {
  1571.   display: block;
  1572. }
  1573.  
  1574. /* In trash mode, hide the menus and web store link. */
  1575. #footer.showing-trash-mode #chrome-web-store-link,
  1576. #footer.showing-trash-mode .menu-container {
  1577.   -webkit-transition-delay: 0;
  1578.   opacity: 0;
  1579.   visibility: hidden;
  1580. }
  1581.  
  1582. #footer .menu-container {
  1583.   -webkit-box-align: center;
  1584.   /* Put menus in a box so the order can easily be swapped. */
  1585.   display: -webkit-box;
  1586.   height: 100%;
  1587.   margin: 0;
  1588. }
  1589.  
  1590. .other-sessions-promo-message {
  1591.   display: none;
  1592.   padding: 0;
  1593. }
  1594.  
  1595. .other-sessions-promo-message:only-child {
  1596.   display: block;
  1597. }
  1598.  
  1599. .other-sessions-promo-message p {
  1600.   margin: 0;
  1601. }
  1602. </style>
  1603. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1604.  * Use of this source code is governed by a BSD-style license that can be
  1605.  * found in the LICENSE file. */
  1606.  
  1607. .footer-menu-button {
  1608.   -webkit-appearance: none;
  1609.   -webkit-padding-end: 15px;
  1610.   -webkit-padding-start: 9px;
  1611.   -webkit-transition: opacity 200ms;
  1612.   -webkit-transition-delay: 100ms;
  1613.   background: none;
  1614.   border: 0;
  1615.   color: inherit;
  1616.   cursor: pointer;
  1617.   display: block;
  1618.   font: inherit;
  1619.   height: 100%;
  1620.   margin: 0;
  1621.   /* The padding increases the clickable area. */
  1622.   padding-bottom: 0;
  1623.   padding-top: 0;
  1624.   white-space: nowrap;
  1625. }
  1626.  
  1627. .footer-menu-button:hover:not([menu-shown]) {
  1628.   color: #666;
  1629. }
  1630.  
  1631. .footer-menu-button:hover:not([menu-shown]) .disclosure-triangle {
  1632.   background-color: #666;
  1633. }
  1634.  
  1635. .footer-menu-button[menu-shown] {
  1636.   color: #555;
  1637. }
  1638.  
  1639. .footer-menu-button[menu-shown] .disclosure-triangle {
  1640.   background-color: #555;
  1641. }
  1642.  
  1643. .footer-menu {
  1644.   -webkit-margin-end: 10px;
  1645.   max-height: 400px;
  1646.   overflow: auto;
  1647.   padding: 6px 8px;
  1648.   /* Needs to be above #footer. */
  1649.   z-index: 10;
  1650. }
  1651.  
  1652. .footer-menu,
  1653. .footer-menu-context-menu {
  1654.   min-width: 150px;
  1655. }
  1656.  
  1657. /* TODO(dubroy): Unify this with tile page scrollbar. */
  1658. .footer-menu::-webkit-scrollbar-button {
  1659.   display: none;
  1660. }
  1661.  
  1662. .footer-menu::-webkit-scrollbar {
  1663.   width: 8px;
  1664. }
  1665.  
  1666. .footer-menu::-webkit-scrollbar-thumb {
  1667.   background-color: #D9D9D9;
  1668.   border: 2px solid white;
  1669. }
  1670.  
  1671. .footer-menu-item {
  1672.   -webkit-margin-end: 8px;
  1673.   -webkit-margin-start: 0;
  1674.   -webkit-padding-end: 0;
  1675.   -webkit-padding-start: 22px;
  1676.   background: no-repeat 0 50%;
  1677.   background-color: transparent !important;
  1678.   background-size: 16px 16px;
  1679.   box-sizing: border-box;
  1680.   display: block;
  1681.   line-height: 1.5em;
  1682.   margin-bottom: 0.5em;
  1683.   margin-top: 0.5em;
  1684.   max-width: 450px;
  1685.   outline: none;
  1686.   overflow: hidden;
  1687.   text-overflow: ellipsis;
  1688.   white-space: nowrap;
  1689. }
  1690.  
  1691. .footer-menu-item:not(:hover),
  1692. .footer-menu-item:not(:focus) {
  1693.   text-decoration: none;
  1694. }
  1695.  
  1696. .footer-menu-item:hover,
  1697. .footer-menu-item:focus {
  1698.   text-decoration: underline;
  1699. }
  1700.  
  1701. .footer-menu-item:first-of-type {
  1702.   margin-top: 0.25em;
  1703. }
  1704.  
  1705. .footer-menu-item:last-of-type {
  1706.   margin-bottom: 0.25em;
  1707. }
  1708.  
  1709. .footer-menu section {
  1710.   padding: 0;
  1711. }
  1712.  
  1713. .footer-menu section h3 {
  1714.   color: black;
  1715.   font-size: 1em;
  1716.   font-weight: bold;
  1717.   margin-bottom: 0.5em;
  1718. }
  1719.  
  1720. .footer-menu section:first-of-type h3 {
  1721.   margin-top: 0.25em;
  1722. }
  1723.  
  1724. /* Used to add additional details to a section header */
  1725. .footer-menu section h3 .details {
  1726.   color: rgb(151, 156, 160);
  1727.   font-style: italic;
  1728.   font-weight: normal;
  1729. }
  1730.  
  1731. .footer-menu section h3 .details::before {
  1732.   content: '\2013';  /* En-dash character. */
  1733.   margin: 0 0.5em;
  1734. }
  1735.  
  1736. html[dir='rtl'] .footer-menu-item {
  1737.   background: no-repeat 100% 50%;
  1738. }
  1739.  
  1740. .recent-window {
  1741.   background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOElEQVQ4y2M4ceLEYSD+TyY+zECBZjCGG4AOQGLPnj1DwTgNwAWIMQSvAYTAqAGjBgwuAyjJzkcAtmTCw0j9ScUAAAAASUVORK5CYII=');
  1742. }
  1743.  
  1744. /* TODO(estade): find a better color for active. */
  1745. .footer-menu-item:active,
  1746. .footer-menu-item:visited,
  1747. .footer-menu-item:link {
  1748.   color: hsl(213, 90%, 24%) !important;
  1749. }
  1750.  
  1751. .disclosure-triangle {
  1752.   -webkit-margin-start: 2px;
  1753.   -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAJUlEQVQYV2NgoDuYSQSGK/RB0+yDrACbQqwKkBUW41OArJB0AAACXws0ERupuAAAAABJRU5ErkJggg==');
  1754.   background-color: #7F7F7F;
  1755.   display: inline-block;
  1756.   height: 9px;
  1757.   width: 9px;
  1758. }
  1759.  
  1760. .footer-menu-context-menu {
  1761.   /* Needs to be above .footer-menu. */
  1762.   z-index: 11;
  1763. }
  1764.  
  1765. .footer-menu hr {
  1766.   background-color: rgb(217, 217, 217);
  1767.   border: 0;
  1768.   height: 1px;
  1769. }
  1770. </style>
  1771. <style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1772.  * Use of this source code is governed by a BSD-style license that can be
  1773.  * found in the LICENSE file. */
  1774.  
  1775. .tile-page {
  1776.   -webkit-box-orient: vertical;
  1777.   display: -webkit-box;
  1778.   height: 100%;
  1779.   position: relative;
  1780.   width: 100%;
  1781. }
  1782.  
  1783. .tile-page-scrollbar {
  1784.   -webkit-box-sizing: border-box;
  1785.   margin: 0 4px;
  1786.   pointer-events: none;
  1787.   position: absolute;
  1788.   right: 0;
  1789.   width: 5px;
  1790.   z-index: 5;
  1791. }
  1792.  
  1793. .tile-page-content {
  1794.   -webkit-box-flex: 1;
  1795.   /* Don't apply clip mask to padding. */
  1796.   -webkit-mask-clip: content-box;
  1797.   /* TODO(estade): this mask is disabled for technical reasons. It negatively
  1798.    * impacts performance of page switching, also it causes problems with Mac
  1799.    * text: http://crbug.com/86955
  1800.   -webkit-mask-image: -webkit-linear-gradient(bottom, transparent, black 30px);
  1801.   */
  1802.   /* The following four properties are necessary so that the mask won't clip
  1803.    * the scrollbar. */
  1804.   box-sizing: border-box;
  1805.   overflow-y: scroll;
  1806.   /* Scrollbar width(13px) + balance right padding.  */
  1807.   padding-left: 93px;
  1808.   padding-right: 80px;
  1809.   /* This value is mirrored in TilePage.updateTopMargin_ */
  1810.   padding-top: 60px;
  1811.   position: relative;
  1812.   text-align: center;
  1813.   width: 100%;
  1814. }
  1815.  
  1816. .top-margin {
  1817.   /* The only reason height is set to 1px, rather than left at 0, is that
  1818.    * otherwise webkit collapses the top and bottom margins. */
  1819.   height: 1px;
  1820. }
  1821.  
  1822. .tile-grid {
  1823.   position: relative;
  1824.   width: 100%;
  1825. }
  1826.  
  1827. .tile {
  1828.   -webkit-print-color-adjust: exact;
  1829.   /* Don't offer the context menu on long-press. */
  1830.   -webkit-touch-callout: none;
  1831.   -webkit-user-drag: element;
  1832.   display: inline-block;
  1833.   position: absolute;
  1834. }
  1835.  
  1836. /* NOTE: Dopplegangers nest themselves inside of other tiles, so don't
  1837.  * accidentally double apply font-size to them. */
  1838. .tile:not(.doppleganger) {
  1839.   font-size: 1.2em;
  1840. }
  1841.  
  1842. /* Not real but not a doppleganger: show nothing. This state exists for a
  1843.  * webstore tile that's on the same page as a [+]. */
  1844. .tile:not(.real):not(.doppleganger) {
  1845.   display: none;
  1846. }
  1847.  
  1848. /* I don't know why this is necessary. -webkit-user-drag: element on .tile
  1849.  * should be enough. If we don't do this, we get 2 drag representations for
  1850.  * the image. */
  1851. .tile img {
  1852.   -webkit-user-drag: none;
  1853. }
  1854.  
  1855. .doppleganger {
  1856.   left: 0 !important;
  1857.   right: 0 !important;
  1858.   top: 0 !important;
  1859. }
  1860.  
  1861. .tile.dragging {
  1862.   opacity: 0;
  1863. }
  1864.  
  1865. .tile.drag-representation {
  1866.   -webkit-transition: opacity 200ms;
  1867.   pointer-events: none;
  1868.   position: fixed;
  1869.   z-index: 3;
  1870. }
  1871.  
  1872. .tile.drag-representation.placing > * {
  1873.   -webkit-transition: -webkit-transform 200ms;
  1874. }
  1875.  
  1876. /* When a drag finishes while we're not showing the page where the tile
  1877.  * belongs, the tile shrinks to a dot. */
  1878. .tile.drag-representation.dropped-on-other-page > * {
  1879.    -webkit-transform: scale(0) rotate(0);
  1880. }
  1881.  
  1882. .tile.drag-representation.deleting > * {
  1883.   -webkit-transform: scale(0) rotate(360deg);
  1884.   -webkit-transition: -webkit-transform 600ms;
  1885. }
  1886.  
  1887. .animating-tile-page .tile,
  1888. .tile.drag-representation.placing {
  1889.   -webkit-transition: left 200ms, right 200ms, top 200ms;
  1890. }
  1891.  
  1892. .hovering-on-trash {
  1893.   opacity: 0.6;
  1894. }
  1895.  
  1896. .animating-tile-page .top-margin {
  1897.   -webkit-transition: margin-bottom 200ms;
  1898. }
  1899.  
  1900. .animating-tile-page #notification-container {
  1901.   -webkit-transition: margin 200ms, opacity 200ms;
  1902. }
  1903.  
  1904. @-webkit-keyframes bounce {
  1905.   0% {
  1906.     -webkit-transform: scale(0, 0);
  1907.   }
  1908.  
  1909.   60% {
  1910.     -webkit-transform: scale(1.2, 1.2);
  1911.   }
  1912.  
  1913.   100% {
  1914.     -webkit-transform: scale(1, 1);
  1915.   }
  1916. }
  1917.  
  1918. .tile > .new-tile-contents {
  1919.   -webkit-animation: bounce 500ms ease-in-out;
  1920. }
  1921.  
  1922. @-webkit-keyframes blipout {
  1923.   0% {
  1924.     -webkit-transform: scale(1, 1);
  1925.   }
  1926.  
  1927.   60% {
  1928.     -webkit-animation-timing-function: ease-in;
  1929.     -webkit-transform: scale(1.3, 0.02);
  1930.     opacity: 1;
  1931.   }
  1932.  
  1933.   90% {
  1934.     -webkit-animation-timing-function: default;
  1935.     -webkit-transform: scale(0.3, 0.02);
  1936.     opacity: 0.7;
  1937.   }
  1938.  
  1939.   100% {
  1940.     -webkit-animation-timing-function: linear;
  1941.     -webkit-transform: scale(0.3, 0.02);
  1942.     opacity: 0;
  1943.   }
  1944. }
  1945.  
  1946. .tile > .removing-tile-contents {
  1947.   -webkit-animation: blipout 300ms;
  1948.   -webkit-animation-fill-mode: forwards;
  1949.   pointer-events: none;
  1950. }
  1951.  
  1952. .tile-page:not(.selected-card) * {
  1953.   -webkit-transition: none !important;
  1954. }
  1955.  
  1956. /** Scrollbars ****************************************************************/
  1957.  
  1958. .tile-page-content::-webkit-scrollbar {
  1959.   width: 13px;
  1960. }
  1961.  
  1962. .tile-page-content::-webkit-scrollbar-button {
  1963.   display: none;
  1964. }
  1965. </style>
  1966. <link id="themecss" rel="stylesheet">
  1967.  
  1968. <script>// Copyright (c) 2011 The Chromium Authors. All rights reserved.
  1969. // Use of this source code is governed by a BSD-style license that can be
  1970. // found in the LICENSE file.
  1971.  
  1972. /** @fileoverview EventTracker is a simple class that manages the addition and
  1973.  *  removal of DOM event listeners. In particular, it keeps track of all
  1974.  *  listeners that have been added and makes it easy to remove some or all of
  1975.  *  them without requiring all the information again. This is particularly
  1976.  *  handy when the listener is a generated function such as a lambda or the
  1977.  *  result of calling Function.bind.
  1978.  */
  1979.  
  1980. // Use an anonymous function to enable strict mode just for this file (which
  1981. // will be concatenated with other files when embedded in Chrome)
  1982. var EventTracker = (function() {
  1983.   'use strict';
  1984.  
  1985.   /**
  1986.    *  Create an EventTracker to track a set of events.
  1987.    *  EventTracker instances are typically tied 1:1 with other objects or
  1988.    *  DOM elements whose listeners should be removed when the object is disposed
  1989.    *  or the corresponding elements are removed from the DOM.
  1990.    *  @constructor
  1991.    */
  1992.   function EventTracker() {
  1993.     /**
  1994.      *  @type {Array.<EventTracker.Entry>}
  1995.      *  @private
  1996.      */
  1997.     this.listeners_ = [];
  1998.   }
  1999.  
  2000.   /**
  2001.    * The type of the internal tracking entry.
  2002.    *  @typedef {{node: !Node,
  2003.    *            eventType: string,
  2004.    *            listener: Function,
  2005.    *            capture: boolean}}
  2006.    */
  2007.   EventTracker.Entry;
  2008.  
  2009.   EventTracker.prototype = {
  2010.     /**
  2011.      * Add an event listener - replacement for Node.addEventListener.
  2012.      * @param {!Node} node The DOM node to add a listener to.
  2013.      * @param {string} eventType The type of event to subscribe to.
  2014.      * @param {Function} listener The listener to add.
  2015.      * @param {boolean} capture Whether to invoke during the capture phase.
  2016.      */
  2017.     add: function(node, eventType, listener, capture) {
  2018.       var h = {
  2019.         node: node,
  2020.         eventType: eventType,
  2021.         listener: listener,
  2022.         capture: capture
  2023.       };
  2024.       this.listeners_.push(h);
  2025.       node.addEventListener(eventType, listener, capture);
  2026.     },
  2027.  
  2028.     /**
  2029.      * Remove any specified event listeners added with this EventTracker.
  2030.      * @param {!Node} node The DOM node to remove a listener from.
  2031.      * @param {?string} eventType The type of event to remove.
  2032.      */
  2033.     remove: function(node, eventType) {
  2034.       this.listeners_ = this.listeners_.filter(function(h) {
  2035.         if (h.node == node && (!eventType || (h.eventType == eventType))) {
  2036.           EventTracker.removeEventListener_(h);
  2037.           return false;
  2038.         }
  2039.         return true;
  2040.       });
  2041.     },
  2042.  
  2043.     /**
  2044.      * Remove all event listeners added with this EventTracker.
  2045.      */
  2046.     removeAll: function() {
  2047.       this.listeners_.forEach(EventTracker.removeEventListener_);
  2048.       this.listeners_ = [];
  2049.     }
  2050.   };
  2051.  
  2052.   /**
  2053.    * Remove a single event listener given it's tracker entry.  It's up to the
  2054.    * caller to ensure the entry is removed from listeners_.
  2055.    * @param {EventTracker.Entry} h The entry describing the listener to remove.
  2056.    * @private
  2057.    */
  2058.   EventTracker.removeEventListener_ = function(h) {
  2059.     h.node.removeEventListener(h.eventType, h.listener, h.capture);
  2060.   };
  2061.  
  2062.   return EventTracker;
  2063. })();
  2064.  
  2065. </script>
  2066. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2067. // Use of this source code is governed by a BSD-style license that can be
  2068. // found in the LICENSE file.
  2069.  
  2070. /**
  2071.  * @fileoverview This file defines a singleton which provides access to all data
  2072.  * that is available as soon as the page's resources are loaded (before DOM
  2073.  * content has finished loading). This data includes both localized strings and
  2074.  * any data that is important to have ready from a very early stage (e.g. things
  2075.  * that must be displayed right away).
  2076.  */
  2077.  
  2078. var loadTimeData;
  2079.  
  2080. (function() {
  2081.   'use strict';
  2082.  
  2083.   function LoadTimeData() {
  2084.   }
  2085.  
  2086.   LoadTimeData.prototype = {
  2087.     /**
  2088.      * Sets the backing object.
  2089.      * @param {Object} value The de-serialized page data.
  2090.      */
  2091.     set data(value) {
  2092.       expect(!this.data_, 'Re-setting data.');
  2093.       this.data_ = value;
  2094.     },
  2095.  
  2096.     /**
  2097.      * @return {boolean} True if |id| is a key in the dictionary.
  2098.      */
  2099.     valueExists: function(id) {
  2100.       return id in this.data_;
  2101.     },
  2102.  
  2103.     /**
  2104.      * Fetches a value, expecting that it exists.
  2105.      * @param {string} id The key that identifies the desired value.
  2106.      * @return {*} The corresponding value.
  2107.      */
  2108.     getValue: function(id) {
  2109.       expect(this.data_, 'No data. Did you remember to include strings.js?');
  2110.       var value = this.data_[id];
  2111.       expect(typeof value != 'undefined', 'Could not find value for ' + id);
  2112.       return value;
  2113.     },
  2114.  
  2115.     /**
  2116.      * As above, but also makes sure that the value is a string.
  2117.      * @param {string} id The key that identifies the desired string.
  2118.      * @return {string} The corresponding string value.
  2119.      */
  2120.     getString: function(id) {
  2121.       var value = this.getValue(id);
  2122.       expectIsType(id, value, 'string');
  2123.       return value;
  2124.     },
  2125.  
  2126.     /**
  2127.      * Returns a formatted localized string where $1 to $9 are replaced by the
  2128.      * second to the tenth argument.
  2129.      * @param {string} id The ID of the string we want.
  2130.      * @param {...string} The extra values to include in the formatted output.
  2131.      * @return {string} The formatted string.
  2132.      */
  2133.     getStringF: function(id) {
  2134.       var value = this.getString(id);
  2135.       if (!value)
  2136.         return;
  2137.  
  2138.       var varArgs = arguments;
  2139.       return value.replace(/\$[$1-9]/g, function(m) {
  2140.         return m == '$$' ? '$' : varArgs[m[1]];
  2141.       });
  2142.     },
  2143.  
  2144.     /**
  2145.      * As above, but also makes sure that the value is a boolean.
  2146.      * @param {string} id The key that identifies the desired boolean.
  2147.      * @return {boolean} The corresponding boolean value.
  2148.      */
  2149.     getBoolean: function(id) {
  2150.       var value = this.getValue(id);
  2151.       expectIsType(id, value, 'boolean');
  2152.       return value;
  2153.     },
  2154.  
  2155.     /**
  2156.      * As above, but also makes sure that the value is an integer.
  2157.      * @param {string} id The key that identifies the desired number.
  2158.      * @return {number} The corresponding number value.
  2159.      */
  2160.     getInteger: function(id) {
  2161.       var value = this.getValue(id);
  2162.       expectIsType(id, value, 'number');
  2163.       expect(value == Math.floor(value), 'Number isn\'t integer: ' + value);
  2164.       return value;
  2165.     },
  2166.  
  2167.     /**
  2168.      * Override values in loadTimeData with the values found in |replacements|.
  2169.      * @param {Object} replacements The dictionary object of keys to replace.
  2170.      */
  2171.     overrideValues: function(replacements) {
  2172.       expect(typeof replacements == 'object',
  2173.              'Replacements must be a dictionary object.');
  2174.       for (var key in replacements) {
  2175.         this.data_[key] = replacements[key];
  2176.       }
  2177.     }
  2178.   };
  2179.  
  2180.   /**
  2181.    * Checks condition, displays error message if expectation fails.
  2182.    * @param {*} condition The condition to check for truthiness.
  2183.    * @param {string} message The message to display if the check fails.
  2184.    */
  2185.   function expect(condition, message) {
  2186.     if (!condition)
  2187.       console.error(message);
  2188.   }
  2189.  
  2190.   /**
  2191.    * Checks that the given value has the given type.
  2192.    * @param {string} id The id of the value (only used for error message).
  2193.    * @param {*} value The value to check the type on.
  2194.    * @param {string} type The type we expect |value| to be.
  2195.    */
  2196.   function expectIsType(id, value, type) {
  2197.     expect(typeof value == type, '[' + value + '] (' + id +
  2198.                                  ') is not a ' + type);
  2199.   }
  2200.  
  2201.   expect(!loadTimeData, 'should only include this file once');
  2202.   loadTimeData = new LoadTimeData;
  2203. })();
  2204. </script>
  2205. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2206. // Use of this source code is governed by a BSD-style license that can be
  2207. // found in the LICENSE file.
  2208.  
  2209. /**
  2210.  * Parse a very small subset of HTML.  This ensures that insecure HTML /
  2211.  * javascript cannot be injected into the new tab page.
  2212.  * @param {string} s The string to parse.
  2213.  * @param {Array.<string>=} opt_extraTags Optional extra allowed tags.
  2214.  * @param {Object.<string, function(Node, string):boolean>=} opt_extraAttrs
  2215.  *     Optional extra allowed attributes (all tags are run through these).
  2216.  * @throws {Error} In case of non supported markup.
  2217.  * @return {DocumentFragment} A document fragment containing the DOM tree.
  2218.  */
  2219. var parseHtmlSubset = (function() {
  2220.   'use strict';
  2221.  
  2222.   var allowedAttributes = {
  2223.     'href': function(node, value) {
  2224.       // Only allow a[href] starting with chrome:// and https://
  2225.       return node.tagName == 'A' && (value.indexOf('chrome://') == 0 ||
  2226.           value.indexOf('https://') == 0);
  2227.     },
  2228.     'target': function(node, value) {
  2229.       // Allow a[target] but reset the value to "".
  2230.       if (node.tagName != 'A')
  2231.         return false;
  2232.       node.setAttribute('target', '');
  2233.       return true;
  2234.     }
  2235.   };
  2236.  
  2237.   /**
  2238.    * Whitelist of tag names allowed in parseHtmlSubset.
  2239.    * @type {!Array.<string>}
  2240.    * @const
  2241.    */
  2242.   var allowedTags = ['A', 'B', 'STRONG'];
  2243.  
  2244.   function merge() {
  2245.     var clone = {};
  2246.     for (var i = 0; i < arguments.length; ++i) {
  2247.       if (typeof arguments[i] == 'object') {
  2248.         for (var key in arguments[i]) {
  2249.           if (arguments[i].hasOwnProperty(key))
  2250.             clone[key] = arguments[i][key];
  2251.         }
  2252.       }
  2253.     }
  2254.     return clone;
  2255.   }
  2256.  
  2257.   function walk(n, f) {
  2258.     f(n);
  2259.     for (var i = 0; i < n.childNodes.length; i++) {
  2260.       walk(n.childNodes[i], f);
  2261.     }
  2262.   }
  2263.  
  2264.   function assertElement(tags, node) {
  2265.     if (tags.indexOf(node.tagName) == -1)
  2266.       throw Error(node.tagName + ' is not supported');
  2267.   }
  2268.  
  2269.   function assertAttribute(attrs, attrNode, node) {
  2270.     var n = attrNode.nodeName;
  2271.     var v = attrNode.nodeValue;
  2272.     if (!attrs.hasOwnProperty(n) || !attrs[n](node, v))
  2273.       throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported');
  2274.   }
  2275.  
  2276.   return function(s, opt_extraTags, opt_extraAttrs) {
  2277.     var extraTags =
  2278.         (opt_extraTags || []).map(function(str) { return str.toUpperCase(); });
  2279.     var tags = allowedTags.concat(extraTags);
  2280.     var attrs = merge(allowedAttributes, opt_extraAttrs || {});
  2281.  
  2282.     var r = document.createRange();
  2283.     r.selectNode(document.body);
  2284.     // This does not execute any scripts.
  2285.     var df = r.createContextualFragment(s);
  2286.     walk(df, function(node) {
  2287.       switch (node.nodeType) {
  2288.         case Node.ELEMENT_NODE:
  2289.           assertElement(tags, node);
  2290.           var nodeAttrs = node.attributes;
  2291.           for (var i = 0; i < nodeAttrs.length; ++i) {
  2292.             assertAttribute(attrs, nodeAttrs[i], node);
  2293.           }
  2294.           break;
  2295.  
  2296.         case Node.COMMENT_NODE:
  2297.         case Node.DOCUMENT_FRAGMENT_NODE:
  2298.         case Node.TEXT_NODE:
  2299.           break;
  2300.  
  2301.         default:
  2302.           throw Error('Node type ' + node.nodeType + ' is not supported');
  2303.       }
  2304.     });
  2305.     return df;
  2306.   };
  2307. })();
  2308. </script>
  2309. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2310. // Use of this source code is governed by a BSD-style license that can be
  2311. // found in the LICENSE file.
  2312.  
  2313. /**
  2314.  * The global object.
  2315.  * @type {!Object}
  2316.  * @const
  2317.  */
  2318. var global = this;
  2319.  
  2320. /**
  2321.  * Alias for document.getElementById.
  2322.  * @param {string} id The ID of the element to find.
  2323.  * @return {HTMLElement} The found element or null if not found.
  2324.  */
  2325. function $(id) {
  2326.   return document.getElementById(id);
  2327. }
  2328.  
  2329. /**
  2330.  * Calls chrome.send with a callback and restores the original afterwards.
  2331.  * @param {string} name The name of the message to send.
  2332.  * @param {!Array} params The parameters to send.
  2333.  * @param {string} callbackName The name of the function that the backend calls.
  2334.  * @param {!Function} callback The function to call.
  2335.  */
  2336. function chromeSend(name, params, callbackName, callback) {
  2337.   var old = global[callbackName];
  2338.   global[callbackName] = function() {
  2339.     // restore
  2340.     global[callbackName] = old;
  2341.  
  2342.     var args = Array.prototype.slice.call(arguments);
  2343.     return callback.apply(global, args);
  2344.   };
  2345.   chrome.send(name, params);
  2346. }
  2347.  
  2348. /**
  2349.  * Generates a CSS url string.
  2350.  * @param {string} s The URL to generate the CSS url for.
  2351.  * @return {string} The CSS url string.
  2352.  */
  2353. function url(s) {
  2354.   // http://www.w3.org/TR/css3-values/#uris
  2355.   // Parentheses, commas, whitespace characters, single quotes (') and double
  2356.   // quotes (") appearing in a URI must be escaped with a backslash
  2357.   var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
  2358.   // WebKit has a bug when it comes to URLs that end with \
  2359.   // https://bugs.webkit.org/show_bug.cgi?id=28885
  2360.   if (/\\\\$/.test(s2)) {
  2361.     // Add a space to work around the WebKit bug.
  2362.     s2 += ' ';
  2363.   }
  2364.   return 'url("' + s2 + '")';
  2365. }
  2366.  
  2367. /**
  2368.  * Parses query parameters from Location.
  2369.  * @param {string} location The URL to generate the CSS url for.
  2370.  * @return {object} Dictionary containing name value pairs for URL
  2371.  */
  2372. function parseQueryParams(location) {
  2373.   var params = {};
  2374.   var query = unescape(location.search.substring(1));
  2375.   var vars = query.split('&');
  2376.   for (var i = 0; i < vars.length; i++) {
  2377.     var pair = vars[i].split('=');
  2378.     params[pair[0]] = pair[1];
  2379.   }
  2380.   return params;
  2381. }
  2382.  
  2383. function findAncestorByClass(el, className) {
  2384.   return findAncestor(el, function(el) {
  2385.     if (el.classList)
  2386.       return el.classList.contains(className);
  2387.     return null;
  2388.   });
  2389. }
  2390.  
  2391. /**
  2392.  * Return the first ancestor for which the {@code predicate} returns true.
  2393.  * @param {Node} node The node to check.
  2394.  * @param {function(Node) : boolean} predicate The function that tests the
  2395.  *     nodes.
  2396.  * @return {Node} The found ancestor or null if not found.
  2397.  */
  2398. function findAncestor(node, predicate) {
  2399.   var last = false;
  2400.   while (node != null && !(last = predicate(node))) {
  2401.     node = node.parentNode;
  2402.   }
  2403.   return last ? node : null;
  2404. }
  2405.  
  2406. function swapDomNodes(a, b) {
  2407.   var afterA = a.nextSibling;
  2408.   if (afterA == b) {
  2409.     swapDomNodes(b, a);
  2410.     return;
  2411.   }
  2412.   var aParent = a.parentNode;
  2413.   b.parentNode.replaceChild(a, b);
  2414.   aParent.insertBefore(b, afterA);
  2415. }
  2416.  
  2417. /**
  2418.  * Disables text selection and dragging, with optional whitelist callbacks.
  2419.  * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
  2420.  *    is defined and returns true, the onselectionstart event will be
  2421.  *    surpressed.
  2422.  * @param {function(Event):boolean=} opt_allowDragStart Unless this function
  2423.  *    is defined and returns true, the ondragstart event will be surpressed.
  2424.  */
  2425. function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
  2426.   // Disable text selection.
  2427.   document.onselectstart = function(e) {
  2428.     if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
  2429.       e.preventDefault();
  2430.   };
  2431.  
  2432.   // Disable dragging.
  2433.   document.ondragstart = function(e) {
  2434.     if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
  2435.       e.preventDefault();
  2436.   };
  2437. }
  2438.  
  2439. /**
  2440.  * Call this to stop clicks on <a href="#"> links from scrolling to the top of
  2441.  * the page (and possibly showing a # in the link).
  2442.  */
  2443. function preventDefaultOnPoundLinkClicks() {
  2444.   document.addEventListener('click', function(e) {
  2445.     var anchor = findAncestor(e.target, function(el) {
  2446.       return el.tagName == 'A';
  2447.     });
  2448.     // Use getAttribute() to prevent URL normalization.
  2449.     if (anchor && anchor.getAttribute('href') == '#')
  2450.       e.preventDefault();
  2451.   });
  2452. }
  2453.  
  2454. /**
  2455.  * Check the directionality of the page.
  2456.  * @return {boolean} True if Chrome is running an RTL UI.
  2457.  */
  2458. function isRTL() {
  2459.   return document.documentElement.dir == 'rtl';
  2460. }
  2461.  
  2462. /**
  2463.  * Simple common assertion API
  2464.  * @param {*} condition The condition to test.  Note that this may be used to
  2465.  *     test whether a value is defined or not, and we don't want to force a
  2466.  *     cast to Boolean.
  2467.  * @param {string=} opt_message A message to use in any error.
  2468.  */
  2469. function assert(condition, opt_message) {
  2470.   'use strict';
  2471.   if (!condition) {
  2472.     var msg = 'Assertion failed';
  2473.     if (opt_message)
  2474.       msg = msg + ': ' + opt_message;
  2475.     throw new Error(msg);
  2476.   }
  2477. }
  2478.  
  2479. /**
  2480.  * Get an element that's known to exist by its ID. We use this instead of just
  2481.  * calling getElementById and not checking the result because this lets us
  2482.  * satisfy the JSCompiler type system.
  2483.  * @param {string} id The identifier name.
  2484.  * @return {!Element} the Element.
  2485.  */
  2486. function getRequiredElement(id) {
  2487.   var element = $(id);
  2488.   assert(element, 'Missing required element: ' + id);
  2489.   return element;
  2490. }
  2491.  
  2492. // Handle click on a link. If the link points to a chrome: or file: url, then
  2493. // call into the browser to do the navigation.
  2494. document.addEventListener('click', function(e) {
  2495.   // Allow preventDefault to work.
  2496.   if (!e.returnValue)
  2497.     return;
  2498.  
  2499.   var el = e.target;
  2500.   if (el.nodeType == Node.ELEMENT_NODE &&
  2501.       el.webkitMatchesSelector('A, A *')) {
  2502.     while (el.tagName != 'A') {
  2503.       el = el.parentElement;
  2504.     }
  2505.  
  2506.     if ((el.protocol == 'file:' || el.protocol == 'about:') &&
  2507.         (e.button == 0 || e.button == 1)) {
  2508.       chrome.send('navigateToUrl', [
  2509.         el.href,
  2510.         el.target,
  2511.         e.button,
  2512.         e.altKey,
  2513.         e.ctrlKey,
  2514.         e.metaKey,
  2515.         e.shiftKey
  2516.       ]);
  2517.       e.preventDefault();
  2518.     }
  2519.   }
  2520. });
  2521.  
  2522. /**
  2523.  * Creates a new URL which is the old URL with a GET param of key=value.
  2524.  * @param {string} url The base URL. There is not sanity checking on the URL so
  2525.  *     it must be passed in a proper format.
  2526.  * @param {string} key The key of the param.
  2527.  * @param {string} value The value of the param.
  2528.  * @return {string} The new URL.
  2529.  */
  2530. function appendParam(url, key, value) {
  2531.   var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
  2532.  
  2533.   if (url.indexOf('?') == -1)
  2534.     return url + '?' + param;
  2535.   return url + '&' + param;
  2536. }
  2537.  
  2538. /**
  2539.  * Creates a new URL for a favicon request.
  2540.  * @param {string} url The url for the favicon.
  2541.  * @param {number=} opt_size Optional preferred size of the favicon.
  2542.  * @return {string} Updated URL for the favicon.
  2543.  */
  2544. function getFaviconURL(url, opt_size) {
  2545.   var size = opt_size || 16;
  2546.   return 'chrome://favicon/size/' + size + '@' +
  2547.       window.devicePixelRatio + 'x/' + url;
  2548. }
  2549. </script>
  2550.  
  2551. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2552. // Use of this source code is governed by a BSD-style license that can be
  2553. // found in the LICENSE file.
  2554.  
  2555. /**
  2556.  * The global object.
  2557.  * @type {!Object}
  2558.  * @const
  2559.  */
  2560. var global = this;
  2561.  
  2562. /** Platform, package, object property, and Event support. **/
  2563. this.cr = (function() {
  2564.   'use strict';
  2565.  
  2566.   /**
  2567.    * Builds an object structure for the provided namespace path,
  2568.    * ensuring that names that already exist are not overwritten. For
  2569.    * example:
  2570.    * "a.b.c" -> a = {};a.b={};a.b.c={};
  2571.    * @param {string} name Name of the object that this file defines.
  2572.    * @param {*=} opt_object The object to expose at the end of the path.
  2573.    * @param {Object=} opt_objectToExportTo The object to add the path to;
  2574.    *     default is {@code global}.
  2575.    * @private
  2576.    */
  2577.   function exportPath(name, opt_object, opt_objectToExportTo) {
  2578.     var parts = name.split('.');
  2579.     var cur = opt_objectToExportTo || global;
  2580.  
  2581.     for (var part; parts.length && (part = parts.shift());) {
  2582.       if (!parts.length && opt_object !== undefined) {
  2583.         // last part and we have an object; use it
  2584.         cur[part] = opt_object;
  2585.       } else if (part in cur) {
  2586.         cur = cur[part];
  2587.       } else {
  2588.         cur = cur[part] = {};
  2589.       }
  2590.     }
  2591.     return cur;
  2592.   };
  2593.  
  2594.   /**
  2595.    * Fires a property change event on the target.
  2596.    * @param {EventTarget} target The target to dispatch the event on.
  2597.    * @param {string} propertyName The name of the property that changed.
  2598.    * @param {*} newValue The new value for the property.
  2599.    * @param {*} oldValue The old value for the property.
  2600.    */
  2601.   function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
  2602.     var e = new cr.Event(propertyName + 'Change');
  2603.     e.propertyName = propertyName;
  2604.     e.newValue = newValue;
  2605.     e.oldValue = oldValue;
  2606.     target.dispatchEvent(e);
  2607.   }
  2608.  
  2609.   /**
  2610.    * Converts a camelCase javascript property name to a hyphenated-lower-case
  2611.    * attribute name.
  2612.    * @param {string} jsName The javascript camelCase property name.
  2613.    * @return {string} The equivalent hyphenated-lower-case attribute name.
  2614.    */
  2615.   function getAttributeName(jsName) {
  2616.     return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
  2617.   }
  2618.  
  2619.   /**
  2620.    * The kind of property to define in {@code defineProperty}.
  2621.    * @enum {number}
  2622.    * @const
  2623.    */
  2624.   var PropertyKind = {
  2625.     /**
  2626.      * Plain old JS property where the backing data is stored as a "private"
  2627.      * field on the object.
  2628.      */
  2629.     JS: 'js',
  2630.  
  2631.     /**
  2632.      * The property backing data is stored as an attribute on an element.
  2633.      */
  2634.     ATTR: 'attr',
  2635.  
  2636.     /**
  2637.      * The property backing data is stored as an attribute on an element. If the
  2638.      * element has the attribute then the value is true.
  2639.      */
  2640.     BOOL_ATTR: 'boolAttr'
  2641.   };
  2642.  
  2643.   /**
  2644.    * Helper function for defineProperty that returns the getter to use for the
  2645.    * property.
  2646.    * @param {string} name The name of the property.
  2647.    * @param {cr.PropertyKind} kind The kind of the property.
  2648.    * @return {function():*} The getter for the property.
  2649.    */
  2650.   function getGetter(name, kind) {
  2651.     switch (kind) {
  2652.       case PropertyKind.JS:
  2653.         var privateName = name + '_';
  2654.         return function() {
  2655.           return this[privateName];
  2656.         };
  2657.       case PropertyKind.ATTR:
  2658.         var attributeName = getAttributeName(name);
  2659.         return function() {
  2660.           return this.getAttribute(attributeName);
  2661.         };
  2662.       case PropertyKind.BOOL_ATTR:
  2663.         var attributeName = getAttributeName(name);
  2664.         return function() {
  2665.           return this.hasAttribute(attributeName);
  2666.         };
  2667.     }
  2668.   }
  2669.  
  2670.   /**
  2671.    * Helper function for defineProperty that returns the setter of the right
  2672.    * kind.
  2673.    * @param {string} name The name of the property we are defining the setter
  2674.    *     for.
  2675.    * @param {cr.PropertyKind} kind The kind of property we are getting the
  2676.    *     setter for.
  2677.    * @param {function(*):void} opt_setHook A function to run after the property
  2678.    *     is set, but before the propertyChange event is fired.
  2679.    * @return {function(*):void} The function to use as a setter.
  2680.    */
  2681.   function getSetter(name, kind, opt_setHook) {
  2682.     switch (kind) {
  2683.       case PropertyKind.JS:
  2684.         var privateName = name + '_';
  2685.         return function(value) {
  2686.           var oldValue = this[name];
  2687.           if (value !== oldValue) {
  2688.             this[privateName] = value;
  2689.             if (opt_setHook)
  2690.               opt_setHook.call(this, value, oldValue);
  2691.             dispatchPropertyChange(this, name, value, oldValue);
  2692.           }
  2693.         };
  2694.  
  2695.       case PropertyKind.ATTR:
  2696.         var attributeName = getAttributeName(name);
  2697.         return function(value) {
  2698.           var oldValue = this[name];
  2699.           if (value !== oldValue) {
  2700.             if (value == undefined)
  2701.               this.removeAttribute(attributeName);
  2702.             else
  2703.               this.setAttribute(attributeName, value);
  2704.             if (opt_setHook)
  2705.               opt_setHook.call(this, value, oldValue);
  2706.             dispatchPropertyChange(this, name, value, oldValue);
  2707.           }
  2708.         };
  2709.  
  2710.       case PropertyKind.BOOL_ATTR:
  2711.         var attributeName = getAttributeName(name);
  2712.         return function(value) {
  2713.           var oldValue = this[name];
  2714.           if (value !== oldValue) {
  2715.             if (value)
  2716.               this.setAttribute(attributeName, name);
  2717.             else
  2718.               this.removeAttribute(attributeName);
  2719.             if (opt_setHook)
  2720.               opt_setHook.call(this, value, oldValue);
  2721.             dispatchPropertyChange(this, name, value, oldValue);
  2722.           }
  2723.         };
  2724.     }
  2725.   }
  2726.  
  2727.   /**
  2728.    * Defines a property on an object. When the setter changes the value a
  2729.    * property change event with the type {@code name + 'Change'} is fired.
  2730.    * @param {!Object} obj The object to define the property for.
  2731.    * @param {string} name The name of the property.
  2732.    * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use.
  2733.    * @param {function(*):void} opt_setHook A function to run after the
  2734.    *     property is set, but before the propertyChange event is fired.
  2735.    */
  2736.   function defineProperty(obj, name, opt_kind, opt_setHook) {
  2737.     if (typeof obj == 'function')
  2738.       obj = obj.prototype;
  2739.  
  2740.     var kind = opt_kind || PropertyKind.JS;
  2741.  
  2742.     if (!obj.__lookupGetter__(name))
  2743.       obj.__defineGetter__(name, getGetter(name, kind));
  2744.  
  2745.     if (!obj.__lookupSetter__(name))
  2746.       obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
  2747.   }
  2748.  
  2749.   /**
  2750.    * Counter for use with createUid
  2751.    */
  2752.   var uidCounter = 1;
  2753.  
  2754.   /**
  2755.    * @return {number} A new unique ID.
  2756.    */
  2757.   function createUid() {
  2758.     return uidCounter++;
  2759.   }
  2760.  
  2761.   /**
  2762.    * Returns a unique ID for the item. This mutates the item so it needs to be
  2763.    * an object
  2764.    * @param {!Object} item The item to get the unique ID for.
  2765.    * @return {number} The unique ID for the item.
  2766.    */
  2767.   function getUid(item) {
  2768.     if (item.hasOwnProperty('uid'))
  2769.       return item.uid;
  2770.     return item.uid = createUid();
  2771.   }
  2772.  
  2773.   /**
  2774.    * Dispatches a simple event on an event target.
  2775.    * @param {!EventTarget} target The event target to dispatch the event on.
  2776.    * @param {string} type The type of the event.
  2777.    * @param {boolean=} opt_bubbles Whether the event bubbles or not.
  2778.    * @param {boolean=} opt_cancelable Whether the default action of the event
  2779.    *     can be prevented.
  2780.    * @return {boolean} If any of the listeners called {@code preventDefault}
  2781.    *     during the dispatch this will return false.
  2782.    */
  2783.   function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
  2784.     var e = new cr.Event(type, opt_bubbles, opt_cancelable);
  2785.     return target.dispatchEvent(e);
  2786.   }
  2787.  
  2788.   /**
  2789.    * Calls |fun| and adds all the fields of the returned object to the object
  2790.    * named by |name|. For example, cr.define('cr.ui', function() {
  2791.    *   function List() {
  2792.    *     ...
  2793.    *   }
  2794.    *   function ListItem() {
  2795.    *     ...
  2796.    *   }
  2797.    *   return {
  2798.    *     List: List,
  2799.    *     ListItem: ListItem,
  2800.    *   };
  2801.    * });
  2802.    * defines the functions cr.ui.List and cr.ui.ListItem.
  2803.    * @param {string} name The name of the object that we are adding fields to.
  2804.    * @param {!Function} fun The function that will return an object containing
  2805.    *     the names and values of the new fields.
  2806.    */
  2807.   function define(name, fun) {
  2808.     var obj = exportPath(name);
  2809.     var exports = fun();
  2810.     for (var propertyName in exports) {
  2811.       // Maybe we should check the prototype chain here? The current usage
  2812.       // pattern is always using an object literal so we only care about own
  2813.       // properties.
  2814.       var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
  2815.                                                                propertyName);
  2816.       if (propertyDescriptor)
  2817.         Object.defineProperty(obj, propertyName, propertyDescriptor);
  2818.     }
  2819.   }
  2820.  
  2821.   /**
  2822.    * Adds a {@code getInstance} static method that always return the same
  2823.    * instance object.
  2824.    * @param {!Function} ctor The constructor for the class to add the static
  2825.    *     method to.
  2826.    */
  2827.   function addSingletonGetter(ctor) {
  2828.     ctor.getInstance = function() {
  2829.       return ctor.instance_ || (ctor.instance_ = new ctor());
  2830.     };
  2831.   }
  2832.  
  2833.   /**
  2834.    * Creates a new event to be used with cr.EventTarget or DOM EventTarget
  2835.    * objects.
  2836.    * @param {string} type The name of the event.
  2837.    * @param {boolean=} opt_bubbles Whether the event bubbles.
  2838.    *     Default is false.
  2839.    * @param {boolean=} opt_preventable Whether the default action of the event
  2840.    *     can be prevented.
  2841.    * @constructor
  2842.    * @extends {Event}
  2843.    */
  2844.   function Event(type, opt_bubbles, opt_preventable) {
  2845.     var e = cr.doc.createEvent('Event');
  2846.     e.initEvent(type, !!opt_bubbles, !!opt_preventable);
  2847.     e.__proto__ = global.Event.prototype;
  2848.     return e;
  2849.   };
  2850.  
  2851.   /**
  2852.    * Initialization which must be deferred until run-time.
  2853.    */
  2854.   function initialize() {
  2855.     // If 'document' isn't defined, then we must be being pre-compiled,
  2856.     // so set a trap so that we're initialized on first access at run-time.
  2857.     if (!global.document) {
  2858.       var originalCr = cr;
  2859.  
  2860.       Object.defineProperty(global, 'cr', {
  2861.         get: function() {
  2862.           Object.defineProperty(global, 'cr', {value: originalCr});
  2863.           originalCr.initialize();
  2864.           return originalCr;
  2865.         },
  2866.         configurable: true
  2867.       });
  2868.  
  2869.       return;
  2870.     }
  2871.  
  2872.     Event.prototype = {__proto__: global.Event.prototype};
  2873.  
  2874.     cr.doc = document;
  2875.  
  2876.     /**
  2877.      * Whether we are using a Mac or not.
  2878.      */
  2879.     cr.isMac = /Mac/.test(navigator.platform);
  2880.  
  2881.     /**
  2882.      * Whether this is on the Windows platform or not.
  2883.      */
  2884.     cr.isWindows = /Win/.test(navigator.platform);
  2885.  
  2886.     /**
  2887.      * Whether this is on chromeOS or not.
  2888.      */
  2889.     cr.isChromeOS = /CrOS/.test(navigator.userAgent);
  2890.  
  2891.     /**
  2892.      * Whether this is on vanilla Linux (not chromeOS).
  2893.      */
  2894.     cr.isLinux = /Linux/.test(navigator.userAgent);
  2895.  
  2896.     /**
  2897.      * Whether this uses GTK or not.
  2898.      */
  2899.     cr.isGTK = typeof chrome.getVariableValue == 'function' &&
  2900.         /GTK/.test(chrome.getVariableValue('toolkit'));
  2901.  
  2902.     /**
  2903.      * Whether this uses the views toolkit or not.
  2904.      */
  2905.     cr.isViews = typeof chrome.getVariableValue == 'function' &&
  2906.         /views/.test(chrome.getVariableValue('toolkit'));
  2907.   }
  2908.  
  2909.   return {
  2910.     addSingletonGetter: addSingletonGetter,
  2911.     createUid: createUid,
  2912.     define: define,
  2913.     defineProperty: defineProperty,
  2914.     dispatchPropertyChange: dispatchPropertyChange,
  2915.     dispatchSimpleEvent: dispatchSimpleEvent,
  2916.     Event: Event,
  2917.     getUid: getUid,
  2918.     initialize: initialize,
  2919.     PropertyKind: PropertyKind
  2920.   };
  2921. })();
  2922.  
  2923.  
  2924. /**
  2925.  * TODO(kgr): Move this to another file which is to be loaded last.
  2926.  * This will be done as part of future work to make this code pre-compilable.
  2927.  */
  2928. cr.initialize();
  2929. </script>
  2930. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2931. // Use of this source code is governed by a BSD-style license that can be
  2932. // found in the LICENSE file.
  2933.  
  2934. cr.define('cr.ui', function() {
  2935.  
  2936.   /**
  2937.    * Decorates elements as an instance of a class.
  2938.    * @param {string|!Element} source The way to find the element(s) to decorate.
  2939.    *     If this is a string then {@code querySeletorAll} is used to find the
  2940.    *     elements to decorate.
  2941.    * @param {!Function} constr The constructor to decorate with. The constr
  2942.    *     needs to have a {@code decorate} function.
  2943.    */
  2944.   function decorate(source, constr) {
  2945.     var elements;
  2946.     if (typeof source == 'string')
  2947.       elements = cr.doc.querySelectorAll(source);
  2948.     else
  2949.       elements = [source];
  2950.  
  2951.     for (var i = 0, el; el = elements[i]; i++) {
  2952.       if (!(el instanceof constr))
  2953.         constr.decorate(el);
  2954.     }
  2955.   }
  2956.  
  2957.   /**
  2958.    * Helper function for creating new element for define.
  2959.    */
  2960.   function createElementHelper(tagName, opt_bag) {
  2961.     // Allow passing in ownerDocument to create in a different document.
  2962.     var doc;
  2963.     if (opt_bag && opt_bag.ownerDocument)
  2964.       doc = opt_bag.ownerDocument;
  2965.     else
  2966.       doc = cr.doc;
  2967.     return doc.createElement(tagName);
  2968.   }
  2969.  
  2970.   /**
  2971.    * Creates the constructor for a UI element class.
  2972.    *
  2973.    * Usage:
  2974.    * <pre>
  2975.    * var List = cr.ui.define('list');
  2976.    * List.prototype = {
  2977.    *   __proto__: HTMLUListElement.prototype,
  2978.    *   decorate: function() {
  2979.    *     ...
  2980.    *   },
  2981.    *   ...
  2982.    * };
  2983.    * </pre>
  2984.    *
  2985.    * @param {string|Function} tagNameOrFunction The tagName or
  2986.    *     function to use for newly created elements. If this is a function it
  2987.    *     needs to return a new element when called.
  2988.    * @return {function(Object=):Element} The constructor function which takes
  2989.    *     an optional property bag. The function also has a static
  2990.    *     {@code decorate} method added to it.
  2991.    */
  2992.   function define(tagNameOrFunction) {
  2993.     var createFunction, tagName;
  2994.     if (typeof tagNameOrFunction == 'function') {
  2995.       createFunction = tagNameOrFunction;
  2996.       tagName = '';
  2997.     } else {
  2998.       createFunction = createElementHelper;
  2999.       tagName = tagNameOrFunction;
  3000.     }
  3001.  
  3002.     /**
  3003.      * Creates a new UI element constructor.
  3004.      * @param {Object=} opt_propertyBag Optional bag of properties to set on the
  3005.      *     object after created. The property {@code ownerDocument} is special
  3006.      *     cased and it allows you to create the element in a different
  3007.      *     document than the default.
  3008.      * @constructor
  3009.      */
  3010.     function f(opt_propertyBag) {
  3011.       var el = createFunction(tagName, opt_propertyBag);
  3012.       f.decorate(el);
  3013.       for (var propertyName in opt_propertyBag) {
  3014.         el[propertyName] = opt_propertyBag[propertyName];
  3015.       }
  3016.       return el;
  3017.     }
  3018.  
  3019.     /**
  3020.      * Decorates an element as a UI element class.
  3021.      * @param {!Element} el The element to decorate.
  3022.      */
  3023.     f.decorate = function(el) {
  3024.       el.__proto__ = f.prototype;
  3025.       el.decorate();
  3026.     };
  3027.  
  3028.     return f;
  3029.   }
  3030.  
  3031.   /**
  3032.    * Input elements do not grow and shrink with their content. This is a simple
  3033.    * (and not very efficient) way of handling shrinking to content with support
  3034.    * for min width and limited by the width of the parent element.
  3035.    * @param {HTMLElement} el The element to limit the width for.
  3036.    * @param {number} parentEl The parent element that should limit the size.
  3037.    * @param {number} min The minimum width.
  3038.    * @param {number} opt_scale Optional scale factor to apply to the width.
  3039.    */
  3040.   function limitInputWidth(el, parentEl, min, opt_scale) {
  3041.     // Needs a size larger than borders
  3042.     el.style.width = '10px';
  3043.     var doc = el.ownerDocument;
  3044.     var win = doc.defaultView;
  3045.     var computedStyle = win.getComputedStyle(el);
  3046.     var parentComputedStyle = win.getComputedStyle(parentEl);
  3047.     var rtl = computedStyle.direction == 'rtl';
  3048.  
  3049.     // To get the max width we get the width of the treeItem minus the position
  3050.     // of the input.
  3051.     var inputRect = el.getBoundingClientRect();  // box-sizing
  3052.     var parentRect = parentEl.getBoundingClientRect();
  3053.     var startPos = rtl ? parentRect.right - inputRect.right :
  3054.         inputRect.left - parentRect.left;
  3055.  
  3056.     // Add up border and padding of the input.
  3057.     var inner = parseInt(computedStyle.borderLeftWidth, 10) +
  3058.         parseInt(computedStyle.paddingLeft, 10) +
  3059.         parseInt(computedStyle.paddingRight, 10) +
  3060.         parseInt(computedStyle.borderRightWidth, 10);
  3061.  
  3062.     // We also need to subtract the padding of parent to prevent it to overflow.
  3063.     var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
  3064.         parseInt(parentComputedStyle.paddingRight, 10);
  3065.  
  3066.     var max = parentEl.clientWidth - startPos - inner - parentPadding;
  3067.     if (opt_scale)
  3068.       max *= opt_scale;
  3069.  
  3070.     function limit() {
  3071.       if (el.scrollWidth > max) {
  3072.         el.style.width = max + 'px';
  3073.       } else {
  3074.         el.style.width = 0;
  3075.         var sw = el.scrollWidth;
  3076.         if (sw < min) {
  3077.           el.style.width = min + 'px';
  3078.         } else {
  3079.           el.style.width = sw + 'px';
  3080.         }
  3081.       }
  3082.     }
  3083.  
  3084.     el.addEventListener('input', limit);
  3085.     limit();
  3086.   }
  3087.  
  3088.   /**
  3089.    * Takes a number and spits out a value CSS will be happy with. To avoid
  3090.    * subpixel layout issues, the value is rounded to the nearest integral value.
  3091.    * @param {number} pixels The number of pixels.
  3092.    * @return {string} e.g. '16px'.
  3093.    */
  3094.   function toCssPx(pixels) {
  3095.     if (!window.isFinite(pixels))
  3096.       console.error('Pixel value is not a number: ' + pixels);
  3097.     return Math.round(pixels) + 'px';
  3098.   }
  3099.  
  3100.   /**
  3101.    * Users complain they occasionaly use doubleclicks instead of clicks
  3102.    * (http://crbug.com/140364). To fix it we freeze click handling for
  3103.    * the doubleclick time interval.
  3104.    * @param {MouseEvent} e Initial click event.
  3105.    */
  3106.   function swallowDoubleClick(e) {
  3107.     var doc = e.target.ownerDocument;
  3108.     var counter = e.type == 'click' ? e.detail : 0;
  3109.     function swallow(e) {
  3110.       e.stopPropagation();
  3111.       e.preventDefault();
  3112.     }
  3113.     function onclick(e) {
  3114.       if (e.detail > counter) {
  3115.         counter = e.detail;
  3116.         // Swallow the click since it's a click inside the doubleclick timeout.
  3117.         swallow(e);
  3118.       } else {
  3119.         // Stop tracking clicks and let regular handling.
  3120.         doc.removeEventListener('dblclick', swallow, true);
  3121.         doc.removeEventListener('click', onclick, true);
  3122.       }
  3123.     }
  3124.     doc.addEventListener('click', onclick, true);
  3125.     doc.addEventListener('dblclick', swallow, true);
  3126.   }
  3127.  
  3128.   return {
  3129.     decorate: decorate,
  3130.     define: define,
  3131.     limitInputWidth: limitInputWidth,
  3132.     toCssPx: toCssPx,
  3133.     swallowDoubleClick: swallowDoubleClick
  3134.   };
  3135. });
  3136. </script>
  3137. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3138. // Use of this source code is governed by a BSD-style license that can be
  3139. // found in the LICENSE file.
  3140.  
  3141. // require: event_tracker.js
  3142.  
  3143. cr.define('cr.ui', function() {
  3144.  
  3145.   /**
  3146.    * The arrow location specifies how the arrow and bubble are positioned in
  3147.    * relation to the anchor node.
  3148.    * @enum
  3149.    */
  3150.   var ArrowLocation = {
  3151.     // The arrow is positioned at the top and the start of the bubble. In left
  3152.     // to right mode this is the top left. The entire bubble is positioned below
  3153.     // the anchor node.
  3154.     TOP_START: 'top-start',
  3155.     // The arrow is positioned at the top and the end of the bubble. In left to
  3156.     // right mode this is the top right. The entire bubble is positioned below
  3157.     // the anchor node.
  3158.     TOP_END: 'top-end',
  3159.     // The arrow is positioned at the bottom and the start of the bubble. In
  3160.     // left to right mode this is the bottom left. The entire bubble is
  3161.     // positioned above the anchor node.
  3162.     BOTTOM_START: 'bottom-start',
  3163.     // The arrow is positioned at the bottom and the end of the bubble. In
  3164.     // left to right mode this is the bottom right. The entire bubble is
  3165.     // positioned above the anchor node.
  3166.     BOTTOM_END: 'bottom-end'
  3167.   };
  3168.  
  3169.   /**
  3170.    * The bubble alignment specifies the position of the bubble in relation to
  3171.    * the anchor node.
  3172.    * @enum
  3173.    */
  3174.   var BubbleAlignment = {
  3175.     // The bubble is positioned just above or below the anchor node (as
  3176.     // specified by the arrow location) so that the arrow points at the midpoint
  3177.     // of the anchor.
  3178.     ARROW_TO_MID_ANCHOR: 'arrow-to-mid-anchor',
  3179.     // The bubble is positioned just above or below the anchor node (as
  3180.     // specified by the arrow location) so that its reference edge lines up with
  3181.     // the edge of the anchor.
  3182.     BUBBLE_EDGE_TO_ANCHOR_EDGE: 'bubble-edge-anchor-edge',
  3183.     // The bubble is positioned so that it is entirely within view and does not
  3184.     // obstruct the anchor element, if possible. The specified arrow location is
  3185.     // taken into account as the preferred alignment but may be overruled if
  3186.     // there is insufficient space (see BubbleBase.reposition for the exact
  3187.     // placement algorithm).
  3188.     ENTIRELY_VISIBLE: 'entirely-visible'
  3189.   };
  3190.  
  3191.   /**
  3192.    * Abstract base class that provides common functionality for implementing
  3193.    * free-floating informational bubbles with a triangular arrow pointing at an
  3194.    * anchor node.
  3195.    */
  3196.   var BubbleBase = cr.ui.define('div');
  3197.  
  3198.   /**
  3199.    * The horizontal distance between the tip of the arrow and the reference edge
  3200.    * of the bubble (as specified by the arrow location). In pixels.
  3201.    * @type {number}
  3202.    * @const
  3203.    */
  3204.   BubbleBase.ARROW_OFFSET = 30;
  3205.  
  3206.   /**
  3207.    * Minimum horizontal spacing between edge of bubble and edge of viewport
  3208.    * (when using the ENTIRELY_VISIBLE alignment). In pixels.
  3209.    * @type {number}
  3210.    * @const
  3211.    */
  3212.   BubbleBase.MIN_VIEWPORT_EDGE_MARGIN = 2;
  3213.  
  3214.   BubbleBase.prototype = {
  3215.     // Set up the prototype chain.
  3216.     __proto__: HTMLDivElement.prototype,
  3217.  
  3218.     /**
  3219.      * Initialization function for the cr.ui framework.
  3220.      */
  3221.     decorate: function() {
  3222.       this.className = 'bubble';
  3223.       this.innerHTML =
  3224.           '<div class="bubble-content"></div>' +
  3225.           '<div class="bubble-shadow"></div>' +
  3226.           '<div class="bubble-arrow"></div>';
  3227.       this.hidden = true;
  3228.       this.bubbleAlignment = BubbleAlignment.ENTIRELY_VISIBLE;
  3229.     },
  3230.  
  3231.     /**
  3232.      * Set the anchor node, i.e. the node that this bubble points at. Only
  3233.      * available when the bubble is not being shown.
  3234.      * @param {HTMLElement} node The new anchor node.
  3235.      */
  3236.     set anchorNode(node) {
  3237.       if (!this.hidden)
  3238.         return;
  3239.  
  3240.       this.anchorNode_ = node;
  3241.     },
  3242.  
  3243.     /**
  3244.      * Set the conent of the bubble. Only available when the bubble is not being
  3245.      * shown.
  3246.      * @param {HTMLElement} node The root node of the new content.
  3247.      */
  3248.     set content(node) {
  3249.       if (!this.hidden)
  3250.         return;
  3251.  
  3252.       var bubbleContent = this.querySelector('.bubble-content');
  3253.       bubbleContent.innerHTML = '';
  3254.       bubbleContent.appendChild(node);
  3255.     },
  3256.  
  3257.     /**
  3258.      * Set the arrow location. Only available when the bubble is not being
  3259.      * shown.
  3260.      * @param {cr.ui.ArrowLocation} location The new arrow location.
  3261.      */
  3262.     set arrowLocation(location) {
  3263.       if (!this.hidden)
  3264.         return;
  3265.  
  3266.       this.arrowAtRight_ = location == ArrowLocation.TOP_END ||
  3267.                            location == ArrowLocation.BOTTOM_END;
  3268.       if (document.documentElement.dir == 'rtl')
  3269.         this.arrowAtRight_ = !this.arrowAtRight_;
  3270.       this.arrowAtTop_ = location == ArrowLocation.TOP_START ||
  3271.                          location == ArrowLocation.TOP_END;
  3272.     },
  3273.  
  3274.     /**
  3275.      * Set the bubble alignment. Only available when the bubble is not being
  3276.      * shown.
  3277.      * @param {cr.ui.BubbleAlignment} alignment The new bubble alignment.
  3278.      */
  3279.     set bubbleAlignment(alignment) {
  3280.       if (!this.hidden)
  3281.         return;
  3282.  
  3283.       this.bubbleAlignment_ = alignment;
  3284.     },
  3285.  
  3286.     /**
  3287.      * Update the position of the bubble. Whenever the layout may have changed,
  3288.      * the bubble should either be repositioned by calling this function or
  3289.      * hidden so that it does not point to a nonsensical location on the page.
  3290.      */
  3291.     reposition: function() {
  3292.       var documentWidth = document.documentElement.clientWidth;
  3293.       var documentHeight = document.documentElement.clientHeight;
  3294.       var anchor = this.anchorNode_.getBoundingClientRect();
  3295.       var anchorMid = (anchor.left + anchor.right) / 2;
  3296.       var bubble = this.getBoundingClientRect();
  3297.       var arrow = this.querySelector('.bubble-arrow').getBoundingClientRect();
  3298.  
  3299.       if (this.bubbleAlignment_ == BubbleAlignment.ENTIRELY_VISIBLE) {
  3300.         // Work out horizontal placement. The bubble is initially positioned so
  3301.         // that the arrow tip points toward the midpoint of the anchor and is
  3302.         // BubbleBase.ARROW_OFFSET pixels from the reference edge and (as
  3303.         // specified by the arrow location). If the bubble is not entirely
  3304.         // within view, it is then shifted, preserving the arrow tip position.
  3305.         var left = this.arrowAtRight_ ?
  3306.            anchorMid + BubbleBase.ARROW_OFFSET - bubble.width :
  3307.            anchorMid - BubbleBase.ARROW_OFFSET;
  3308.         var max_left_pos =
  3309.             documentWidth - bubble.width - BubbleBase.MIN_VIEWPORT_EDGE_MARGIN;
  3310.         var min_left_pos = BubbleBase.MIN_VIEWPORT_EDGE_MARGIN;
  3311.         if (document.documentElement.dir == 'rtl')
  3312.           left = Math.min(Math.max(left, min_left_pos), max_left_pos);
  3313.         else
  3314.           left = Math.max(Math.min(left, max_left_pos), min_left_pos);
  3315.         var arrowTip = Math.min(
  3316.             Math.max(arrow.width / 2,
  3317.                      this.arrowAtRight_ ? left + bubble.width - anchorMid :
  3318.                                           anchorMid - left),
  3319.             bubble.width - arrow.width / 2);
  3320.  
  3321.         // Work out the vertical placement, attempting to fit the bubble
  3322.         // entirely into view. The following placements are considered in
  3323.         // decreasing order of preference:
  3324.         // * Outside the anchor, arrow tip touching the anchor (arrow at
  3325.         //   top/bottom as specified by the arrow location).
  3326.         // * Outside the anchor, arrow tip touching the anchor (arrow at
  3327.         //   bottom/top, opposite the specified arrow location).
  3328.         // * Outside the anchor, arrow tip overlapping the anchor (arrow at
  3329.         //   top/bottom as specified by the arrow location).
  3330.         // * Outside the anchor, arrow tip overlapping the anchor (arrow at
  3331.         //   bottom/top, opposite the specified arrow location).
  3332.         // * Overlapping the anchor.
  3333.         var offsetTop = Math.min(documentHeight - anchor.bottom - bubble.height,
  3334.                                  arrow.height / 2);
  3335.         var offsetBottom = Math.min(anchor.top - bubble.height,
  3336.                                     arrow.height / 2);
  3337.         if (offsetTop < 0 && offsetBottom < 0) {
  3338.           var top = 0;
  3339.           this.updateArrowPosition_(false, false, arrowTip);
  3340.         } else if (offsetTop > offsetBottom ||
  3341.                    offsetTop == offsetBottom && this.arrowAtTop_) {
  3342.           var top = anchor.bottom + offsetTop;
  3343.           this.updateArrowPosition_(true, true, arrowTip);
  3344.         } else {
  3345.           var top = anchor.top - bubble.height - offsetBottom;
  3346.           this.updateArrowPosition_(true, false, arrowTip);
  3347.         }
  3348.       } else {
  3349.         if (this.bubbleAlignment_ ==
  3350.             BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE) {
  3351.           var left = this.arrowAtRight_ ? anchor.right - bubble.width :
  3352.               anchor.left;
  3353.         } else {
  3354.           var left = this.arrowAtRight_ ?
  3355.               anchorMid - this.clientWidth + BubbleBase.ARROW_OFFSET :
  3356.               anchorMid - BubbleBase.ARROW_OFFSET;
  3357.         }
  3358.         var top = this.arrowAtTop_ ? anchor.bottom + arrow.height / 2 :
  3359.             anchor.top - this.clientHeight - arrow.height / 2;
  3360.         this.updateArrowPosition_(true, this.arrowAtTop_,
  3361.                                   BubbleBase.ARROW_OFFSET);
  3362.       }
  3363.  
  3364.       this.style.left = left + 'px';
  3365.       this.style.top = top + 'px';
  3366.     },
  3367.  
  3368.     /**
  3369.      * Show the bubble.
  3370.      */
  3371.     show: function() {
  3372.       if (!this.hidden)
  3373.         return;
  3374.  
  3375.       this.attachToDOM_();
  3376.       this.hidden = false;
  3377.       this.reposition();
  3378.  
  3379.       var doc = this.ownerDocument;
  3380.       this.eventTracker_ = new EventTracker;
  3381.       this.eventTracker_.add(doc, 'keydown', this, true);
  3382.       this.eventTracker_.add(doc, 'mousedown', this, true);
  3383.     },
  3384.  
  3385.     /**
  3386.      * Hide the bubble.
  3387.      */
  3388.     hide: function() {
  3389.       if (this.hidden)
  3390.         return;
  3391.  
  3392.       this.eventTracker_.removeAll();
  3393.       this.hidden = true;
  3394.       this.parentNode.removeChild(this);
  3395.     },
  3396.  
  3397.     /**
  3398.      * Handle keyboard events, dismissing the bubble if necessary.
  3399.      * @param {Event} event The event.
  3400.      */
  3401.     handleEvent: function(event) {
  3402.       // Close the bubble when the user presses <Esc>.
  3403.       if (event.type == 'keydown' && event.keyCode == 27) {
  3404.         this.hide();
  3405.         event.preventDefault();
  3406.         event.stopPropagation();
  3407.       }
  3408.     },
  3409.  
  3410.     /**
  3411.      * Attach the bubble to the document's DOM.
  3412.      * @private
  3413.      */
  3414.     attachToDOM_: function() {
  3415.       document.body.appendChild(this);
  3416.     },
  3417.  
  3418.     /**
  3419.      * Update the arrow so that it appears at the correct position.
  3420.      * @param {Boolean} visible Whether the arrow should be visible.
  3421.      * @param {Boolean} atTop Whether the arrow should be at the top of the
  3422.      * bubble.
  3423.      * @param {number} tipOffset The horizontal distance between the tip of the
  3424.      * arrow and the reference edge of the bubble (as specified by the arrow
  3425.      * location).
  3426.      * @private
  3427.      */
  3428.     updateArrowPosition_: function(visible, atTop, tipOffset) {
  3429.       var bubbleArrow = this.querySelector('.bubble-arrow');
  3430.       bubbleArrow.hidden = !visible;
  3431.       if (!visible)
  3432.         return;
  3433.  
  3434.       var edgeOffset = (-bubbleArrow.clientHeight / 2) + 'px';
  3435.       bubbleArrow.style.top = atTop ? edgeOffset : 'auto';
  3436.       bubbleArrow.style.bottom = atTop ? 'auto' : edgeOffset;
  3437.  
  3438.       edgeOffset = (tipOffset - bubbleArrow.offsetWidth / 2) + 'px';
  3439.       bubbleArrow.style.left = this.arrowAtRight_ ? 'auto' : edgeOffset;
  3440.       bubbleArrow.style.right = this.arrowAtRight_ ? edgeOffset : 'auto';
  3441.     },
  3442.   };
  3443.  
  3444.   /**
  3445.    * A bubble that remains open until the user explicitly dismisses it or clicks
  3446.    * outside the bubble after it has been shown for at least the specified
  3447.    * amount of time (making it less likely that the user will unintentionally
  3448.    * dismiss the bubble). The bubble repositions itself on layout changes.
  3449.    */
  3450.   var Bubble = cr.ui.define('div');
  3451.  
  3452.   Bubble.prototype = {
  3453.     // Set up the prototype chain
  3454.     __proto__: BubbleBase.prototype,
  3455.  
  3456.     /**
  3457.      * Initialization function for the cr.ui framework.
  3458.      */
  3459.     decorate: function() {
  3460.       BubbleBase.prototype.decorate.call(this);
  3461.  
  3462.       var close = document.createElement('div');
  3463.       close.className = 'bubble-close';
  3464.       this.insertBefore(close, this.querySelector('.bubble-content'));
  3465.  
  3466.       this.handleCloseEvent = this.hide;
  3467.       this.deactivateToDismissDelay_ = 0;
  3468.       this.bubbleAlignment = BubbleAlignment.ARROW_TO_MID_ANCHOR;
  3469.     },
  3470.  
  3471.     /**
  3472.      * Handler for close events triggered when the close button is clicked. By
  3473.      * default, set to this.hide. Only available when the bubble is not being
  3474.      * shown.
  3475.      * @param {function} handler The new handler, a function with no parameters.
  3476.      */
  3477.     set handleCloseEvent(handler) {
  3478.       if (!this.hidden)
  3479.         return;
  3480.  
  3481.       this.handleCloseEvent_ = handler;
  3482.     },
  3483.  
  3484.     /**
  3485.      * Set the delay before the user is allowed to click outside the bubble to
  3486.      * dismiss it. Using a delay makes it less likely that the user will
  3487.      * unintentionally dismiss the bubble.
  3488.      * @param {number} delay The delay in milliseconds.
  3489.      */
  3490.     set deactivateToDismissDelay(delay) {
  3491.       this.deactivateToDismissDelay_ = delay;
  3492.     },
  3493.  
  3494.     /**
  3495.      * Hide or show the close button.
  3496.      * @param {Boolean} isVisible True if the close button should be visible.
  3497.      */
  3498.     set closeButtonVisible(isVisible) {
  3499.       this.querySelector('.bubble-close').hidden = !isVisible;
  3500.     },
  3501.  
  3502.     /**
  3503.      * Show the bubble.
  3504.      */
  3505.     show: function() {
  3506.       if (!this.hidden)
  3507.         return;
  3508.  
  3509.       BubbleBase.prototype.show.call(this);
  3510.  
  3511.       this.showTime_ = Date.now();
  3512.       this.eventTracker_.add(window, 'resize', this.reposition.bind(this));
  3513.     },
  3514.  
  3515.     /**
  3516.      * Handle keyboard and mouse events, dismissing the bubble if necessary.
  3517.      * @param {Event} event The event.
  3518.      */
  3519.     handleEvent: function(event) {
  3520.       BubbleBase.prototype.handleEvent.call(this, event);
  3521.  
  3522.       if (event.type == 'mousedown') {
  3523.         // Dismiss the bubble when the user clicks on the close button.
  3524.         if (event.target == this.querySelector('.bubble-close')) {
  3525.           this.handleCloseEvent_();
  3526.         // Dismiss the bubble when the user clicks outside it after the
  3527.         // specified delay has passed.
  3528.         } else if (!this.contains(event.target) &&
  3529.             Date.now() - this.showTime_ >= this.deactivateToDismissDelay_) {
  3530.           this.hide();
  3531.         }
  3532.       }
  3533.     },
  3534.   };
  3535.  
  3536.   return {
  3537.     ArrowLocation: ArrowLocation,
  3538.     BubbleAlignment: BubbleAlignment,
  3539.     BubbleBase: BubbleBase,
  3540.     Bubble: Bubble
  3541.   };
  3542. });
  3543. </script>
  3544. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3545. // Use of this source code is governed by a BSD-style license that can be
  3546. // found in the LICENSE file.
  3547.  
  3548. /**
  3549.  * @fileoverview Card slider implementation. Allows you to create interactions
  3550.  * that have items that can slide left to right to reveal additional items.
  3551.  * Works by adding the necessary event handlers to a specific DOM structure
  3552.  * including a frame, container and cards.
  3553.  * - The frame defines the boundary of one item. Each card will be expanded to
  3554.  *   fill the width of the frame. This element is also overflow hidden so that
  3555.  *   the additional items left / right do not trigger horizontal scrolling.
  3556.  * - The container is what all the touch events are attached to. This element
  3557.  *   will be expanded to be the width of all cards.
  3558.  * - The cards are the individual viewable items. There should be one card for
  3559.  *   each item in the list. Only one card will be visible at a time. Two cards
  3560.  *   will be visible while you are transitioning between cards.
  3561.  *
  3562.  * This class is designed to work well on any hardware-accelerated touch device.
  3563.  * It should still work on pre-hardware accelerated devices it just won't feel
  3564.  * very good. It should also work well with a mouse.
  3565.  */
  3566.  
  3567. // Use an anonymous function to enable strict mode just for this file (which
  3568. // will be concatenated with other files when embedded in Chrome
  3569. cr.define('cr.ui', function() {
  3570.   'use strict';
  3571.  
  3572.   /**
  3573.    * @constructor
  3574.    * @param {!Element} frame The bounding rectangle that cards are visible in.
  3575.    * @param {!Element} container The surrounding element that will have event
  3576.    *     listeners attached to it.
  3577.    * @param {number} cardWidth The width of each card should have.
  3578.    */
  3579.   function CardSlider(frame, container, cardWidth) {
  3580.     /**
  3581.      * @type {!Element}
  3582.      * @private
  3583.      */
  3584.     this.frame_ = frame;
  3585.  
  3586.     /**
  3587.      * @type {!Element}
  3588.      * @private
  3589.      */
  3590.     this.container_ = container;
  3591.  
  3592.     /**
  3593.      * Array of card elements.
  3594.      * @type {!Array.<!Element>}
  3595.      * @private
  3596.      */
  3597.     this.cards_ = [];
  3598.  
  3599.     /**
  3600.      * Index of currently shown card.
  3601.      * @type {number}
  3602.      * @private
  3603.      */
  3604.     this.currentCard_ = -1;
  3605.  
  3606.     /**
  3607.      * @type {number}
  3608.      * @private
  3609.      */
  3610.     this.cardWidth_ = cardWidth;
  3611.  
  3612.     /**
  3613.      * @type {!cr.ui.TouchHandler}
  3614.      * @private
  3615.      */
  3616.     this.touchHandler_ = new cr.ui.TouchHandler(this.container_);
  3617.   }
  3618.  
  3619.  
  3620.   /**
  3621.    * The time to transition between cards when animating. Measured in ms.
  3622.    * @type {number}
  3623.    * @private
  3624.    * @const
  3625.    */
  3626.   CardSlider.TRANSITION_TIME_ = 200;
  3627.  
  3628.  
  3629.   /**
  3630.    * The minimum velocity required to transition cards if they did not drag past
  3631.    * the halfway point between cards. Measured in pixels / ms.
  3632.    * @type {number}
  3633.    * @private
  3634.    * @const
  3635.    */
  3636.   CardSlider.TRANSITION_VELOCITY_THRESHOLD_ = 0.2;
  3637.  
  3638.  
  3639.   CardSlider.prototype = {
  3640.     /**
  3641.      * The current left offset of the container relative to the frame. This
  3642.      * position does not include deltas from active drag operations, and
  3643.      * always aligns with a frame boundary.
  3644.      * @type {number}
  3645.      * @private
  3646.      */
  3647.     currentLeft_: 0,
  3648.  
  3649.     /**
  3650.      * Current offset relative to |currentLeft_| due to an active drag
  3651.      * operation.
  3652.      * @type {number}
  3653.      * @private
  3654.      */
  3655.     deltaX_: 0,
  3656.  
  3657.     /**
  3658.      * Initialize all elements and event handlers. Must call after construction
  3659.      * and before usage.
  3660.      * @param {boolean} ignoreMouseWheelEvents If true, horizontal mouse wheel
  3661.      *     events will be ignored, rather than flipping between pages.
  3662.      */
  3663.     initialize: function(ignoreMouseWheelEvents) {
  3664.       var view = this.container_.ownerDocument.defaultView;
  3665.       assert(view.getComputedStyle(this.container_).display == '-webkit-box',
  3666.           'Container should be display -webkit-box.');
  3667.       assert(view.getComputedStyle(this.frame_).overflow == 'hidden',
  3668.           'Frame should be overflow hidden.');
  3669.       assert(view.getComputedStyle(this.container_).position == 'static',
  3670.           'Container should be position static.');
  3671.  
  3672.       this.updateCardWidths_();
  3673.  
  3674.       this.mouseWheelScrollAmount_ = 0;
  3675.       this.mouseWheelCardSelected_ = false;
  3676.       this.mouseWheelIsContinuous_ = false;
  3677.       this.scrollClearTimeout_ = null;
  3678.       if (!ignoreMouseWheelEvents) {
  3679.         this.frame_.addEventListener('mousewheel',
  3680.                                      this.onMouseWheel_.bind(this));
  3681.       }
  3682.       this.container_.addEventListener(
  3683.           'webkitTransitionEnd', this.onWebkitTransitionEnd_.bind(this));
  3684.  
  3685.       // Also support touch events in case a touch screen happens to be
  3686.       // available.  Note that this has minimal impact in the common case of
  3687.       // no touch events (eg. we're mainly just adding listeners for events that
  3688.       // will never trigger).
  3689.       var TouchHandler = cr.ui.TouchHandler;
  3690.       this.container_.addEventListener(TouchHandler.EventType.TOUCH_START,
  3691.                                        this.onTouchStart_.bind(this));
  3692.       this.container_.addEventListener(TouchHandler.EventType.DRAG_START,
  3693.                                        this.onDragStart_.bind(this));
  3694.       this.container_.addEventListener(TouchHandler.EventType.DRAG_MOVE,
  3695.                                        this.onDragMove_.bind(this));
  3696.       this.container_.addEventListener(TouchHandler.EventType.DRAG_END,
  3697.                                        this.onDragEnd_.bind(this));
  3698.  
  3699.       this.touchHandler_.enable(/* opt_capture */ false);
  3700.     },
  3701.  
  3702.     /**
  3703.      * Use in cases where the width of the frame has changed in order to update
  3704.      * the width of cards. For example should be used when orientation changes
  3705.      * in full width sliders.
  3706.      * @param {number} newCardWidth Width all cards should have, in pixels.
  3707.      */
  3708.     resize: function(newCardWidth) {
  3709.       if (newCardWidth != this.cardWidth_) {
  3710.         this.cardWidth_ = newCardWidth;
  3711.  
  3712.         this.updateCardWidths_();
  3713.  
  3714.         // Must upate the transform on the container to show the correct card.
  3715.         this.transformToCurrentCard_();
  3716.       }
  3717.     },
  3718.  
  3719.     /**
  3720.      * Sets the cards used. Can be called more than once to switch card sets.
  3721.      * @param {!Array.<!Element>} cards The individual viewable cards.
  3722.      * @param {number} index Index of the card to in the new set of cards to
  3723.      *     navigate to.
  3724.      */
  3725.     setCards: function(cards, index) {
  3726.       assert(index >= 0 && index < cards.length,
  3727.           'Invalid index in CardSlider#setCards');
  3728.       this.cards_ = cards;
  3729.  
  3730.       this.updateCardWidths_();
  3731.       this.updateSelectedCardAttributes_();
  3732.  
  3733.       // Jump to the given card index.
  3734.       this.selectCard(index, false, false, true);
  3735.     },
  3736.  
  3737.     /**
  3738.      * Ensures that for all cards:
  3739.      * - if the card is the current card, then it has 'selected-card' in its
  3740.      *   classList, and is visible for accessibility
  3741.      * - if the card is not the selected card, then it does not have
  3742.      *   'selected-card' in its classList, and is invisible for accessibility.
  3743.      * @private
  3744.      */
  3745.     updateSelectedCardAttributes_: function() {
  3746.       for (var i = 0; i < this.cards_.length; i++) {
  3747.         if (i == this.currentCard_) {
  3748.           this.cards_[i].classList.add('selected-card');
  3749.           this.cards_[i].removeAttribute('aria-hidden');
  3750.         } else {
  3751.           this.cards_[i].classList.remove('selected-card');
  3752.           this.cards_[i].setAttribute('aria-hidden', true);
  3753.         }
  3754.       }
  3755.     },
  3756.  
  3757.     /**
  3758.      * Updates the width of each card.
  3759.      * @private
  3760.      */
  3761.     updateCardWidths_: function() {
  3762.       for (var i = 0, card; card = this.cards_[i]; i++)
  3763.         card.style.width = this.cardWidth_ + 'px';
  3764.     },
  3765.  
  3766.     /**
  3767.      * Returns the index of the current card.
  3768.      * @return {number} index of the current card.
  3769.      */
  3770.     get currentCard() {
  3771.       return this.currentCard_;
  3772.     },
  3773.  
  3774.     /**
  3775.      * Allows setting the current card index.
  3776.      * @param {number} index A new index to set the current index to.
  3777.      * @return {number} The new index after having been set.
  3778.      */
  3779.     set currentCard(index) {
  3780.       return (this.currentCard_ = index);
  3781.     },
  3782.  
  3783.     /**
  3784.      * Returns the number of cards.
  3785.      * @return {number} number of cards.
  3786.      */
  3787.     get cardCount() {
  3788.       return this.cards_.length;
  3789.     },
  3790.  
  3791.     /**
  3792.      * Returns the current card itself.
  3793.      * @return {!Element} the currently shown card.
  3794.      */
  3795.     get currentCardValue() {
  3796.       return this.cards_[this.currentCard_];
  3797.     },
  3798.  
  3799.     /**
  3800.      * Returns the frame holding the cards.
  3801.      * @return {Element} The frame used to position the cards.
  3802.      */
  3803.     get frame() {
  3804.       return this.frame_;
  3805.     },
  3806.  
  3807.     /**
  3808.      * Handle horizontal scrolls to flip between pages.
  3809.      * @private
  3810.      */
  3811.     onMouseWheel_: function(e) {
  3812.       if (e.wheelDeltaX == 0)
  3813.         return;
  3814.  
  3815.       // Continuous devices such as an Apple Touchpad or Apple MagicMouse will
  3816.       // send arbitrary delta values. Conversly, standard mousewheels will
  3817.       // send delta values in increments of 120.  (There is of course a small
  3818.       // chance we mistake a continuous device for a non-continuous device.
  3819.       // Unfortunately there isn't a better way to do this until real touch
  3820.       // events are available to desktop clients.)
  3821.       var DISCRETE_DELTA = 120;
  3822.       if (e.wheelDeltaX % DISCRETE_DELTA)
  3823.         this.mouseWheelIsContinuous_ = true;
  3824.  
  3825.       if (this.mouseWheelIsContinuous_) {
  3826.         // For continuous devices, detect a page swipe when the accumulated
  3827.         // delta matches a pre-defined threshhold.  After changing the page,
  3828.         // ignore wheel events for a short time before repeating this process.
  3829.         if (this.mouseWheelCardSelected_) return;
  3830.         this.mouseWheelScrollAmount_ += e.wheelDeltaX;
  3831.         if (Math.abs(this.mouseWheelScrollAmount_) >= 600) {
  3832.           var pagesToScroll = this.mouseWheelScrollAmount_ > 0 ? 1 : -1;
  3833.           if (!isRTL())
  3834.             pagesToScroll *= -1;
  3835.           var newCardIndex = this.currentCard + pagesToScroll;
  3836.           newCardIndex = Math.min(this.cards_.length - 1,
  3837.                                   Math.max(0, newCardIndex));
  3838.           this.selectCard(newCardIndex, true);
  3839.           this.mouseWheelCardSelected_ = true;
  3840.         }
  3841.       } else {
  3842.         // For discrete devices, consider each wheel tick a page change.
  3843.         var pagesToScroll = e.wheelDeltaX / DISCRETE_DELTA;
  3844.         if (!isRTL())
  3845.           pagesToScroll *= -1;
  3846.         var newCardIndex = this.currentCard + pagesToScroll;
  3847.         newCardIndex = Math.min(this.cards_.length - 1,
  3848.                                 Math.max(0, newCardIndex));
  3849.         this.selectCard(newCardIndex, true);
  3850.       }
  3851.  
  3852.       // We got a mouse wheel event, so cancel any pending scroll wheel timeout.
  3853.       if (this.scrollClearTimeout_ != null)
  3854.         clearTimeout(this.scrollClearTimeout_);
  3855.       // If we didn't use up all the scroll, hold onto it for a little bit, but
  3856.       // drop it after a delay.
  3857.       if (this.mouseWheelScrollAmount_ != 0) {
  3858.         this.scrollClearTimeout_ =
  3859.             setTimeout(this.clearMouseWheelScroll_.bind(this), 500);
  3860.       }
  3861.     },
  3862.  
  3863.     /**
  3864.      * Resets the amount of horizontal scroll we've seen to 0. See
  3865.      * onMouseWheel_.
  3866.      * @private
  3867.      */
  3868.     clearMouseWheelScroll_: function() {
  3869.       this.mouseWheelScrollAmount_ = 0;
  3870.       this.mouseWheelCardSelected_ = false;
  3871.     },
  3872.  
  3873.     /**
  3874.      * Handles the ends of -webkit-transitions on -webkit-transform (animated
  3875.      * card switches).
  3876.      * @param {Event} e The webkitTransitionEnd event.
  3877.      * @private
  3878.      */
  3879.     onWebkitTransitionEnd_: function(e) {
  3880.       // Ignore irrelevant transitions that might bubble up.
  3881.       if (e.target !== this.container_ ||
  3882.           e.propertyName != '-webkit-transform') {
  3883.         return;
  3884.       }
  3885.       this.fireChangeEndedEvent_(true);
  3886.     },
  3887.  
  3888.     /**
  3889.      * Dispatches a simple event to tell subscribers we're done moving to the
  3890.      * newly selected card.
  3891.      * @param {boolean} wasAnimated whether or not the change was animated.
  3892.      * @private
  3893.      */
  3894.     fireChangeEndedEvent_: function(wasAnimated) {
  3895.       var e = document.createEvent('Event');
  3896.       e.initEvent('cardSlider:card_change_ended', true, true);
  3897.       e.cardSlider = this;
  3898.       e.changedTo = this.currentCard_;
  3899.       e.wasAnimated = wasAnimated;
  3900.       this.container_.dispatchEvent(e);
  3901.     },
  3902.  
  3903.     /**
  3904.      * Add a card to the card slider at a particular index. If the card being
  3905.      * added is inserted in front of the current card, cardSlider.currentCard
  3906.      * will be adjusted accordingly (to current card + 1).
  3907.      * @param {!Node} card A card that will be added to the card slider.
  3908.      * @param {number} index An index at which the given |card| should be
  3909.      *     inserted. Must be positive and less than the number of cards.
  3910.      */
  3911.     addCardAtIndex: function(card, index) {
  3912.       assert(card instanceof Node, '|card| isn\'t a Node');
  3913.       this.assertValidIndex_(index);
  3914.       this.cards_ = Array.prototype.concat.call(
  3915.           this.cards_.slice(0, index), card, this.cards_.slice(index));
  3916.  
  3917.       this.updateSelectedCardAttributes_();
  3918.  
  3919.       if (this.currentCard_ == -1)
  3920.         this.currentCard_ = 0;
  3921.       else if (index <= this.currentCard_)
  3922.         this.selectCard(this.currentCard_ + 1, false, true, true);
  3923.  
  3924.       this.fireAddedEvent_(card, index);
  3925.     },
  3926.  
  3927.     /**
  3928.      * Append a card to the end of the list.
  3929.      * @param {!Node} card A card to add at the end of the card slider.
  3930.      */
  3931.     appendCard: function(card) {
  3932.       assert(card instanceof Node, '|card| isn\'t a Node');
  3933.       this.cards_.push(card);
  3934.       this.fireAddedEvent_(card, this.cards_.length - 1);
  3935.     },
  3936.  
  3937.     /**
  3938.      * Dispatches a simple event to tell interested subscribers that a card was
  3939.      * added to this card slider.
  3940.      * @param {Node} card The recently added card.
  3941.      * @param {number} index The position of the newly added card.
  3942.      * @private
  3943.      */
  3944.     fireAddedEvent_: function(card, index) {
  3945.       this.assertValidIndex_(index);
  3946.       var e = document.createEvent('Event');
  3947.       e.initEvent('cardSlider:card_added', true, true);
  3948.       e.addedIndex = index;
  3949.       e.addedCard = card;
  3950.       this.container_.dispatchEvent(e);
  3951.     },
  3952.  
  3953.     /**
  3954.      * Returns the card at a particular index.
  3955.      * @param {number} index The index of the card to return.
  3956.      * @return {!Element} The card at the given index.
  3957.      */
  3958.     getCardAtIndex: function(index) {
  3959.       this.assertValidIndex_(index);
  3960.       return this.cards_[index];
  3961.     },
  3962.  
  3963.     /**
  3964.      * Removes a card by index from the card slider. If the card to be removed
  3965.      * is the current card or in front of the current card, the current card
  3966.      * will be updated (to current card - 1).
  3967.      * @param {!Node} card A card to be removed.
  3968.      */
  3969.     removeCard: function(card) {
  3970.       assert(card instanceof Node, '|card| isn\'t a Node');
  3971.       this.removeCardAtIndex(this.cards_.indexOf(card));
  3972.     },
  3973.  
  3974.     /**
  3975.      * Removes a card by index from the card slider. If the card to be removed
  3976.      * is the current card or in front of the current card, the current card
  3977.      * will be updated (to current card - 1).
  3978.      * @param {number} index The index of the tile that should be removed.
  3979.      */
  3980.     removeCardAtIndex: function(index) {
  3981.       this.assertValidIndex_(index);
  3982.       var removed = this.cards_.splice(index, 1).pop();
  3983.  
  3984.       if (this.cards_.length == 0)
  3985.         this.currentCard_ = -1;
  3986.       else if (index < this.currentCard_)
  3987.         this.selectCard(this.currentCard_ - 1, false, true);
  3988.  
  3989.       this.fireRemovedEvent_(removed, index);
  3990.     },
  3991.  
  3992.     /**
  3993.      * Dispatches a cardSlider:card_removed event so interested subscribers know
  3994.      * when a card was removed from this card slider.
  3995.      * @param {Node} card The recently removed card.
  3996.      * @param {number} index The index of the card before it was removed.
  3997.      * @private
  3998.      */
  3999.     fireRemovedEvent_: function(card, index) {
  4000.       var e = document.createEvent('Event');
  4001.       e.initEvent('cardSlider:card_removed', true, true);
  4002.       e.removedCard = card;
  4003.       e.removedIndex = index;
  4004.       this.container_.dispatchEvent(e);
  4005.     },
  4006.  
  4007.     /**
  4008.      * This re-syncs the -webkit-transform that's used to position the frame in
  4009.      * the likely event it needs to be updated by a card being inserted or
  4010.      * removed in the flow.
  4011.      */
  4012.     repositionFrame: function() {
  4013.       this.transformToCurrentCard_();
  4014.     },
  4015.  
  4016.     /**
  4017.      * Checks the the given |index| exists in this.cards_.
  4018.      * @param {number} index An index to check.
  4019.      * @private
  4020.      */
  4021.     assertValidIndex_: function(index) {
  4022.       assert(index >= 0 && index < this.cards_.length);
  4023.     },
  4024.  
  4025.     /**
  4026.      * Selects a new card, ensuring that it is a valid index, transforming the
  4027.      * view and possibly calling the change card callback.
  4028.      * @param {number} newCardIndex Index of card to show.
  4029.      * @param {boolean=} opt_animate If true will animate transition from
  4030.      *     current position to new position.
  4031.      * @param {boolean=} opt_dontNotify If true, don't tell subscribers that
  4032.      *     we've changed cards.
  4033.      * @param {boolean=} opt_forceChange If true, ignore if the card already
  4034.      *     selected.
  4035.      */
  4036.     selectCard: function(newCardIndex,
  4037.                          opt_animate,
  4038.                          opt_dontNotify,
  4039.                          opt_forceChange) {
  4040.       this.assertValidIndex_(newCardIndex);
  4041.  
  4042.       var previousCard = this.currentCardValue;
  4043.       var isChangingCard =
  4044.           !this.cards_[newCardIndex].classList.contains('selected-card');
  4045.  
  4046.       if (typeof opt_forceChange != 'undefined' && opt_forceChange)
  4047.         isChangingCard = true;
  4048.  
  4049.       if (isChangingCard) {
  4050.         this.currentCard_ = newCardIndex;
  4051.         this.updateSelectedCardAttributes_();
  4052.       }
  4053.  
  4054.       var willTransitionHappen = this.transformToCurrentCard_(opt_animate);
  4055.  
  4056.       if (isChangingCard && !opt_dontNotify) {
  4057.         var event = document.createEvent('Event');
  4058.         event.initEvent('cardSlider:card_changed', true, true);
  4059.         event.cardSlider = this;
  4060.         event.wasAnimated = !!opt_animate;
  4061.         this.container_.dispatchEvent(event);
  4062.  
  4063.         // We also dispatch an event on the cards themselves.
  4064.         if (previousCard) {
  4065.           cr.dispatchSimpleEvent(previousCard, 'carddeselected',
  4066.                                  true, true);
  4067.         }
  4068.         cr.dispatchSimpleEvent(this.currentCardValue, 'cardselected',
  4069.                                true, true);
  4070.       }
  4071.  
  4072.       // If we're not changing, animated, or transitioning, fire a
  4073.       // cardSlider:card_change_ended event right away.
  4074.       if ((!isChangingCard || !opt_animate || !willTransitionHappen) &&
  4075.           !opt_dontNotify) {
  4076.         this.fireChangeEndedEvent_(false);
  4077.       }
  4078.     },
  4079.  
  4080.     /**
  4081.      * Selects a card from the stack. Passes through to selectCard.
  4082.      * @param {Node} newCard The card that should be selected.
  4083.      * @param {boolean=} opt_animate Whether to animate.
  4084.      */
  4085.     selectCardByValue: function(newCard, opt_animate) {
  4086.       var i = this.cards_.indexOf(newCard);
  4087.       assert(i != -1);
  4088.       this.selectCard(i, opt_animate);
  4089.     },
  4090.  
  4091.     /**
  4092.      * Centers the view on the card denoted by this.currentCard. Can either
  4093.      * animate to that card or snap to it.
  4094.      * @param {boolean=} opt_animate If true will animate transition from
  4095.      *     current position to new position.
  4096.      * @return {boolean} Whether or not a transformation was necessary.
  4097.      * @private
  4098.      */
  4099.     transformToCurrentCard_: function(opt_animate) {
  4100.       var prevLeft = this.currentLeft_;
  4101.       this.currentLeft_ = -this.cardWidth_ *
  4102.           (isRTL() ? this.cards_.length - this.currentCard - 1 :
  4103.                      this.currentCard);
  4104.  
  4105.       // If there's no change, return something to let the caller know there
  4106.       // won't be a transition occuring.
  4107.       if (prevLeft == this.currentLeft_ && this.deltaX_ == 0)
  4108.         return false;
  4109.  
  4110.       // Animate to the current card, which will either transition if the
  4111.       // current card is new, or reset the existing card if we didn't drag
  4112.       // enough to change cards.
  4113.       var transition = '';
  4114.       if (opt_animate) {
  4115.         transition = '-webkit-transform ' + CardSlider.TRANSITION_TIME_ +
  4116.                      'ms ease-in-out';
  4117.       }
  4118.       this.container_.style.WebkitTransition = transition;
  4119.       this.translateTo_(this.currentLeft_);
  4120.  
  4121.       return true;
  4122.     },
  4123.  
  4124.     /**
  4125.      * Moves the view to the specified position.
  4126.      * @param {number} x Horizontal position to move to.
  4127.      * @private
  4128.      */
  4129.     translateTo_: function(x) {
  4130.       // We use a webkitTransform to slide because this is GPU accelerated on
  4131.       // Chrome and iOS.  Once Chrome does GPU acceleration on the position
  4132.       // fixed-layout elements we could simply set the element's position to
  4133.       // fixed and modify 'left' instead.
  4134.       this.deltaX_ = x - this.currentLeft_;
  4135.       this.container_.style.WebkitTransform = 'translate3d(' + x + 'px, 0, 0)';
  4136.     },
  4137.  
  4138.     /* Touch ******************************************************************/
  4139.  
  4140.     /**
  4141.      * Clear any transition that is in progress and enable dragging for the
  4142.      * touch.
  4143.      * @param {!cr.ui.TouchHandler.Event} e The TouchHandler event.
  4144.      * @private
  4145.      */
  4146.     onTouchStart_: function(e) {
  4147.       this.container_.style.WebkitTransition = '';
  4148.       e.enableDrag = true;
  4149.     },
  4150.  
  4151.     /**
  4152.      * Tell the TouchHandler that dragging is acceptable when the user begins by
  4153.      * scrolling horizontally and there is more than one card to slide.
  4154.      * @param {!cr.ui.TouchHandler.Event} e The TouchHandler event.
  4155.      * @private
  4156.      */
  4157.     onDragStart_: function(e) {
  4158.       e.enableDrag = this.cardCount > 1 && Math.abs(e.dragDeltaX) >
  4159.           Math.abs(e.dragDeltaY);
  4160.     },
  4161.  
  4162.     /**
  4163.      * On each drag move event reposition the container appropriately so the
  4164.      * cards look like they are sliding.
  4165.      * @param {!cr.ui.TouchHandler.Event} e The TouchHandler event.
  4166.      * @private
  4167.      */
  4168.     onDragMove_: function(e) {
  4169.       var deltaX = e.dragDeltaX;
  4170.       // If dragging beyond the first or last card then apply a backoff so the
  4171.       // dragging feels stickier than usual.
  4172.       if (!this.currentCard && deltaX > 0 ||
  4173.           this.currentCard == (this.cards_.length - 1) && deltaX < 0) {
  4174.         deltaX /= 2;
  4175.       }
  4176.       this.translateTo_(this.currentLeft_ + deltaX);
  4177.     },
  4178.  
  4179.     /**
  4180.      * On drag end events we may want to transition to another card, depending
  4181.      * on the ending position of the drag and the velocity of the drag.
  4182.      * @param {!cr.ui.TouchHandler.Event} e The TouchHandler event.
  4183.      * @private
  4184.      */
  4185.     onDragEnd_: function(e) {
  4186.       var deltaX = e.dragDeltaX;
  4187.       var velocity = this.touchHandler_.getEndVelocity().x;
  4188.       var newX = this.currentLeft_ + deltaX;
  4189.       var newCardIndex = Math.round(-newX / this.cardWidth_);
  4190.  
  4191.       if (newCardIndex == this.currentCard && Math.abs(velocity) >
  4192.           CardSlider.TRANSITION_VELOCITY_THRESHOLD_) {
  4193.         // The drag wasn't far enough to change cards but the velocity was
  4194.         // high enough to transition anyways. If the velocity is to the left
  4195.         // (negative) then the user wishes to go right (card + 1).
  4196.         newCardIndex += velocity > 0 ? -1 : 1;
  4197.       }
  4198.       // Ensure that the new card index is valid.  The new card index could be
  4199.       // invalid if a swipe suggests scrolling off the end of the list of
  4200.       // cards.
  4201.       if (newCardIndex < 0)
  4202.         newCardIndex = 0;
  4203.       else if (newCardIndex >= this.cardCount)
  4204.         newCardIndex = this.cardCount - 1;
  4205.       this.selectCard(newCardIndex, /* animate */ true);
  4206.     },
  4207.  
  4208.     /**
  4209.      * Cancel any current touch/slide as if we saw a touch end
  4210.      */
  4211.     cancelTouch: function() {
  4212.       // Stop listening to any current touch
  4213.       this.touchHandler_.cancelTouch();
  4214.  
  4215.       // Ensure we're at a card bounary
  4216.       this.transformToCurrentCard_(true);
  4217.     },
  4218.   };
  4219.  
  4220.   return {
  4221.     CardSlider: CardSlider
  4222.   };
  4223. });
  4224. </script>
  4225. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4226. // Use of this source code is governed by a BSD-style license that can be
  4227. // found in the LICENSE file.
  4228.  
  4229. cr.define('cr.ui', function() {
  4230.  
  4231.   /** @const */ var Menu = cr.ui.Menu;
  4232.  
  4233.   /**
  4234.    * Handles context menus.
  4235.    * @constructor
  4236.    */
  4237.   function ContextMenuHandler() {}
  4238.  
  4239.   ContextMenuHandler.prototype = {
  4240.  
  4241.     /**
  4242.      * The menu that we are currently showing.
  4243.      * @type {cr.ui.Menu}
  4244.      */
  4245.     menu_: null,
  4246.     get menu() {
  4247.       return this.menu_;
  4248.     },
  4249.  
  4250.     /**
  4251.      * Shows a menu as a context menu.
  4252.      * @param {!Event} e The event triggering the show (usually a contextmenu
  4253.      *     event).
  4254.      * @param {!cr.ui.Menu} menu The menu to show.
  4255.      */
  4256.     showMenu: function(e, menu) {
  4257.       this.menu_ = menu;
  4258.       menu.updateCommands(e.currentTarget);
  4259.       menu.hidden = false;
  4260.       menu.contextElement = e.currentTarget;
  4261.  
  4262.       // when the menu is shown we steal all keyboard events.
  4263.       var doc = menu.ownerDocument;
  4264.       doc.addEventListener('keydown', this, true);
  4265.       doc.addEventListener('mousedown', this, true);
  4266.       // Note: this should be listening for focus, as in menu_button.js, but
  4267.       // as per crbug.com/162190 this indirectly causes odd behaviour.
  4268.       // Since the context menu is currently not keyboard-accessible, blur
  4269.       // is sufficient for now.
  4270.       doc.addEventListener('blur', this, true);
  4271.       doc.defaultView.addEventListener('resize', this);
  4272.       menu.addEventListener('contextmenu', this);
  4273.       menu.addEventListener('activate', this);
  4274.       this.positionMenu_(e, menu);
  4275.     },
  4276.  
  4277.     /**
  4278.      * Hide the currently shown menu.
  4279.      */
  4280.     hideMenu: function() {
  4281.       var menu = this.menu;
  4282.       if (!menu)
  4283.         return;
  4284.  
  4285.       menu.hidden = true;
  4286.       menu.contextElement = null;
  4287.       var doc = menu.ownerDocument;
  4288.       doc.removeEventListener('keydown', this, true);
  4289.       doc.removeEventListener('mousedown', this, true);
  4290.       doc.removeEventListener('blur', this, true);
  4291.       doc.defaultView.removeEventListener('resize', this);
  4292.       menu.removeEventListener('contextmenu', this);
  4293.       menu.removeEventListener('activate', this);
  4294.       menu.selectedIndex = -1;
  4295.       this.menu_ = null;
  4296.  
  4297.       // On windows we might hide the menu in a right mouse button up and if
  4298.       // that is the case we wait some short period before we allow the menu
  4299.       // to be shown again.
  4300.       this.hideTimestamp_ = cr.isWindows ? Date.now() : 0;
  4301.     },
  4302.  
  4303.     /**
  4304.      * Positions the menu
  4305.      * @param {!Event} e The event object triggering the showing.
  4306.      * @param {!cr.ui.Menu} menu The menu to position.
  4307.      * @private
  4308.      */
  4309.     positionMenu_: function(e, menu) {
  4310.       // TODO(arv): Handle scrolled documents when needed.
  4311.  
  4312.       var element = e.currentTarget;
  4313.       var x, y;
  4314.       // When the user presses the context menu key (on the keyboard) we need
  4315.       // to detect this.
  4316.       if (this.keyIsDown_) {
  4317.         var rect = element.getRectForContextMenu ?
  4318.                        element.getRectForContextMenu() :
  4319.                        element.getBoundingClientRect();
  4320.         var offset = Math.min(rect.width, rect.height) / 2;
  4321.         x = rect.left + offset;
  4322.         y = rect.top + offset;
  4323.       } else {
  4324.         x = e.clientX;
  4325.         y = e.clientY;
  4326.       }
  4327.  
  4328.       cr.ui.positionPopupAtPoint(x, y, menu);
  4329.     },
  4330.  
  4331.     /**
  4332.      * Handles event callbacks.
  4333.      * @param {!Event} e The event object.
  4334.      */
  4335.     handleEvent: function(e) {
  4336.       // Keep track of keydown state so that we can use that to determine the
  4337.       // reason for the contextmenu event.
  4338.       switch (e.type) {
  4339.         case 'keydown':
  4340.           this.keyIsDown_ = !e.ctrlKey && !e.altKey &&
  4341.               // context menu key or Shift-F10
  4342.               (e.keyCode == 93 && !e.shiftKey ||
  4343.                e.keyIdentifier == 'F10' && e.shiftKey);
  4344.           break;
  4345.  
  4346.         case 'keyup':
  4347.           this.keyIsDown_ = false;
  4348.           break;
  4349.       }
  4350.  
  4351.       // Context menu is handled even when we have no menu.
  4352.       if (e.type != 'contextmenu' && !this.menu)
  4353.         return;
  4354.  
  4355.       switch (e.type) {
  4356.         case 'mousedown':
  4357.           if (!this.menu.contains(e.target))
  4358.             this.hideMenu();
  4359.           else
  4360.             e.preventDefault();
  4361.           break;
  4362.         case 'keydown':
  4363.           // keyIdentifier does not report 'Esc' correctly
  4364.           if (e.keyCode == 27 /* Esc */) {
  4365.             this.hideMenu();
  4366.             e.stopPropagation();
  4367.             e.preventDefault();
  4368.  
  4369.           // If the menu is visible we let it handle all the keyboard events.
  4370.           } else if (this.menu) {
  4371.             this.menu.handleKeyDown(e);
  4372.             e.preventDefault();
  4373.             e.stopPropagation();
  4374.           }
  4375.           break;
  4376.  
  4377.         case 'activate':
  4378.         case 'blur':
  4379.         case 'resize':
  4380.           this.hideMenu();
  4381.           break;
  4382.  
  4383.         case 'contextmenu':
  4384.           if ((!this.menu || !this.menu.contains(e.target)) &&
  4385.               (!this.hideTimestamp_ || Date.now() - this.hideTimestamp_ > 50))
  4386.             this.showMenu(e, e.currentTarget.contextMenu);
  4387.           e.preventDefault();
  4388.           // Don't allow elements further up in the DOM to show their menus.
  4389.           e.stopPropagation();
  4390.           break;
  4391.       }
  4392.     },
  4393.  
  4394.     /**
  4395.      * Adds a contextMenu property to an element or element class.
  4396.      * @param {!Element|!Function} element The element or class to add the
  4397.      *     contextMenu property to.
  4398.      */
  4399.     addContextMenuProperty: function(element) {
  4400.       if (typeof element == 'function')
  4401.         element = element.prototype;
  4402.  
  4403.       element.__defineGetter__('contextMenu', function() {
  4404.         return this.contextMenu_;
  4405.       });
  4406.       element.__defineSetter__('contextMenu', function(menu) {
  4407.         var oldContextMenu = this.contextMenu;
  4408.  
  4409.         if (typeof menu == 'string' && menu[0] == '#') {
  4410.           menu = this.ownerDocument.getElementById(menu.slice(1));
  4411.           cr.ui.decorate(menu, Menu);
  4412.         }
  4413.  
  4414.         if (menu === oldContextMenu)
  4415.           return;
  4416.  
  4417.         if (oldContextMenu && !menu) {
  4418.           this.removeEventListener('contextmenu', contextMenuHandler);
  4419.           this.removeEventListener('keydown', contextMenuHandler);
  4420.           this.removeEventListener('keyup', contextMenuHandler);
  4421.         }
  4422.         if (menu && !oldContextMenu) {
  4423.           this.addEventListener('contextmenu', contextMenuHandler);
  4424.           this.addEventListener('keydown', contextMenuHandler);
  4425.           this.addEventListener('keyup', contextMenuHandler);
  4426.         }
  4427.  
  4428.         this.contextMenu_ = menu;
  4429.  
  4430.         if (menu && menu.id)
  4431.           this.setAttribute('contextmenu', '#' + menu.id);
  4432.  
  4433.         cr.dispatchPropertyChange(this, 'contextMenu', menu, oldContextMenu);
  4434.       });
  4435.  
  4436.       if (!element.getRectForContextMenu) {
  4437.         /**
  4438.          * @return {!ClientRect} The rect to use for positioning the context
  4439.          *     menu when the context menu is not opened using a mouse position.
  4440.          */
  4441.         element.getRectForContextMenu = function() {
  4442.           return this.getBoundingClientRect();
  4443.         };
  4444.       }
  4445.     },
  4446.  
  4447.     /**
  4448.      * Sets the given contextMenu to the given element. A contextMenu property
  4449.      * would be added if necessary.
  4450.      * @param {!Element} element The element or class to set the contextMenu to.
  4451.      * @param {!cr.ui.Menu} contextMenu The contextMenu property to be set.
  4452.      */
  4453.     setContextMenu: function(element, contextMenu) {
  4454.       if (!element.contextMenu)
  4455.         this.addContextMenuProperty(element);
  4456.       element.contextMenu = contextMenu;
  4457.     }
  4458.   };
  4459.  
  4460.   /**
  4461.    * The singleton context menu handler.
  4462.    * @type {!ContextMenuHandler}
  4463.    */
  4464.   var contextMenuHandler = new ContextMenuHandler;
  4465.  
  4466.   // Export
  4467.   return {
  4468.     contextMenuHandler: contextMenuHandler
  4469.   };
  4470. });
  4471. </script>
  4472. <script>// Copyright (c) 2011 The Chromium Authors. All rights reserved.
  4473. // Use of this source code is governed by a BSD-style license that can be
  4474. // found in the LICENSE file.
  4475.  
  4476. /**
  4477.  * @fileoverview DragWrapper
  4478.  * A class for simplifying HTML5 drag and drop. Classes should use this to
  4479.  * handle the nitty gritty of nested drag enters and leaves.
  4480.  */
  4481. cr.define('cr.ui', function() {
  4482.   /**
  4483.    * Creates a DragWrapper which listens for drag target events on |target| and
  4484.    * delegates event handling to |handler|. The |handler| must implement:
  4485.    *   shouldAcceptDrag
  4486.    *   doDragEnter
  4487.    *   doDragLeave
  4488.    *   doDragOver
  4489.    *   doDrop
  4490.    */
  4491.   function DragWrapper(target, handler) {
  4492.     this.initialize(target, handler);
  4493.   }
  4494.  
  4495.   DragWrapper.prototype = {
  4496.     initialize: function(target, handler) {
  4497.       target.addEventListener('dragenter',
  4498.                               this.onDragEnter_.bind(this));
  4499.       target.addEventListener('dragover', this.onDragOver_.bind(this));
  4500.       target.addEventListener('drop', this.onDrop_.bind(this));
  4501.       target.addEventListener('dragleave', this.onDragLeave_.bind(this));
  4502.  
  4503.       this.target_ = target;
  4504.       this.handler_ = handler;
  4505.     },
  4506.  
  4507.     /**
  4508.      * The number of un-paired dragenter events that have fired on |this|. This
  4509.      * is incremented by |onDragEnter_| and decremented by |onDragLeave_|. This
  4510.      * is necessary because dragging over child widgets will fire additional
  4511.      * enter and leave events on |this|. A non-zero value does not necessarily
  4512.      * indicate that |isCurrentDragTarget()| is true.
  4513.      * @type {number}
  4514.      * @private
  4515.      */
  4516.     dragEnters_: 0,
  4517.  
  4518.     /**
  4519.      * Whether the tile page is currently being dragged over with data it can
  4520.      * accept.
  4521.      * @type {boolean}
  4522.      */
  4523.     get isCurrentDragTarget() {
  4524.       return this.target_.classList.contains('drag-target');
  4525.     },
  4526.  
  4527.     /**
  4528.      * Handler for dragenter events fired on |target_|.
  4529.      * @param {Event} e A MouseEvent for the drag.
  4530.      * @private
  4531.      */
  4532.     onDragEnter_: function(e) {
  4533.       if (++this.dragEnters_ == 1) {
  4534.         if (this.handler_.shouldAcceptDrag(e)) {
  4535.           this.target_.classList.add('drag-target');
  4536.           this.handler_.doDragEnter(e);
  4537.         }
  4538.       } else {
  4539.         // Sometimes we'll get an enter event over a child element without an
  4540.         // over event following it. In this case we have to still call the
  4541.         // drag over handler so that we make the necessary updates (one visible
  4542.         // symptom of not doing this is that the cursor's drag state will
  4543.         // flicker during drags).
  4544.         this.onDragOver_(e);
  4545.       }
  4546.     },
  4547.  
  4548.     /**
  4549.      * Thunk for dragover events fired on |target_|.
  4550.      * @param {Event} e A MouseEvent for the drag.
  4551.      * @private
  4552.      */
  4553.     onDragOver_: function(e) {
  4554.       if (!this.target_.classList.contains('drag-target'))
  4555.         return;
  4556.       this.handler_.doDragOver(e);
  4557.     },
  4558.  
  4559.     /**
  4560.      * Thunk for drop events fired on |target_|.
  4561.      * @param {Event} e A MouseEvent for the drag.
  4562.      * @private
  4563.      */
  4564.     onDrop_: function(e) {
  4565.       this.dragEnters_ = 0;
  4566.       if (!this.target_.classList.contains('drag-target'))
  4567.         return;
  4568.       this.target_.classList.remove('drag-target');
  4569.       this.handler_.doDrop(e);
  4570.     },
  4571.  
  4572.     /**
  4573.      * Thunk for dragleave events fired on |target_|.
  4574.      * @param {Event} e A MouseEvent for the drag.
  4575.      * @private
  4576.      */
  4577.     onDragLeave_: function(e) {
  4578.       if (--this.dragEnters_ > 0)
  4579.         return;
  4580.  
  4581.       this.target_.classList.remove('drag-target');
  4582.       this.handler_.doDragLeave(e);
  4583.     },
  4584.   };
  4585.  
  4586.   return {
  4587.     DragWrapper: DragWrapper
  4588.   };
  4589. });
  4590. </script>
  4591. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4592. // Use of this source code is governed by a BSD-style license that can be
  4593. // found in the LICENSE file.
  4594.  
  4595. // require: event_tracker.js
  4596.  
  4597. cr.define('cr.ui', function() {
  4598.   'use strict';
  4599.  
  4600.   /**
  4601.    * ExpandableBubble is a free-floating compact informational bubble with an
  4602.    * arrow that points at a place of interest on the page. When clicked, the
  4603.    * bubble expands to show more of its content. Width of the bubble is the
  4604.    * width of the node it is overlapping when unexpanded. Expanded, it is of a
  4605.    * fixed width, but variable height. Currently the arrow is always positioned
  4606.    * at the bottom right and points down.
  4607.    * @constructor
  4608.    * @extends {cr.ui.div}
  4609.    */
  4610.   var ExpandableBubble = cr.ui.define('div');
  4611.  
  4612.   ExpandableBubble.prototype = {
  4613.     __proto__: HTMLDivElement.prototype,
  4614.  
  4615.     /** @override */
  4616.     decorate: function() {
  4617.       this.className = 'expandable-bubble';
  4618.       this.innerHTML =
  4619.           '<div class="expandable-bubble-contents">' +
  4620.             '<div class="expandable-bubble-title"></div>' +
  4621.             '<div class="expandable-bubble-main" hidden></div>' +
  4622.           '</div>' +
  4623.           '<div class="expandable-bubble-close" hidden></div>';
  4624.  
  4625.       this.hidden = true;
  4626.       this.bubbleSuppressed = false;
  4627.       this.handleCloseEvent = this.hide;
  4628.     },
  4629.  
  4630.     /**
  4631.      * Sets the title of the bubble. The title is always visible when the
  4632.      * bubble is visible.
  4633.      * @type {Node} An HTML element to set as the title.
  4634.      */
  4635.     set contentTitle(node) {
  4636.       var bubbleTitle = this.querySelector('.expandable-bubble-title');
  4637.       bubbleTitle.textContent = '';
  4638.       bubbleTitle.appendChild(node);
  4639.     },
  4640.  
  4641.     /**
  4642.      * Sets the content node of the bubble. The content node is only visible
  4643.      * when the bubble is expanded.
  4644.      * @param {Node} An HTML element.
  4645.      */
  4646.     set content(node) {
  4647.       var bubbleMain = this.querySelector('.expandable-bubble-main');
  4648.       bubbleMain.textContent = '';
  4649.       bubbleMain.appendChild(node);
  4650.     },
  4651.  
  4652.     /**
  4653.      * Sets the anchor node, i.e. the node that this bubble points at and
  4654.      * partially overlaps.
  4655.      * @param {HTMLElement} node The new anchor node.
  4656.      */
  4657.     set anchorNode(node) {
  4658.       this.anchorNode_ = node;
  4659.  
  4660.       if (!this.hidden)
  4661.         this.resizeAndReposition();
  4662.     },
  4663.  
  4664.     /**
  4665.      * Handles the close event which is triggered when the close button
  4666.      * is clicked. By default is set to this.hide.
  4667.      * @param {function} A function with no parameters
  4668.      */
  4669.     set handleCloseEvent(func) {
  4670.       this.handleCloseEvent_ = func;
  4671.     },
  4672.  
  4673.     /**
  4674.      * Temporarily suppresses the bubble from view (and toggles it back).
  4675.      * 'Suppressed' and 'hidden' are two bubble states that both indicate that
  4676.      * the bubble should not be visible, but when you 'un-suppress' a bubble,
  4677.      * only a suppressed bubble becomes visible. This can be handy, for example,
  4678.      * if the user switches away from the app card (then we need to know which
  4679.      * bubbles to show (only the suppressed ones, not the hidden ones). Hiding
  4680.      * and un-hiding a bubble overrides the suppressed state (a bubble cannot
  4681.      * be suppressed but not hidden).
  4682.      */
  4683.     set suppressed(suppress) {
  4684.       if (suppress) {
  4685.         // If the bubble is already hidden, then we don't need to suppress it.
  4686.         if (this.hidden)
  4687.           return;
  4688.  
  4689.         this.hidden = true;
  4690.       } else if (this.bubbleSuppressed) {
  4691.         this.hidden = false;
  4692.       }
  4693.       this.bubbleSuppressed = suppress;
  4694.       this.resizeAndReposition(this);
  4695.     },
  4696.  
  4697.     /**
  4698.      * Updates the position of the bubble.
  4699.      * @private
  4700.      */
  4701.     reposition_: function() {
  4702.       var clientRect = this.anchorNode_.getBoundingClientRect();
  4703.  
  4704.       // Center bubble in collapsed mode (if it doesn't take up all the room we
  4705.       // have).
  4706.       var offset = 0;
  4707.       if (!this.expanded)
  4708.         offset = (clientRect.width - parseInt(this.style.width)) / 2;
  4709.       this.style.left = this.style.right = clientRect.left + offset + 'px';
  4710.  
  4711.       var top = Math.max(0, clientRect.top - 4);
  4712.       this.style.top = this.expanded ?
  4713.           (top - this.offsetHeight + this.unexpandedHeight) + 'px' :
  4714.           top + 'px';
  4715.     },
  4716.  
  4717.     /**
  4718.      * Resizes the bubble and then repositions it.
  4719.      * @private
  4720.      */
  4721.     resizeAndReposition: function() {
  4722.       var clientRect = this.anchorNode_.getBoundingClientRect();
  4723.       var width = clientRect.width;
  4724.  
  4725.       var bubbleTitle = this.querySelector('.expandable-bubble-title');
  4726.       var closeElement = this.querySelector('.expandable-bubble-close');
  4727.       var closeWidth = this.expanded ? closeElement.clientWidth : 0;
  4728.       var margin = 15;
  4729.  
  4730.       // Suppress the width style so we can get it to calculate its width.
  4731.       // We'll set the right width again when we are done.
  4732.       bubbleTitle.style.width = '';
  4733.  
  4734.       if (this.expanded) {
  4735.         // We always show the full title but never show less width than 250
  4736.         // pixels.
  4737.         var expandedWidth =
  4738.             Math.max(250, bubbleTitle.scrollWidth + closeWidth + margin);
  4739.         this.style.marginLeft = (width - expandedWidth) + 'px';
  4740.         width = expandedWidth;
  4741.       } else {
  4742.         var newWidth = Math.min(bubbleTitle.scrollWidth + margin, width);
  4743.         // If we've maxed out in width then apply the mask.
  4744.         this.masked = newWidth == width;
  4745.         width = newWidth;
  4746.         this.style.marginLeft = '0';
  4747.       }
  4748.  
  4749.       // Width is determined by the width of the title (when not expanded) but
  4750.       // capped to the width of the anchor node.
  4751.       this.style.width = width + 'px';
  4752.       bubbleTitle.style.width = Math.max(0, width - margin - closeWidth) + 'px';
  4753.  
  4754.       // Also reposition the bubble -- dimensions have potentially changed.
  4755.       this.reposition_();
  4756.     },
  4757.  
  4758.     /*
  4759.      * Expand the bubble (bringing the full content into view).
  4760.      * @private
  4761.      */
  4762.     expandBubble_: function() {
  4763.       this.querySelector('.expandable-bubble-main').hidden = false;
  4764.       this.querySelector('.expandable-bubble-close').hidden = false;
  4765.       this.expanded = true;
  4766.       this.resizeAndReposition();
  4767.     },
  4768.  
  4769.     /**
  4770.      * Collapse the bubble, hiding the main content and the close button.
  4771.      * This is automatically called when the window is resized.
  4772.      * @private
  4773.      */
  4774.     collapseBubble_: function() {
  4775.       this.querySelector('.expandable-bubble-main').hidden = true;
  4776.       this.querySelector('.expandable-bubble-close').hidden = true;
  4777.       this.expanded = false;
  4778.       this.resizeAndReposition();
  4779.     },
  4780.  
  4781.     /**
  4782.      * The onclick handler for the notification (expands the bubble).
  4783.      * @param {Event} e The event.
  4784.      * @private
  4785.      */
  4786.     onNotificationClick_: function(e) {
  4787.       if (!this.contains(e.target))
  4788.         return;
  4789.  
  4790.       if (!this.expanded) {
  4791.         // Save the height of the unexpanded bubble, so we can make sure to
  4792.         // position it correctly (arrow points in the same location) after
  4793.         // we expand it.
  4794.         this.unexpandedHeight = this.offsetHeight;
  4795.       }
  4796.  
  4797.       this.expandBubble_();
  4798.     },
  4799.  
  4800.     /**
  4801.      * Shows the bubble. The bubble will start collapsed and expand when
  4802.      * clicked.
  4803.      */
  4804.     show: function() {
  4805.       if (!this.hidden)
  4806.         return;
  4807.  
  4808.       document.body.appendChild(this);
  4809.       this.hidden = false;
  4810.       this.resizeAndReposition();
  4811.  
  4812.       this.eventTracker_ = new EventTracker;
  4813.       this.eventTracker_.add(window,
  4814.                              'load', this.resizeAndReposition.bind(this));
  4815.       this.eventTracker_.add(window,
  4816.                              'resize', this.resizeAndReposition.bind(this));
  4817.       this.eventTracker_.add(this, 'click', this.onNotificationClick_);
  4818.  
  4819.       var doc = this.ownerDocument;
  4820.       this.eventTracker_.add(doc, 'keydown', this, true);
  4821.       this.eventTracker_.add(doc, 'mousedown', this, true);
  4822.     },
  4823.  
  4824.     /**
  4825.      * Hides the bubble from view.
  4826.      */
  4827.     hide: function() {
  4828.       this.hidden = true;
  4829.       this.bubbleSuppressed = false;
  4830.       this.eventTracker_.removeAll();
  4831.       this.parentNode.removeChild(this);
  4832.     },
  4833.  
  4834.     /**
  4835.      * Handles keydown and mousedown events, dismissing the bubble if
  4836.      * necessary.
  4837.      * @param {Event} e The event.
  4838.      * @private
  4839.      */
  4840.     handleEvent: function(e) {
  4841.       var handled = false;
  4842.       switch (e.type) {
  4843.         case 'keydown':
  4844.           if (e.keyCode == 27) {  // Esc.
  4845.             if (this.expanded) {
  4846.               this.collapseBubble_();
  4847.               handled = true;
  4848.             }
  4849.           }
  4850.           break;
  4851.  
  4852.         case 'mousedown':
  4853.           if (e.target == this.querySelector('.expandable-bubble-close')) {
  4854.             this.handleCloseEvent_();
  4855.             handled = true;
  4856.           } else if (!this.contains(e.target)) {
  4857.             if (this.expanded) {
  4858.               this.collapseBubble_();
  4859.               handled = true;
  4860.             }
  4861.           }
  4862.           break;
  4863.       }
  4864.  
  4865.       if (handled) {
  4866.         // The bubble emulates a focus grab when expanded, so when we've
  4867.         // collapsed/hide the bubble we consider the event handles and don't
  4868.         // need to propagate it further.
  4869.         e.stopPropagation();
  4870.         e.preventDefault();
  4871.       }
  4872.     },
  4873.   };
  4874.  
  4875.   /**
  4876.    * Whether the bubble is expanded or not.
  4877.    * @type {boolean}
  4878.    */
  4879.   cr.defineProperty(ExpandableBubble, 'expanded', cr.PropertyKind.BOOL_ATTR);
  4880.  
  4881.   /**
  4882.    * Whether the title needs to be masked out towards the right, which indicates
  4883.    * to the user that part of the text is clipped. This is only used when the
  4884.    * bubble is collapsed and the title doesn't fit because it is maxed out in
  4885.    * width within the anchored node.
  4886.    * @type {boolean}
  4887.    */
  4888.   cr.defineProperty(ExpandableBubble, 'masked', cr.PropertyKind.BOOL_ATTR);
  4889.  
  4890.   return {
  4891.     ExpandableBubble: ExpandableBubble
  4892.   };
  4893. });
  4894. </script>
  4895. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4896. // Use of this source code is governed by a BSD-style license that can be
  4897. // found in the LICENSE file.
  4898.  
  4899. cr.define('cr.ui', function() {
  4900.  
  4901.   /** @const */ var MenuItem = cr.ui.MenuItem;
  4902.  
  4903.   /**
  4904.    * Creates a new menu element. Menu dispatches all commands on the element it
  4905.    * was shown for.
  4906.    *
  4907.    * @param {Object=} opt_propertyBag Optional properties.
  4908.    * @constructor
  4909.    * @extends {HTMLMenuElement}
  4910.    */
  4911.   var Menu = cr.ui.define('menu');
  4912.  
  4913.   Menu.prototype = {
  4914.     __proto__: HTMLMenuElement.prototype,
  4915.  
  4916.     selectedIndex_: -1,
  4917.  
  4918.     /**
  4919.      * Element for which menu is being shown.
  4920.      */
  4921.     contextElement: null,
  4922.  
  4923.     /**
  4924.      * Initializes the menu element.
  4925.      */
  4926.     decorate: function() {
  4927.       this.addEventListener('mouseover', this.handleMouseOver_);
  4928.       this.addEventListener('mouseout', this.handleMouseOut_);
  4929.  
  4930.       this.classList.add('decorated');
  4931.       this.setAttribute('role', 'menu');
  4932.       this.hidden = true;  // Hide the menu by default.
  4933.  
  4934.       // Decorate the children as menu items.
  4935.       var children = this.children;
  4936.       for (var i = 0, child; child = children[i]; i++) {
  4937.         cr.ui.decorate(child, MenuItem);
  4938.       }
  4939.     },
  4940.  
  4941.     /**
  4942.      * Adds menu item at the end of the list.
  4943.      * @param {Object} item Menu item properties.
  4944.      * @return {cr.ui.MenuItem} The created menu item.
  4945.      */
  4946.     addMenuItem: function(item) {
  4947.       var menuItem = this.ownerDocument.createElement('menuitem');
  4948.       this.appendChild(menuItem);
  4949.  
  4950.       cr.ui.decorate(menuItem, MenuItem);
  4951.  
  4952.       if (item.label)
  4953.         menuItem.label = item.label;
  4954.  
  4955.       if (item.iconUrl)
  4956.         menuItem.iconUrl = item.iconUrl;
  4957.  
  4958.       return menuItem;
  4959.     },
  4960.  
  4961.     /**
  4962.      * Adds separator at the end of the list.
  4963.      */
  4964.     addSeparator: function() {
  4965.       var separator = this.ownerDocument.createElement('hr');
  4966.       this.appendChild(separator);
  4967.     },
  4968.  
  4969.     /**
  4970.      * Clears menu.
  4971.      */
  4972.     clear: function() {
  4973.       this.textContent = '';
  4974.     },
  4975.  
  4976.     /**
  4977.      * Walks up the ancestors of |el| until a menu item belonging to this menu
  4978.      * is found.
  4979.      * @param {Element} el The element to start searching from.
  4980.      * @return {cr.ui.MenuItem} The found menu item or null.
  4981.      * @private
  4982.      */
  4983.     findMenuItem_: function(el) {
  4984.       while (el && el.parentNode != this) {
  4985.         el = el.parentNode;
  4986.       }
  4987.       return el;
  4988.     },
  4989.  
  4990.     /**
  4991.      * Handles mouseover events and selects the hovered item.
  4992.      * @param {Event} e The mouseover event.
  4993.      * @private
  4994.      */
  4995.     handleMouseOver_: function(e) {
  4996.       var overItem = this.findMenuItem_(e.target);
  4997.       this.selectedItem = overItem;
  4998.     },
  4999.  
  5000.     /**
  5001.      * Handles mouseout events and deselects any selected item.
  5002.      * @param {Event} e The mouseout event.
  5003.      * @private
  5004.      */
  5005.     handleMouseOut_: function(e) {
  5006.       this.selectedItem = null;
  5007.     },
  5008.  
  5009.     /**
  5010.      * The selected menu item or null if none.
  5011.      * @type {cr.ui.MenuItem}
  5012.      */
  5013.     get selectedItem() {
  5014.       return this.children[this.selectedIndex];
  5015.     },
  5016.     set selectedItem(item) {
  5017.       var index = Array.prototype.indexOf.call(this.children, item);
  5018.       this.selectedIndex = index;
  5019.     },
  5020.  
  5021.     /**
  5022.      * Focuses the selected item. If selectedIndex is invalid, set it to 0
  5023.      * first.
  5024.      */
  5025.     focusSelectedItem: function() {
  5026.       if (this.selectedIndex < 0 ||
  5027.           this.selectedIndex > this.children.length) {
  5028.         this.selectedIndex = 0;
  5029.       }
  5030.  
  5031.       if (this.selectedItem)
  5032.         this.selectedItem.focus();
  5033.     },
  5034.  
  5035.     /**
  5036.      * Menu length
  5037.      */
  5038.     get length() {
  5039.       return this.children.length;
  5040.     },
  5041.  
  5042.     /**
  5043.      * This is the function that handles keyboard navigation. This is usually
  5044.      * called by the element responsible for managing the menu.
  5045.      * @param {Event} e The keydown event object.
  5046.      * @return {boolean} Whether the event was handled be the menu.
  5047.      */
  5048.     handleKeyDown: function(e) {
  5049.       var item = this.selectedItem;
  5050.  
  5051.       var self = this;
  5052.       function selectNextAvailable(m) {
  5053.         var children = self.children;
  5054.         var len = children.length;
  5055.         var i = self.selectedIndex;
  5056.         if (i == -1 && m == -1) {
  5057.           // Edge case when needed to go the last item first.
  5058.           i = 0;
  5059.         }
  5060.  
  5061.         // "i" may be negative(-1), so modulus operation and cycle below
  5062.         // wouldn't work as assumed. This trick makes startPosition positive
  5063.         // without altering it's modulo.
  5064.         var startPosition = (i + len) % len;
  5065.  
  5066.         while (true) {
  5067.           i = (i + m + len) % len;
  5068.  
  5069.           // Check not to enter into infinite loop if all items are hidden or
  5070.           // disabled.
  5071.           if (i == startPosition)
  5072.             break;
  5073.  
  5074.           item = children[i];
  5075.           if (item && !item.isSeparator() && !item.hidden && !item.disabled)
  5076.             break;
  5077.         }
  5078.         if (item && !item.disabled)
  5079.           self.selectedIndex = i;
  5080.       }
  5081.  
  5082.       switch (e.keyIdentifier) {
  5083.         case 'Down':
  5084.           selectNextAvailable(1);
  5085.           this.focusSelectedItem();
  5086.           return true;
  5087.         case 'Up':
  5088.           selectNextAvailable(-1);
  5089.           this.focusSelectedItem();
  5090.           return true;
  5091.         case 'Enter':
  5092.         case 'U+0020': // Space
  5093.           if (item) {
  5094.             var activationEvent = cr.doc.createEvent('Event');
  5095.             activationEvent.initEvent('activate', true, true);
  5096.             activationEvent.originalEvent = e;
  5097.             if (item.dispatchEvent(activationEvent)) {
  5098.               if (item.command)
  5099.                 item.command.execute();
  5100.             }
  5101.           }
  5102.           return true;
  5103.       }
  5104.  
  5105.       return false;
  5106.     },
  5107.  
  5108.     /**
  5109.      * Updates menu items command according to context.
  5110.      * @param {Node=} node Node for which to actuate commands state.
  5111.      */
  5112.     updateCommands: function(node) {
  5113.       var children = this.children;
  5114.  
  5115.       for (var i = 0, child; child = children[i]; i++)
  5116.         child.updateCommand(node);
  5117.     }
  5118.   };
  5119.  
  5120.   function selectedIndexChanged(selectedIndex, oldSelectedIndex) {
  5121.     var oldSelectedItem = this.children[oldSelectedIndex];
  5122.     if (oldSelectedItem) {
  5123.       oldSelectedItem.selected = false;
  5124.       oldSelectedItem.blur();
  5125.     }
  5126.     var item = this.selectedItem;
  5127.     if (item)
  5128.       item.selected = true;
  5129.   }
  5130.  
  5131.   /**
  5132.    * The selected menu item.
  5133.    * @type {number}
  5134.    */
  5135.   cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS,
  5136.       selectedIndexChanged);
  5137.  
  5138.   // Export
  5139.   return {
  5140.     Menu: Menu
  5141.   };
  5142. });
  5143. </script>
  5144. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5145. // Use of this source code is governed by a BSD-style license that can be
  5146. // found in the LICENSE file.
  5147.  
  5148. cr.define('cr.ui', function() {
  5149.   /** @const */ var Command = cr.ui.Command;
  5150.  
  5151.   /**
  5152.    * Creates a new menu item element.
  5153.    * @param {Object=} opt_propertyBag Optional properties.
  5154.    * @constructor
  5155.    * @extends {HTMLDivElement}
  5156.    */
  5157.   var MenuItem = cr.ui.define('div');
  5158.  
  5159.   /**
  5160.    * Creates a new menu separator element.
  5161.    * @return {cr.ui.MenuItem} The new separator element.
  5162.    */
  5163.   MenuItem.createSeparator = function() {
  5164.     var el = cr.doc.createElement('hr');
  5165.     MenuItem.decorate(el);
  5166.     return el;
  5167.   };
  5168.  
  5169.   MenuItem.prototype = {
  5170.     __proto__: HTMLButtonElement.prototype,
  5171.  
  5172.     /**
  5173.      * Initializes the menu item.
  5174.      */
  5175.     decorate: function() {
  5176.       var commandId;
  5177.       if ((commandId = this.getAttribute('command')))
  5178.         this.command = commandId;
  5179.  
  5180.       this.addEventListener('mouseup', this.handleMouseUp_);
  5181.  
  5182.       // Adding the 'custom-appearance' class prevents widgets.css from changing
  5183.       // the appearance of this element.
  5184.       this.classList.add('custom-appearance');
  5185.  
  5186.       this.setAttribute('role', 'menuitem');
  5187.  
  5188.       var iconUrl;
  5189.       if ((iconUrl = this.getAttribute('icon')))
  5190.         this.iconUrl = iconUrl;
  5191.     },
  5192.  
  5193.     /**
  5194.      * The command associated with this menu item. If this is set to a string
  5195.      * of the form "#element-id" then the element is looked up in the document
  5196.      * of the command.
  5197.      * @type {cr.ui.Command}
  5198.      */
  5199.     command_: null,
  5200.     get command() {
  5201.       return this.command_;
  5202.     },
  5203.     set command(command) {
  5204.       if (this.command_) {
  5205.         this.command_.removeEventListener('labelChange', this);
  5206.         this.command_.removeEventListener('disabledChange', this);
  5207.         this.command_.removeEventListener('hiddenChange', this);
  5208.         this.command_.removeEventListener('checkedChange', this);
  5209.       }
  5210.  
  5211.       if (typeof command == 'string' && command[0] == '#') {
  5212.         command = this.ownerDocument.getElementById(command.slice(1));
  5213.         cr.ui.decorate(command, Command);
  5214.       }
  5215.  
  5216.       this.command_ = command;
  5217.       if (command) {
  5218.         if (command.id)
  5219.           this.setAttribute('command', '#' + command.id);
  5220.  
  5221.         this.label = command.label;
  5222.         this.disabled = command.disabled;
  5223.         this.hidden = command.hidden;
  5224.  
  5225.         this.command_.addEventListener('labelChange', this);
  5226.         this.command_.addEventListener('disabledChange', this);
  5227.         this.command_.addEventListener('hiddenChange', this);
  5228.         this.command_.addEventListener('checkedChange', this);
  5229.       }
  5230.  
  5231.       this.updateShortcut_();
  5232.     },
  5233.  
  5234.     /**
  5235.      * The text label.
  5236.      * @type {string}
  5237.      */
  5238.     get label() {
  5239.       return this.textContent;
  5240.     },
  5241.     set label(label) {
  5242.       this.textContent = label;
  5243.     },
  5244.  
  5245.     /**
  5246.      * Menu icon.
  5247.      * @type {string}
  5248.      */
  5249.     get iconUrl() {
  5250.       return this.style.backgroundImage;
  5251.     },
  5252.     set iconUrl(url) {
  5253.       this.style.backgroundImage = 'url(' + url + ')';
  5254.     },
  5255.  
  5256.     /**
  5257.      * @return {boolean} Whether the menu item is a separator.
  5258.      */
  5259.     isSeparator: function() {
  5260.       return this.tagName == 'HR';
  5261.     },
  5262.  
  5263.     /**
  5264.      * Updates shortcut text according to associated command. If command has
  5265.      * multiple shortcuts, only first one is displayed.
  5266.      */
  5267.     updateShortcut_: function() {
  5268.       this.removeAttribute('shortcutText');
  5269.  
  5270.       if (!(this.command_ && this.command_.shortcut))
  5271.         return;
  5272.  
  5273.       var shortcuts = this.command_.shortcut.split(/\s+/);
  5274.  
  5275.       if (shortcuts.length == 0)
  5276.         return;
  5277.  
  5278.       var shortcut = shortcuts[0];
  5279.       var mods = {};
  5280.       var ident = '';
  5281.       shortcut.split('-').forEach(function(part) {
  5282.         var partUc = part.toUpperCase();
  5283.         switch (partUc) {
  5284.           case 'CTRL':
  5285.           case 'ALT':
  5286.           case 'SHIFT':
  5287.           case 'META':
  5288.             mods[partUc] = true;
  5289.             break;
  5290.           default:
  5291.             console.assert(!ident, 'Shortcut has two non-modifier keys');
  5292.             ident = part;
  5293.         }
  5294.       });
  5295.  
  5296.       var shortcutText = '';
  5297.  
  5298.       // TODO(zvorygin): if more cornercases appear - optimize following
  5299.       // code. Currently 'Enter' keystroke is passed as 'Enter', and 'Space'
  5300.       // is passed as 'U+0020'
  5301.       if (ident == 'U+0020')
  5302.         ident = 'Space';
  5303.  
  5304.       ['CTRL', 'ALT', 'SHIFT', 'META'].forEach(function(mod) {
  5305.         if (mods[mod])
  5306.           shortcutText += loadTimeData.getString('SHORTCUT_' + mod) + '+';
  5307.       });
  5308.  
  5309.       if (ident.indexOf('U+') != 0) {
  5310.         shortcutText +=
  5311.             loadTimeData.getString('SHORTCUT_' + ident.toUpperCase());
  5312.       } else {
  5313.         shortcutText +=
  5314.             String.fromCharCode(parseInt(ident.substring(2), 16));
  5315.       }
  5316.  
  5317.       this.setAttribute('shortcutText', shortcutText);
  5318.     },
  5319.  
  5320.     /**
  5321.      * Handles mouseup events. This dispatches an activate event; if there is an
  5322.      * associated command, that command is executed.
  5323.      * @param {Event} e The mouseup event object.
  5324.      * @private
  5325.      */
  5326.     handleMouseUp_: function(e) {
  5327.       if (!this.disabled && !this.isSeparator() && this.selected) {
  5328.         // Store |contextElement| since it'll be removed by {Menu} on handling
  5329.         // 'activate' event.
  5330.         var contextElement = this.parentNode.contextElement;
  5331.         var activationEvent = cr.doc.createEvent('Event');
  5332.         activationEvent.initEvent('activate', true, true);
  5333.         activationEvent.originalEvent = e;
  5334.         // Dispatch command event followed by executing the command object.
  5335.         if (this.dispatchEvent(activationEvent)) {
  5336.           var command = this.command;
  5337.           if (command) {
  5338.             command.execute(contextElement);
  5339.             cr.ui.swallowDoubleClick(e);
  5340.           }
  5341.         }
  5342.       }
  5343.     },
  5344.  
  5345.     /**
  5346.      * Updates command according to the node on which this menu was invoked.
  5347.      * @param {Node=} opt_node Node on which menu was opened.
  5348.      */
  5349.     updateCommand: function(opt_node) {
  5350.       if (this.command_) {
  5351.         this.command_.canExecuteChange(opt_node);
  5352.       }
  5353.     },
  5354.  
  5355.     /**
  5356.      * Handles changes to the associated command.
  5357.      * @param {Event} e The event object.
  5358.      */
  5359.     handleEvent: function(e) {
  5360.       switch (e.type) {
  5361.         case 'disabledChange':
  5362.           this.disabled = this.command.disabled;
  5363.           break;
  5364.         case 'hiddenChange':
  5365.           this.hidden = this.command.hidden;
  5366.           break;
  5367.         case 'labelChange':
  5368.           this.label = this.command.label;
  5369.           break;
  5370.         case 'checkedChange':
  5371.           this.checked = this.command.checked;
  5372.           break;
  5373.       }
  5374.     }
  5375.   };
  5376.  
  5377.   /**
  5378.    * Whether the menu item is disabled or not.
  5379.    * @type {boolean}
  5380.    */
  5381.   cr.defineProperty(MenuItem, 'disabled', cr.PropertyKind.BOOL_ATTR);
  5382.  
  5383.   /**
  5384.    * Whether the menu item is hidden or not.
  5385.    * @type {boolean}
  5386.    */
  5387.   cr.defineProperty(MenuItem, 'hidden', cr.PropertyKind.BOOL_ATTR);
  5388.  
  5389.   /**
  5390.    * Whether the menu item is selected or not.
  5391.    * @type {boolean}
  5392.    */
  5393.   cr.defineProperty(MenuItem, 'selected', cr.PropertyKind.BOOL_ATTR);
  5394.  
  5395.   /**
  5396.    * Whether the menu item is checked or not.
  5397.    * @type {boolean}
  5398.    */
  5399.   cr.defineProperty(MenuItem, 'checked', cr.PropertyKind.BOOL_ATTR);
  5400.  
  5401.   // Export
  5402.   return {
  5403.     MenuItem: MenuItem
  5404.   };
  5405. });
  5406. </script>
  5407. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5408. // Use of this source code is governed by a BSD-style license that can be
  5409. // found in the LICENSE file.
  5410.  
  5411. /**
  5412.  * @fileoverview This file provides utility functions for position popups.
  5413.  */
  5414.  
  5415. cr.define('cr.ui', function() {
  5416.  
  5417.   /**
  5418.    * Type def for rects as returned by getBoundingClientRect.
  5419.    * @typedef { {left: number, top: number, width: number, height: number,
  5420.    *             right: number, bottom: number}}
  5421.    */
  5422.   var Rect;
  5423.  
  5424.   /**
  5425.    * Enum for defining how to anchor a popup to an anchor element.
  5426.    * @enum {number}
  5427.    */
  5428.   var AnchorType = {
  5429.     /**
  5430.      * The popup's right edge is aligned with the left edge of the anchor.
  5431.      * The popup's top edge is aligned with the top edge of the anchor.
  5432.      */
  5433.     BEFORE: 1,  // p: right, a: left, p: top, a: top
  5434.  
  5435.     /**
  5436.      * The popop's left edge is aligned with the right edge of the anchor.
  5437.      * The popup's top edge is aligned with the top edge of the anchor.
  5438.      */
  5439.     AFTER: 2,  // p: left a: right, p: top, a: top
  5440.  
  5441.     /**
  5442.      * The popop's bottom edge is aligned with the top edge of the anchor.
  5443.      * The popup's left edge is aligned with the left edge of the anchor.
  5444.      */
  5445.     ABOVE: 3,  // p: bottom, a: top, p: left, a: left
  5446.  
  5447.     /**
  5448.      * The popop's top edge is aligned with the bottom edge of the anchor.
  5449.      * The popup's left edge is aligned with the left edge of the anchor.
  5450.      */
  5451.     BELOW: 4  // p: top, a: bottom, p: left, a: left
  5452.   };
  5453.  
  5454.   /**
  5455.    * Helper function for positionPopupAroundElement and positionPopupAroundRect.
  5456.    * @param {!Rect} anchorRect The rect for the anchor.
  5457.    * @param {!HTMLElement} popupElement The element used for the popup.
  5458.    * @param {AnchorType} type The type of anchoring to do.
  5459.    * @param {boolean} invertLeftRight Whether to invert the right/left
  5460.    *     alignment.
  5461.    */
  5462.   function positionPopupAroundRect(anchorRect, popupElement, type,
  5463.                                    invertLeftRight) {
  5464.     var popupRect = popupElement.getBoundingClientRect();
  5465.     var availRect;
  5466.     var ownerDoc = popupElement.ownerDocument;
  5467.     var cs = ownerDoc.defaultView.getComputedStyle(popupElement);
  5468.     var docElement = ownerDoc.documentElement;
  5469.  
  5470.     if (cs.position == 'fixed') {
  5471.       // For 'fixed' positioned popups, the available rectangle should be based
  5472.       // on the viewport rather than the document.
  5473.       availRect = {
  5474.         height: docElement.clientHeight,
  5475.         width: docElement.clientWidth,
  5476.         top: 0,
  5477.         bottom: docElement.clientHeight,
  5478.         left: 0,
  5479.         right: docElement.clientWidth
  5480.       };
  5481.     } else {
  5482.       availRect = popupElement.offsetParent.getBoundingClientRect();
  5483.     }
  5484.  
  5485.     if (cs.direction == 'rtl')
  5486.       invertLeftRight = !invertLeftRight;
  5487.  
  5488.     // Flip BEFORE, AFTER based on alignment.
  5489.     if (invertLeftRight) {
  5490.       if (type == AnchorType.BEFORE)
  5491.         type = AnchorType.AFTER;
  5492.       else if (type == AnchorType.AFTER)
  5493.         type = AnchorType.BEFORE;
  5494.     }
  5495.  
  5496.     // Flip type based on available size
  5497.     switch (type) {
  5498.       case AnchorType.BELOW:
  5499.         if (anchorRect.bottom + popupRect.height > availRect.height &&
  5500.             popupRect.height <= anchorRect.top) {
  5501.           type = AnchorType.ABOVE;
  5502.         }
  5503.         break;
  5504.       case AnchorType.ABOVE:
  5505.         if (popupRect.height > anchorRect.top &&
  5506.             anchorRect.bottom + popupRect.height <= availRect.height) {
  5507.           type = AnchorType.BELOW;
  5508.         }
  5509.         break;
  5510.       case AnchorType.AFTER:
  5511.         if (anchorRect.right + popupRect.width > availRect.width &&
  5512.             popupRect.width <= anchorRect.left) {
  5513.           type = AnchorType.BEFORE;
  5514.         }
  5515.         break;
  5516.       case AnchorType.BEFORE:
  5517.         if (popupRect.width > anchorRect.left &&
  5518.             anchorRect.right + popupRect.width <= availRect.width) {
  5519.           type = AnchorType.AFTER;
  5520.         }
  5521.         break;
  5522.     }
  5523.     // flipping done
  5524.  
  5525.     var style = popupElement.style;
  5526.     // Reset all directions.
  5527.     style.left = style.right = style.top = style.bottom = 'auto';
  5528.  
  5529.     // Primary direction
  5530.     switch (type) {
  5531.       case AnchorType.BELOW:
  5532.         if (anchorRect.bottom + popupRect.height <= availRect.height)
  5533.           style.top = anchorRect.bottom + 'px';
  5534.         else
  5535.           style.bottom = '0';
  5536.         break;
  5537.       case AnchorType.ABOVE:
  5538.         if (availRect.height - anchorRect.top >= 0)
  5539.           style.bottom = availRect.height - anchorRect.top + 'px';
  5540.         else
  5541.           style.top = '0';
  5542.         break;
  5543.       case AnchorType.AFTER:
  5544.         if (anchorRect.right + popupRect.width <= availRect.width)
  5545.           style.left = anchorRect.right + 'px';
  5546.         else
  5547.           style.right = '0';
  5548.         break;
  5549.       case AnchorType.BEFORE:
  5550.         if (availRect.width - anchorRect.left >= 0)
  5551.           style.right = availRect.width - anchorRect.left + 'px';
  5552.         else
  5553.           style.left = '0';
  5554.         break;
  5555.     }
  5556.  
  5557.     // Secondary direction
  5558.     switch (type) {
  5559.       case AnchorType.BELOW:
  5560.       case AnchorType.ABOVE:
  5561.         if (invertLeftRight) {
  5562.           // align right edges
  5563.           if (anchorRect.right - popupRect.width >= 0) {
  5564.             style.right = availRect.width - anchorRect.right + 'px';
  5565.  
  5566.           // align left edges
  5567.           } else if (anchorRect.left + popupRect.width <= availRect.width) {
  5568.             style.left = anchorRect.left + 'px';
  5569.  
  5570.           // not enough room on either side
  5571.           } else {
  5572.             style.right = '0';
  5573.           }
  5574.         } else {
  5575.           // align left edges
  5576.           if (anchorRect.left + popupRect.width <= availRect.width) {
  5577.             style.left = anchorRect.left + 'px';
  5578.  
  5579.           // align right edges
  5580.           } else if (anchorRect.right - popupRect.width >= 0) {
  5581.             style.right = availRect.width - anchorRect.right + 'px';
  5582.  
  5583.           // not enough room on either side
  5584.           } else {
  5585.             style.left = '0';
  5586.           }
  5587.         }
  5588.         break;
  5589.  
  5590.       case AnchorType.AFTER:
  5591.       case AnchorType.BEFORE:
  5592.         // align top edges
  5593.         if (anchorRect.top + popupRect.height <= availRect.height) {
  5594.           style.top = anchorRect.top + 'px';
  5595.  
  5596.         // align bottom edges
  5597.         } else if (anchorRect.bottom - popupRect.height >= 0) {
  5598.           style.bottom = availRect.height - anchorRect.bottom + 'px';
  5599.  
  5600.           // not enough room on either side
  5601.         } else {
  5602.           style.top = '0';
  5603.         }
  5604.         break;
  5605.     }
  5606.   }
  5607.  
  5608.   /**
  5609.    * Positions a popup element relative to an anchor element. The popup element
  5610.    * should have position set to absolute and it should be a child of the body
  5611.    * element.
  5612.    * @param {!HTMLElement} anchorElement The element that the popup is anchored
  5613.    *     to.
  5614.    * @param {!HTMLElement} popupElement The popup element we are positioning.
  5615.    * @param {AnchorType} type The type of anchoring we want.
  5616.    * @param {boolean} invertLeftRight Whether to invert the right/left
  5617.    *     alignment.
  5618.    */
  5619.   function positionPopupAroundElement(anchorElement, popupElement, type,
  5620.                                       invertLeftRight) {
  5621.     var anchorRect = anchorElement.getBoundingClientRect();
  5622.     positionPopupAroundRect(anchorRect, popupElement, type, invertLeftRight);
  5623.   }
  5624.  
  5625.   /**
  5626.    * Positions a popup around a point.
  5627.    * @param {number} x The client x position.
  5628.    * @param {number} y The client y position.
  5629.    * @param {!HTMLElement} popupElement The popup element we are positioning.
  5630.    */
  5631.   function positionPopupAtPoint(x, y, popupElement) {
  5632.     var rect = {
  5633.       left: x,
  5634.       top: y,
  5635.       width: 0,
  5636.       height: 0,
  5637.       right: x,
  5638.       bottom: y
  5639.     };
  5640.     positionPopupAroundRect(rect, popupElement, AnchorType.BELOW);
  5641.   }
  5642.  
  5643.   // Export
  5644.   return {
  5645.     AnchorType: AnchorType,
  5646.     positionPopupAroundElement: positionPopupAroundElement,
  5647.     positionPopupAtPoint: positionPopupAtPoint
  5648.   };
  5649. });
  5650. </script>
  5651. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5652. // Use of this source code is governed by a BSD-style license that can be
  5653. // found in the LICENSE file.
  5654.  
  5655. cr.define('cr.ui', function() {
  5656.   /** @const */
  5657.   var Menu = cr.ui.Menu;
  5658.   /** @const */
  5659.   var positionPopupAroundElement = cr.ui.positionPopupAroundElement;
  5660.  
  5661.   /**
  5662.    * Creates a new menu button element.
  5663.    * @param {Object=} opt_propertyBag Optional properties.
  5664.    * @constructor
  5665.    * @extends {HTMLButtonElement}
  5666.    */
  5667.   var MenuButton = cr.ui.define('button');
  5668.  
  5669.   MenuButton.prototype = {
  5670.     __proto__: HTMLButtonElement.prototype,
  5671.  
  5672.     /**
  5673.      * Initializes the menu button.
  5674.      */
  5675.     decorate: function() {
  5676.       this.addEventListener('mousedown', this);
  5677.       this.addEventListener('keydown', this);
  5678.  
  5679.       // Adding the 'custom-appearance' class prevents widgets.css from changing
  5680.       // the appearance of this element.
  5681.       this.classList.add('custom-appearance');
  5682.       this.classList.add('menu-button');  // For styles in menu_button.css.
  5683.  
  5684.       var menu;
  5685.       if ((menu = this.getAttribute('menu')))
  5686.         this.menu = menu;
  5687.  
  5688.       // An event tracker for events we only connect to while the menu is
  5689.       // displayed.
  5690.       this.showingEvents_ = new EventTracker();
  5691.  
  5692.       this.anchorType = cr.ui.AnchorType.BELOW;
  5693.       this.invertLeftRight = false;
  5694.     },
  5695.  
  5696.     /**
  5697.      * The menu associated with the menu button.
  5698.      * @type {cr.ui.Menu}
  5699.      */
  5700.     get menu() {
  5701.       return this.menu_;
  5702.     },
  5703.     set menu(menu) {
  5704.       if (typeof menu == 'string' && menu[0] == '#') {
  5705.         menu = this.ownerDocument.getElementById(menu.slice(1));
  5706.         cr.ui.decorate(menu, Menu);
  5707.       }
  5708.  
  5709.       this.menu_ = menu;
  5710.       if (menu) {
  5711.         if (menu.id)
  5712.           this.setAttribute('menu', '#' + menu.id);
  5713.       }
  5714.     },
  5715.  
  5716.     /**
  5717.      * Handles event callbacks.
  5718.      * @param {Event} e The event object.
  5719.      */
  5720.     handleEvent: function(e) {
  5721.       if (!this.menu)
  5722.         return;
  5723.  
  5724.       switch (e.type) {
  5725.         case 'mousedown':
  5726.           if (e.currentTarget == this.ownerDocument) {
  5727.             if (!this.contains(e.target) && !this.menu.contains(e.target))
  5728.               this.hideMenu();
  5729.             else
  5730.               e.preventDefault();
  5731.           } else {
  5732.             if (this.isMenuShown()) {
  5733.               this.hideMenu();
  5734.             } else if (e.button == 0) {  // Only show the menu when using left
  5735.                                          // mouse button.
  5736.               this.showMenu(false);
  5737.               // Prevent the button from stealing focus on mousedown.
  5738.               e.preventDefault();
  5739.             }
  5740.           }
  5741.           break;
  5742.         case 'keydown':
  5743.           this.handleKeyDown(e);
  5744.           // If the menu is visible we let it handle all the keyboard events.
  5745.           if (this.isMenuShown() && e.currentTarget == this.ownerDocument) {
  5746.             if (this.menu.handleKeyDown(e)) {
  5747.               e.preventDefault();
  5748.               e.stopPropagation();
  5749.             }
  5750.           }
  5751.           break;
  5752.  
  5753.         case 'focus':
  5754.           if (!this.contains(e.target) && !this.menu.contains(e.target))
  5755.             this.hideMenu();
  5756.           break;
  5757.  
  5758.         case 'activate':
  5759.         case 'resize':
  5760.           this.hideMenu();
  5761.           break;
  5762.       }
  5763.     },
  5764.  
  5765.     /**
  5766.      * Shows the menu.
  5767.      * @param {boolean} shouldSetFocus Whether to set focus on the
  5768.      *     selected menu item.
  5769.      */
  5770.     showMenu: function(shouldSetFocus) {
  5771.       this.hideMenu();
  5772.  
  5773.       var event = document.createEvent('UIEvents');
  5774.       event.initUIEvent('menushow', true, true, window, null);
  5775.  
  5776.       if (this.dispatchEvent(event)) {
  5777.         this.menu.hidden = false;
  5778.  
  5779.         this.setAttribute('menu-shown', '');
  5780.         if (shouldSetFocus)
  5781.           this.menu.focusSelectedItem();
  5782.  
  5783.         // when the menu is shown we steal all keyboard events.
  5784.         var doc = this.ownerDocument;
  5785.         var win = doc.defaultView;
  5786.         this.showingEvents_.add(doc, 'keydown', this, true);
  5787.         this.showingEvents_.add(doc, 'mousedown', this, true);
  5788.         this.showingEvents_.add(doc, 'focus', this, true);
  5789.         this.showingEvents_.add(win, 'resize', this);
  5790.         this.showingEvents_.add(this.menu, 'activate', this);
  5791.         this.positionMenu_();
  5792.       }
  5793.     },
  5794.  
  5795.     /**
  5796.      * Hides the menu. If your menu can go out of scope, make sure to call this
  5797.      * first.
  5798.      */
  5799.     hideMenu: function() {
  5800.       if (!this.isMenuShown())
  5801.         return;
  5802.  
  5803.       this.removeAttribute('menu-shown');
  5804.       this.menu.hidden = true;
  5805.  
  5806.       this.showingEvents_.removeAll();
  5807.       this.focus();
  5808.     },
  5809.  
  5810.     /**
  5811.      * Whether the menu is shown.
  5812.      */
  5813.     isMenuShown: function() {
  5814.       return this.hasAttribute('menu-shown');
  5815.     },
  5816.  
  5817.     /**
  5818.      * Positions the menu below the menu button. At this point we do not use any
  5819.      * advanced positioning logic to ensure the menu fits in the viewport.
  5820.      * @private
  5821.      */
  5822.     positionMenu_: function() {
  5823.       positionPopupAroundElement(this, this.menu, this.anchorType,
  5824.                                  this.invertLeftRight);
  5825.     },
  5826.  
  5827.     /**
  5828.      * Handles the keydown event for the menu button.
  5829.      */
  5830.     handleKeyDown: function(e) {
  5831.       switch (e.keyIdentifier) {
  5832.         case 'Down':
  5833.         case 'Up':
  5834.         case 'Enter':
  5835.         case 'U+0020': // Space
  5836.           if (!this.isMenuShown())
  5837.             this.showMenu(true);
  5838.           e.preventDefault();
  5839.           break;
  5840.         case 'Esc':
  5841.         case 'U+001B': // Maybe this is remote desktop playing a prank?
  5842.         case 'U+0009': // Tab
  5843.           this.hideMenu();
  5844.           break;
  5845.       }
  5846.     }
  5847.   };
  5848.  
  5849.   /**
  5850.    * Helper for styling a menu button with a drop-down arrow indicator.
  5851.    * Creates a new 2D canvas context and draws a downward-facing arrow into it.
  5852.    * @param {string} canvasName The name of the canvas. The canvas can be
  5853.    *     addressed from CSS using -webkit-canvas(<canvasName>).
  5854.    * @param {number} width The width of the canvas and the arrow.
  5855.    * @param {number} height The height of the canvas and the arrow.
  5856.    * @param {string} colorSpec The CSS color to use when drawing the arrow.
  5857.    */
  5858.   function createDropDownArrowCanvas(canvasName, width, height, colorSpec) {
  5859.     var ctx = document.getCSSCanvasContext('2d', canvasName, width, height);
  5860.     ctx.fillStyle = ctx.strokeStyle = colorSpec;
  5861.     ctx.beginPath();
  5862.     ctx.moveTo(0, 0);
  5863.     ctx.lineTo(width, 0);
  5864.     ctx.lineTo(height, height);
  5865.     ctx.closePath();
  5866.     ctx.fill();
  5867.     ctx.stroke();
  5868.   };
  5869.  
  5870.   /** @const */ var ARROW_WIDTH = 6;
  5871.   /** @const */ var ARROW_HEIGHT = 3;
  5872.  
  5873.   /**
  5874.    * Create the images used to style drop-down-style MenuButtons.
  5875.    * This should be called before creating any MenuButtons that will have the
  5876.    * CSS class 'drop-down'. If no colors are specified, defaults will be used.
  5877.    * @param {=string} normalColor CSS color for the default button state.
  5878.    * @param {=string} hoverColor CSS color for the hover button state.
  5879.    * @param {=string} activeColor CSS color for the active button state.
  5880.    */
  5881.   MenuButton.createDropDownArrows = function(
  5882.       normalColor, hoverColor, activeColor) {
  5883.     normalColor = normalColor || 'rgb(192, 195, 198)';
  5884.     hoverColor = hoverColor || 'rgb(48, 57, 66)';
  5885.     activeColor = activeColor || 'white';
  5886.  
  5887.     createDropDownArrowCanvas(
  5888.         'drop-down-arrow', ARROW_WIDTH, ARROW_HEIGHT, normalColor);
  5889.     createDropDownArrowCanvas(
  5890.         'drop-down-arrow-hover', ARROW_WIDTH, ARROW_HEIGHT, hoverColor);
  5891.     createDropDownArrowCanvas(
  5892.         'drop-down-arrow-active', ARROW_WIDTH, ARROW_HEIGHT, activeColor);
  5893.   };
  5894.  
  5895.   // Export
  5896.   return {
  5897.     MenuButton: MenuButton
  5898.   };
  5899. });
  5900. </script>
  5901. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5902. // Use of this source code is governed by a BSD-style license that can be
  5903. // found in the LICENSE file.
  5904.  
  5905. /**
  5906.  * @fileoverview This implements a special button that is useful for showing a
  5907.  * context menu.
  5908.  */
  5909.  
  5910. cr.define('cr.ui', function() {
  5911.   /** @const */ var MenuButton = cr.ui.MenuButton;
  5912.  
  5913.   /**
  5914.    * Helper function for ContextMenuButton to find the first ancestor of the
  5915.    * button that has a context menu.
  5916.    * @param {!MenuButton} el The button to start the search from.
  5917.    * @return {HTMLElement} The found element or null if not found.
  5918.    */
  5919.   function getContextMenuTarget(el) {
  5920.     do {
  5921.       el = el.parentNode;
  5922.     } while (el && !('contextMenu' in el));
  5923.     return el;
  5924.   }
  5925.  
  5926.   /**
  5927.    * Creates a new menu button which is used to show the context menu for an
  5928.    * ancestor that has a {@code contextMenu} property.
  5929.    * @param {Object=} opt_propertyBag Optional properties.
  5930.    * @constructor
  5931.    * @extends {MenuButton}
  5932.    */
  5933.   var ContextMenuButton = cr.ui.define('button');
  5934.  
  5935.   ContextMenuButton.prototype = {
  5936.     __proto__: MenuButton.prototype,
  5937.  
  5938.     /**
  5939.      * Override to return the contextMenu for the ancestor.
  5940.      * @override
  5941.      * @type {cr.ui.Menu}
  5942.      */
  5943.     get menu() {
  5944.       var target = getContextMenuTarget(this);
  5945.       return target && target.contextMenu;
  5946.     },
  5947.  
  5948.     /** @override */
  5949.     decorate: function() {
  5950.       this.tabIndex = -1;
  5951.       this.addEventListener('mouseup', this);
  5952.       MenuButton.prototype.decorate.call(this);
  5953.     },
  5954.  
  5955.     /** @override */
  5956.     handleEvent: function(e) {
  5957.       switch (e.type) {
  5958.         case 'mousedown':
  5959.           // Menu buttons prevent focus changes.
  5960.           var target = getContextMenuTarget(this);
  5961.           if (target)
  5962.             target.focus();
  5963.           break;
  5964.         case 'mouseup':
  5965.           // Stop mouseup to prevent selection changes.
  5966.           e.stopPropagation();
  5967.           break;
  5968.       }
  5969.       MenuButton.prototype.handleEvent.call(this, e);
  5970.     }
  5971.   };
  5972.  
  5973.   // Export
  5974.   return {
  5975.     ContextMenuButton: ContextMenuButton
  5976.   };
  5977. });
  5978. </script>
  5979. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5980. // Use of this source code is governed by a BSD-style license that can be
  5981. // found in the LICENSE file.
  5982.  
  5983. /**
  5984.  * @fileoverview Touch Handler. Class that handles all touch events and
  5985.  * uses them to interpret higher level gestures and behaviors. TouchEvent is a
  5986.  * built in mobile safari type:
  5987.  * http://developer.apple.com/safari/library/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html.
  5988.  * This class is intended to work with all webkit browsers, tested on Chrome and
  5989.  * iOS.
  5990.  *
  5991.  * The following types of gestures are currently supported.  See the definition
  5992.  * of TouchHandler.EventType for details.
  5993.  *
  5994.  * Single Touch:
  5995.  *      This provides simple single-touch events.  Any secondary touch is
  5996.  *      ignored.
  5997.  *
  5998.  * Drag:
  5999.  *      A single touch followed by some movement. This behavior will handle all
  6000.  *      of the required events and report the properties of the drag to you
  6001.  *      while the touch is happening and at the end of the drag sequence. This
  6002.  *      behavior will NOT perform the actual dragging (redrawing the element)
  6003.  *      for you, this responsibility is left to the client code.
  6004.  *
  6005.  * Long press:
  6006.  *     When your element is touched and held without any drag occuring, the
  6007.  *     LONG_PRESS event will fire.
  6008.  */
  6009.  
  6010. // Use an anonymous function to enable strict mode just for this file (which
  6011. // will be concatenated with other files when embedded in Chrome)
  6012. cr.define('cr.ui', function() {
  6013.   'use strict';
  6014.  
  6015.   /**
  6016.    * A TouchHandler attaches to an Element, listents for low-level touch (or
  6017.    * mouse) events and dispatching higher-level events on the element.
  6018.    * @param {!Element} element The element to listen on and fire events
  6019.    * for.
  6020.    * @constructor
  6021.    */
  6022.   function TouchHandler(element) {
  6023.     /**
  6024.      * @type {!Element}
  6025.      * @private
  6026.      */
  6027.     this.element_ = element;
  6028.  
  6029.     /**
  6030.      * The absolute sum of all touch y deltas.
  6031.      * @type {number}
  6032.      * @private
  6033.      */
  6034.     this.totalMoveY_ = 0;
  6035.  
  6036.     /**
  6037.      * The absolute sum of all touch x deltas.
  6038.      * @type {number}
  6039.      * @private
  6040.      */
  6041.     this.totalMoveX_ = 0;
  6042.  
  6043.     /**
  6044.      * An array of tuples where the first item is the horizontal component of a
  6045.      * recent relevant touch and the second item is the touch's time stamp. Old
  6046.      * touches are removed based on the max tracking time and when direction
  6047.      * changes.
  6048.       * @type {!Array.<number>}
  6049.       * @private
  6050.       */
  6051.     this.recentTouchesX_ = [];
  6052.  
  6053.     /**
  6054.      * An array of tuples where the first item is the vertical component of a
  6055.      * recent relevant touch and the second item is the touch's time stamp. Old
  6056.      * touches are removed based on the max tracking time and when direction
  6057.      * changes.
  6058.      * @type {!Array.<number>}
  6059.      * @private
  6060.      */
  6061.     this.recentTouchesY_ = [];
  6062.  
  6063.     /**
  6064.      * Used to keep track of all events we subscribe to so we can easily clean
  6065.      * up
  6066.      * @type {EventTracker}
  6067.      * @private
  6068.      */
  6069.     this.events_ = new EventTracker();
  6070.   }
  6071.  
  6072.  
  6073.   /**
  6074.    * DOM Events that may be fired by the TouchHandler at the element
  6075.    */
  6076.   TouchHandler.EventType = {
  6077.     // Fired whenever the element is touched as the only touch to the device.
  6078.     // enableDrag defaults to false, set to true to permit dragging.
  6079.     TOUCH_START: 'touchHandler:touch_start',
  6080.  
  6081.     // Fired when an element is held for a period of time.  Prevents dragging
  6082.     // from occuring (even if enableDrag was set to true).
  6083.     LONG_PRESS: 'touchHandler:long_press',
  6084.  
  6085.     // If enableDrag was set to true at TOUCH_START, DRAG_START will fire when
  6086.     // the touch first moves sufficient distance.  enableDrag is set to true but
  6087.     // can be reset to false to cancel the drag.
  6088.     DRAG_START: 'touchHandler:drag_start',
  6089.  
  6090.     // If enableDrag was true after DRAG_START, DRAG_MOVE will fire whenever the
  6091.     // touch is moved.
  6092.     DRAG_MOVE: 'touchHandler:drag_move',
  6093.  
  6094.     // Fired just before TOUCH_END when a drag is released.  Correlates 1:1 with
  6095.     // a DRAG_START.
  6096.     DRAG_END: 'touchHandler:drag_end',
  6097.  
  6098.     // Fired whenever a touch that is being tracked has been released.
  6099.     // Correlates 1:1 with a TOUCH_START.
  6100.     TOUCH_END: 'touchHandler:touch_end',
  6101.  
  6102.     // Fired whenever the element is tapped in a short time and no dragging is
  6103.     // detected.
  6104.     TAP: 'touchHandler:tap'
  6105.   };
  6106.  
  6107.  
  6108.   /**
  6109.    * The type of event sent by TouchHandler
  6110.    * @constructor
  6111.    * @param {string} type The type of event (one of cr.ui.Grabber.EventType).
  6112.    * @param {boolean} bubbles Whether or not the event should bubble.
  6113.    * @param {number} clientX The X location of the touch.
  6114.    * @param {number} clientY The Y location of the touch.
  6115.    * @param {!Element} touchedElement The element at the current location of the
  6116.    *        touch.
  6117.    */
  6118.   TouchHandler.Event = function(type, bubbles, clientX, clientY,
  6119.       touchedElement) {
  6120.     var event = document.createEvent('Event');
  6121.     event.initEvent(type, bubbles, true);
  6122.     event.__proto__ = TouchHandler.Event.prototype;
  6123.  
  6124.     /**
  6125.      * The X location of the touch affected
  6126.      * @type {number}
  6127.      */
  6128.     event.clientX = clientX;
  6129.  
  6130.     /**
  6131.      * The Y location of the touch affected
  6132.      * @type {number}
  6133.      */
  6134.     event.clientY = clientY;
  6135.  
  6136.     /**
  6137.      * The element at the current location of the touch.
  6138.      * @type {!Element}
  6139.      */
  6140.     event.touchedElement = touchedElement;
  6141.  
  6142.     return event;
  6143.   };
  6144.  
  6145.   TouchHandler.Event.prototype = {
  6146.     __proto__: Event.prototype,
  6147.  
  6148.     /**
  6149.      * For TOUCH_START and DRAG START events, set to true to enable dragging or
  6150.      * false to disable dragging.
  6151.      * @type {boolean|undefined}
  6152.      */
  6153.     enableDrag: undefined,
  6154.  
  6155.     /**
  6156.      * For DRAG events, provides the horizontal component of the
  6157.      * drag delta. Drag delta is defined as the delta of the start touch
  6158.      * position and the current drag position.
  6159.      * @type {number|undefined}
  6160.      */
  6161.     dragDeltaX: undefined,
  6162.  
  6163.     /**
  6164.      * For DRAG events, provides the vertical component of the
  6165.      * drag delta.
  6166.      * @type {number|undefined}
  6167.      */
  6168.     dragDeltaY: undefined
  6169.   };
  6170.  
  6171.   /**
  6172.    * Maximum movement of touch required to be considered a tap.
  6173.    * @type {number}
  6174.    * @private
  6175.    */
  6176.   TouchHandler.MAX_TRACKING_FOR_TAP_ = 8;
  6177.  
  6178.  
  6179.   /**
  6180.    * The maximum number of ms to track a touch event. After an event is older
  6181.    * than this value, it will be ignored in velocity calculations.
  6182.    * @type {number}
  6183.    * @private
  6184.    */
  6185.   TouchHandler.MAX_TRACKING_TIME_ = 250;
  6186.  
  6187.  
  6188.   /**
  6189.    * The maximum number of touches to track.
  6190.    * @type {number}
  6191.    * @private
  6192.    */
  6193.   TouchHandler.MAX_TRACKING_TOUCHES_ = 5;
  6194.  
  6195.  
  6196.   /**
  6197.    * The maximum velocity to return, in pixels per millisecond, that is used
  6198.    * to guard against errors in calculating end velocity of a drag. This is a
  6199.    * very fast drag velocity.
  6200.    * @type {number}
  6201.    * @private
  6202.    */
  6203.   TouchHandler.MAXIMUM_VELOCITY_ = 5;
  6204.  
  6205.  
  6206.   /**
  6207.    * The velocity to return, in pixel per millisecond, when the time stamps on
  6208.    * the events are erroneous. The browser can return bad time stamps if the
  6209.    * thread is blocked for the duration of the drag. This is a low velocity to
  6210.    * prevent the content from moving quickly after a slow drag. It is less
  6211.    * jarring if the content moves slowly after a fast drag.
  6212.    * @type {number}
  6213.    * @private
  6214.    */
  6215.   TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ = 1;
  6216.  
  6217.   /**
  6218.    * The time, in milliseconds, that a touch must be held to be considered
  6219.    * 'long'.
  6220.    * @type {number}
  6221.    * @private
  6222.    */
  6223.   TouchHandler.TIME_FOR_LONG_PRESS_ = 500;
  6224.  
  6225.   TouchHandler.prototype = {
  6226.     /**
  6227.      * If defined, the identifer of the single touch that is active.  Note that
  6228.      * 0 is a valid touch identifier - it should not be treated equivalently to
  6229.      * undefined.
  6230.      * @type {number|undefined}
  6231.      * @private
  6232.      */
  6233.     activeTouch_: undefined,
  6234.  
  6235.     /**
  6236.      * @type {boolean|undefined}
  6237.      * @private
  6238.      */
  6239.     tracking_: undefined,
  6240.  
  6241.     /**
  6242.      * @type {number|undefined}
  6243.      * @private
  6244.      */
  6245.     startTouchX_: undefined,
  6246.  
  6247.     /**
  6248.      * @type {number|undefined}
  6249.      * @private
  6250.      */
  6251.     startTouchY_: undefined,
  6252.  
  6253.     /**
  6254.      * @type {number|undefined}
  6255.      * @private
  6256.      */
  6257.     endTouchX_: undefined,
  6258.  
  6259.     /**
  6260.      * @type {number|undefined}
  6261.      * @private
  6262.      */
  6263.     endTouchY_: undefined,
  6264.  
  6265.     /**
  6266.      * Time of the touchstart event.
  6267.      * @type {number|undefined}
  6268.      * @private
  6269.      */
  6270.     startTime_: undefined,
  6271.  
  6272.     /**
  6273.      * The time of the touchend event.
  6274.      * @type {number|undefined}
  6275.      * @private
  6276.      */
  6277.     endTime_: undefined,
  6278.  
  6279.     /**
  6280.      * @type {number|undefined}
  6281.      * @private
  6282.      */
  6283.     lastTouchX_: undefined,
  6284.  
  6285.     /**
  6286.      * @type {number|undefined}
  6287.      * @private
  6288.      */
  6289.     lastTouchY_: undefined,
  6290.  
  6291.     /**
  6292.      * @type {number|undefined}
  6293.      * @private
  6294.      */
  6295.     lastMoveX_: undefined,
  6296.  
  6297.     /**
  6298.      * @type {number|undefined}
  6299.      * @private
  6300.      */
  6301.     lastMoveY_: undefined,
  6302.  
  6303.     /**
  6304.      * @type {number|undefined}
  6305.      * @private
  6306.      */
  6307.     longPressTimeout_: undefined,
  6308.  
  6309.     /**
  6310.      * If defined and true, the next click event should be swallowed
  6311.      * @type {boolean|undefined}
  6312.      * @private
  6313.      */
  6314.     swallowNextClick_: undefined,
  6315.  
  6316.     /**
  6317.      * @type {boolean}
  6318.      * @private
  6319.      */
  6320.     draggingEnabled_: false,
  6321.  
  6322.     /**
  6323.      * Start listenting for events.
  6324.      * @param {boolean=} opt_capture True if the TouchHandler should listen to
  6325.      *      during the capture phase.
  6326.      * @param {boolean=} opt_mouse True if the TouchHandler should generate
  6327.      *      events for mouse input (in addition to touch input).
  6328.      */
  6329.     enable: function(opt_capture, opt_mouse) {
  6330.       var capture = !!opt_capture;
  6331.  
  6332.       // Just listen to start events for now. When a touch is occuring we'll
  6333.       // want to be subscribed to move and end events on the document, but we
  6334.       // don't want to incur the cost of lots of no-op handlers on the document.
  6335.       this.events_.add(this.element_, 'touchstart', this.onStart_.bind(this),
  6336.                        capture);
  6337.       if (opt_mouse) {
  6338.         this.events_.add(this.element_, 'mousedown',
  6339.                          this.mouseToTouchCallback_(this.onStart_.bind(this)),
  6340.                          capture);
  6341.       }
  6342.  
  6343.       // If the element is long-pressed, we may need to swallow a click
  6344.       this.events_.add(this.element_, 'click', this.onClick_.bind(this), true);
  6345.     },
  6346.  
  6347.     /**
  6348.      * Stop listening to all events.
  6349.      */
  6350.     disable: function() {
  6351.       this.stopTouching_();
  6352.       this.events_.removeAll();
  6353.     },
  6354.  
  6355.     /**
  6356.      * Wraps a callback with translations of mouse events to touch events.
  6357.      * NOTE: These types really should be function(Event) but then we couldn't
  6358.      * use this with bind (which operates on any type of function).  Doesn't
  6359.      * JSDoc support some sort of polymorphic types?
  6360.      * @param {Function} callback The event callback.
  6361.      * @return {Function} The wrapping callback.
  6362.      * @private
  6363.      */
  6364.     mouseToTouchCallback_: function(callback) {
  6365.       return function(e) {
  6366.         // Note that there may be synthesizes mouse events caused by touch
  6367.         // events (a mouseDown after a touch-click).  We leave it up to the
  6368.         // client to worry about this if it matters to them (typically a short
  6369.         // mouseDown/mouseUp without a click is no big problem and it's not
  6370.         // obvious how we identify such synthesized events in a general way).
  6371.         var touch = {
  6372.           // any fixed value will do for the identifier - there will only
  6373.           // ever be a single active 'touch' when using the mouse.
  6374.           identifier: 0,
  6375.           clientX: e.clientX,
  6376.           clientY: e.clientY,
  6377.           target: e.target
  6378.         };
  6379.         e.touches = [];
  6380.         e.targetTouches = [];
  6381.         e.changedTouches = [touch];
  6382.         if (e.type != 'mouseup') {
  6383.           e.touches[0] = touch;
  6384.           e.targetTouches[0] = touch;
  6385.         }
  6386.         callback(e);
  6387.       };
  6388.     },
  6389.  
  6390.     /**
  6391.      * Begin tracking the touchable element, it is eligible for dragging.
  6392.      * @private
  6393.      */
  6394.     beginTracking_: function() {
  6395.       this.tracking_ = true;
  6396.     },
  6397.  
  6398.     /**
  6399.      * Stop tracking the touchable element, it is no longer dragging.
  6400.      * @private
  6401.      */
  6402.     endTracking_: function() {
  6403.       this.tracking_ = false;
  6404.       this.dragging_ = false;
  6405.       this.totalMoveY_ = 0;
  6406.       this.totalMoveX_ = 0;
  6407.     },
  6408.  
  6409.     /**
  6410.      * Reset the touchable element as if we never saw the touchStart
  6411.      * Doesn't dispatch any end events - be careful of existing listeners.
  6412.      */
  6413.     cancelTouch: function() {
  6414.       this.stopTouching_();
  6415.       this.endTracking_();
  6416.       // If clients needed to be aware of this, we could fire a cancel event
  6417.       // here.
  6418.     },
  6419.  
  6420.     /**
  6421.      * Record that touching has stopped
  6422.      * @private
  6423.      */
  6424.     stopTouching_: function() {
  6425.       // Mark as no longer being touched
  6426.       this.activeTouch_ = undefined;
  6427.  
  6428.       // If we're waiting for a long press, stop
  6429.       window.clearTimeout(this.longPressTimeout_);
  6430.  
  6431.       // Stop listening for move/end events until there's another touch.
  6432.       // We don't want to leave handlers piled up on the document.
  6433.       // Note that there's no harm in removing handlers that weren't added, so
  6434.       // rather than track whether we're using mouse or touch we do both.
  6435.       this.events_.remove(document, 'touchmove');
  6436.       this.events_.remove(document, 'touchend');
  6437.       this.events_.remove(document, 'touchcancel');
  6438.       this.events_.remove(document, 'mousemove');
  6439.       this.events_.remove(document, 'mouseup');
  6440.     },
  6441.  
  6442.     /**
  6443.      * Touch start handler.
  6444.      * @param {!TouchEvent} e The touchstart event.
  6445.      * @private
  6446.      */
  6447.     onStart_: function(e) {
  6448.       // Only process single touches.  If there is already a touch happening, or
  6449.       // two simultaneous touches then just ignore them.
  6450.       if (e.touches.length > 1)
  6451.         // Note that we could cancel an active touch here.  That would make
  6452.         // simultaneous touch behave similar to near-simultaneous. However, if
  6453.         // the user is dragging something, an accidental second touch could be
  6454.         // quite disruptive if it cancelled their drag.  Better to just ignore
  6455.         // it.
  6456.         return;
  6457.  
  6458.       // It's still possible there could be an active "touch" if the user is
  6459.       // simultaneously using a mouse and a touch input.
  6460.       if (this.activeTouch_ !== undefined)
  6461.         return;
  6462.  
  6463.       var touch = e.targetTouches[0];
  6464.       this.activeTouch_ = touch.identifier;
  6465.  
  6466.       // We've just started touching so shouldn't swallow any upcoming click
  6467.       if (this.swallowNextClick_)
  6468.         this.swallowNextClick_ = false;
  6469.  
  6470.       this.disableTap_ = false;
  6471.  
  6472.       // Sign up for end/cancel notifications for this touch.
  6473.       // Note that we do this on the document so that even if the user drags
  6474.       // their finger off the element, we'll still know what they're doing.
  6475.       if (e.type == 'mousedown') {
  6476.         this.events_.add(document, 'mouseup',
  6477.             this.mouseToTouchCallback_(this.onEnd_.bind(this)), false);
  6478.       } else {
  6479.         this.events_.add(document, 'touchend', this.onEnd_.bind(this), false);
  6480.         this.events_.add(document, 'touchcancel', this.onEnd_.bind(this),
  6481.             false);
  6482.       }
  6483.  
  6484.       // This timeout is cleared on touchEnd and onDrag
  6485.       // If we invoke the function then we have a real long press
  6486.       window.clearTimeout(this.longPressTimeout_);
  6487.       this.longPressTimeout_ = window.setTimeout(
  6488.           this.onLongPress_.bind(this),
  6489.           TouchHandler.TIME_FOR_LONG_PRESS_);
  6490.  
  6491.       // Dispatch the TOUCH_START event
  6492.       this.draggingEnabled_ =
  6493.           !!this.dispatchEvent_(TouchHandler.EventType.TOUCH_START, touch);
  6494.  
  6495.       // We want dragging notifications
  6496.       if (e.type == 'mousedown') {
  6497.         this.events_.add(document, 'mousemove',
  6498.             this.mouseToTouchCallback_(this.onMove_.bind(this)), false);
  6499.       } else {
  6500.         this.events_.add(document, 'touchmove', this.onMove_.bind(this), false);
  6501.       }
  6502.  
  6503.       this.startTouchX_ = this.lastTouchX_ = touch.clientX;
  6504.       this.startTouchY_ = this.lastTouchY_ = touch.clientY;
  6505.       this.startTime_ = e.timeStamp;
  6506.  
  6507.       this.recentTouchesX_ = [];
  6508.       this.recentTouchesY_ = [];
  6509.       this.recentTouchesX_.push(touch.clientX, e.timeStamp);
  6510.       this.recentTouchesY_.push(touch.clientY, e.timeStamp);
  6511.  
  6512.       this.beginTracking_();
  6513.     },
  6514.  
  6515.     /**
  6516.      * Given a list of Touches, find the one matching our activeTouch
  6517.      * identifier. Note that Chrome currently always uses 0 as the identifier.
  6518.      * In that case we'll end up always choosing the first element in the list.
  6519.      * @param {TouchList} touches The list of Touch objects to search.
  6520.      * @return {!Touch|undefined} The touch matching our active ID if any.
  6521.      * @private
  6522.      */
  6523.     findActiveTouch_: function(touches) {
  6524.       assert(this.activeTouch_ !== undefined, 'Expecting an active touch');
  6525.       // A TouchList isn't actually an array, so we shouldn't use
  6526.       // Array.prototype.filter/some, etc.
  6527.       for (var i = 0; i < touches.length; i++) {
  6528.         if (touches[i].identifier == this.activeTouch_)
  6529.           return touches[i];
  6530.       }
  6531.       return undefined;
  6532.     },
  6533.  
  6534.     /**
  6535.      * Touch move handler.
  6536.      * @param {!TouchEvent} e The touchmove event.
  6537.      * @private
  6538.      */
  6539.     onMove_: function(e) {
  6540.       if (!this.tracking_)
  6541.         return;
  6542.  
  6543.       // Our active touch should always be in the list of touches still active
  6544.       assert(this.findActiveTouch_(e.touches), 'Missing touchEnd');
  6545.  
  6546.       var that = this;
  6547.       var touch = this.findActiveTouch_(e.changedTouches);
  6548.       if (!touch)
  6549.         return;
  6550.  
  6551.       var clientX = touch.clientX;
  6552.       var clientY = touch.clientY;
  6553.  
  6554.       var moveX = this.lastTouchX_ - clientX;
  6555.       var moveY = this.lastTouchY_ - clientY;
  6556.       this.totalMoveX_ += Math.abs(moveX);
  6557.       this.totalMoveY_ += Math.abs(moveY);
  6558.       this.lastTouchX_ = clientX;
  6559.       this.lastTouchY_ = clientY;
  6560.  
  6561.       var couldBeTap =
  6562.           this.totalMoveY_ <= TouchHandler.MAX_TRACKING_FOR_TAP_ ||
  6563.           this.totalMoveX_ <= TouchHandler.MAX_TRACKING_FOR_TAP_;
  6564.  
  6565.       if (!couldBeTap)
  6566.         this.disableTap_ = true;
  6567.  
  6568.       if (this.draggingEnabled_ && !this.dragging_ && !couldBeTap) {
  6569.         // If we're waiting for a long press, stop
  6570.         window.clearTimeout(this.longPressTimeout_);
  6571.  
  6572.         // Dispatch the DRAG_START event and record whether dragging should be
  6573.         // allowed or not.  Note that this relies on the current value of
  6574.         // startTouchX/Y - handlers may use the initial drag delta to determine
  6575.         // if dragging should be permitted.
  6576.         this.dragging_ = this.dispatchEvent_(
  6577.             TouchHandler.EventType.DRAG_START, touch);
  6578.  
  6579.         if (this.dragging_) {
  6580.           // Update the start position here so that drag deltas have better
  6581.           // values but don't touch the recent positions so that velocity
  6582.           // calculations can still use touchstart position in the time and
  6583.           // distance delta.
  6584.           this.startTouchX_ = clientX;
  6585.           this.startTouchY_ = clientY;
  6586.           this.startTime_ = e.timeStamp;
  6587.         } else {
  6588.           this.endTracking_();
  6589.         }
  6590.       }
  6591.  
  6592.       if (this.dragging_) {
  6593.         this.dispatchEvent_(TouchHandler.EventType.DRAG_MOVE, touch);
  6594.  
  6595.         this.removeTouchesInWrongDirection_(this.recentTouchesX_,
  6596.             this.lastMoveX_, moveX);
  6597.         this.removeTouchesInWrongDirection_(this.recentTouchesY_,
  6598.             this.lastMoveY_, moveY);
  6599.         this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
  6600.         this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
  6601.         this.recentTouchesX_.push(clientX, e.timeStamp);
  6602.         this.recentTouchesY_.push(clientY, e.timeStamp);
  6603.       }
  6604.  
  6605.       this.lastMoveX_ = moveX;
  6606.       this.lastMoveY_ = moveY;
  6607.     },
  6608.  
  6609.     /**
  6610.      * Filters the provided recent touches array to remove all touches except
  6611.      * the last if the move direction has changed.
  6612.      * @param {!Array.<number>} recentTouches An array of tuples where the first
  6613.      *     item is the x or y component of the recent touch and the second item
  6614.      *     is the touch time stamp.
  6615.      * @param {number|undefined} lastMove The x or y component of the previous
  6616.      *     move.
  6617.      * @param {number} recentMove The x or y component of the most recent move.
  6618.      * @private
  6619.      */
  6620.     removeTouchesInWrongDirection_: function(recentTouches, lastMove,
  6621.         recentMove) {
  6622.       if (lastMove && recentMove && recentTouches.length > 2 &&
  6623.           (lastMove > 0 ^ recentMove > 0)) {
  6624.         recentTouches.splice(0, recentTouches.length - 2);
  6625.       }
  6626.     },
  6627.  
  6628.     /**
  6629.      * Filters the provided recent touches array to remove all touches older
  6630.      * than the max tracking time or the 5th most recent touch.
  6631.      * @param {!Array.<number>} recentTouches An array of tuples where the first
  6632.      *     item is the x or y component of the recent touch and the second item
  6633.      *     is the touch time stamp.
  6634.      * @param {number} recentTime The time of the most recent event.
  6635.      * @private
  6636.      */
  6637.     removeOldTouches_: function(recentTouches, recentTime) {
  6638.       while (recentTouches.length && recentTime - recentTouches[1] >
  6639.           TouchHandler.MAX_TRACKING_TIME_ ||
  6640.           recentTouches.length >
  6641.               TouchHandler.MAX_TRACKING_TOUCHES_ * 2) {
  6642.         recentTouches.splice(0, 2);
  6643.       }
  6644.     },
  6645.  
  6646.     /**
  6647.      * Touch end handler.
  6648.      * @param {!TouchEvent} e The touchend event.
  6649.      * @private
  6650.      */
  6651.     onEnd_: function(e) {
  6652.       var that = this;
  6653.       assert(this.activeTouch_ !== undefined, 'Expect to already be touching');
  6654.  
  6655.       // If the touch we're tracking isn't changing here, ignore this touch end.
  6656.       var touch = this.findActiveTouch_(e.changedTouches);
  6657.       if (!touch) {
  6658.         // In most cases, our active touch will be in the 'touches' collection,
  6659.         // but we can't assert that because occasionally two touchend events can
  6660.         // occur at almost the same time with both having empty 'touches' lists.
  6661.         // I.e., 'touches' seems like it can be a bit more up-to-date than the
  6662.         // current event.
  6663.         return;
  6664.       }
  6665.  
  6666.       // This is touchEnd for the touch we're monitoring
  6667.       assert(!this.findActiveTouch_(e.touches),
  6668.              'Touch ended also still active');
  6669.  
  6670.       // Indicate that touching has finished
  6671.       this.stopTouching_();
  6672.  
  6673.       if (this.tracking_) {
  6674.         var clientX = touch.clientX;
  6675.         var clientY = touch.clientY;
  6676.  
  6677.         if (this.dragging_) {
  6678.           this.endTime_ = e.timeStamp;
  6679.           this.endTouchX_ = clientX;
  6680.           this.endTouchY_ = clientY;
  6681.  
  6682.           this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
  6683.           this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
  6684.  
  6685.           this.dispatchEvent_(TouchHandler.EventType.DRAG_END, touch);
  6686.  
  6687.           // Note that in some situations we can get a click event here as well.
  6688.           // For now this isn't a problem, but we may want to consider having
  6689.           // some logic that hides clicks that appear to be caused by a touchEnd
  6690.           // used for dragging.
  6691.         }
  6692.  
  6693.         this.endTracking_();
  6694.       }
  6695.       this.draggingEnabled_ = false;
  6696.  
  6697.       // Note that we dispatch the touchEnd event last so that events at
  6698.       // different levels of semantics nest nicely (similar to how DOM
  6699.       // drag-and-drop events are nested inside of the mouse events that trigger
  6700.       // them).
  6701.       this.dispatchEvent_(TouchHandler.EventType.TOUCH_END, touch);
  6702.       if (!this.disableTap_)
  6703.         this.dispatchEvent_(TouchHandler.EventType.TAP, touch);
  6704.     },
  6705.  
  6706.     /**
  6707.      * Get end velocity of the drag. This method is specific to drag behavior,
  6708.      * so if touch behavior and drag behavior is split then this should go with
  6709.      * drag behavior. End velocity is defined as deltaXY / deltaTime where
  6710.      * deltaXY is the difference between endPosition and the oldest recent
  6711.      * position, and deltaTime is the difference between endTime and the oldest
  6712.      * recent time stamp.
  6713.      * @return {Object} The x and y velocity.
  6714.      */
  6715.     getEndVelocity: function() {
  6716.       // Note that we could move velocity to just be an end-event parameter.
  6717.       var velocityX = this.recentTouchesX_.length ?
  6718.           (this.endTouchX_ - this.recentTouchesX_[0]) /
  6719.           (this.endTime_ - this.recentTouchesX_[1]) : 0;
  6720.       var velocityY = this.recentTouchesY_.length ?
  6721.           (this.endTouchY_ - this.recentTouchesY_[0]) /
  6722.           (this.endTime_ - this.recentTouchesY_[1]) : 0;
  6723.  
  6724.       velocityX = this.correctVelocity_(velocityX);
  6725.       velocityY = this.correctVelocity_(velocityY);
  6726.  
  6727.       return {
  6728.         x: velocityX,
  6729.         y: velocityY
  6730.       };
  6731.     },
  6732.  
  6733.     /**
  6734.      * Correct erroneous velocities by capping the velocity if we think it's too
  6735.      * high, or setting it to a default velocity if know that the event data is
  6736.      * bad.
  6737.      * @param {number} velocity The x or y velocity component.
  6738.      * @return {number} The corrected velocity.
  6739.      * @private
  6740.      */
  6741.     correctVelocity_: function(velocity) {
  6742.       var absVelocity = Math.abs(velocity);
  6743.  
  6744.       // We add to recent touches for each touchstart and touchmove. If we have
  6745.       // fewer than 3 touches (6 entries), we assume that the thread was blocked
  6746.       // for the duration of the drag and we received events in quick succession
  6747.       // with the wrong time stamps.
  6748.       if (absVelocity > TouchHandler.MAXIMUM_VELOCITY_) {
  6749.         absVelocity = this.recentTouchesY_.length < 3 ?
  6750.             TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ :
  6751.                 TouchHandler.MAXIMUM_VELOCITY_;
  6752.       }
  6753.       return absVelocity * (velocity < 0 ? -1 : 1);
  6754.     },
  6755.  
  6756.     /**
  6757.      * Handler when an element has been pressed for a long time
  6758.      * @private
  6759.      */
  6760.     onLongPress_: function() {
  6761.       // Swallow any click that occurs on this element without an intervening
  6762.       // touch start event.  This simple click-busting technique should be
  6763.       // sufficient here since a real click should have a touchstart first.
  6764.       this.swallowNextClick_ = true;
  6765.       this.disableTap_ = true;
  6766.  
  6767.       // Dispatch to the LONG_PRESS
  6768.       this.dispatchEventXY_(TouchHandler.EventType.LONG_PRESS, this.element_,
  6769.           this.startTouchX_, this.startTouchY_);
  6770.     },
  6771.  
  6772.     /**
  6773.      * Click handler - used to swallow clicks after a long-press
  6774.      * @param {!Event} e The click event.
  6775.      * @private
  6776.      */
  6777.     onClick_: function(e) {
  6778.       if (this.swallowNextClick_) {
  6779.         e.preventDefault();
  6780.         e.stopPropagation();
  6781.         this.swallowNextClick_ = false;
  6782.       }
  6783.     },
  6784.  
  6785.     /**
  6786.      * Dispatch a TouchHandler event to the element
  6787.      * @param {string} eventType The event to dispatch.
  6788.      * @param {Touch} touch The touch triggering this event.
  6789.      * @return {boolean|undefined} The value of enableDrag after dispatching
  6790.      *         the event.
  6791.      * @private
  6792.      */
  6793.     dispatchEvent_: function(eventType, touch) {
  6794.  
  6795.       // Determine which element was touched.  For mouse events, this is always
  6796.       // the event/touch target.  But for touch events, the target is always the
  6797.       // target of the touchstart (and it's unlikely we can change this
  6798.       // since the common implementation of touch dragging relies on it). Since
  6799.       // touch is our primary scenario (which we want to emulate with mouse),
  6800.       // we'll treat both cases the same and not depend on the target.
  6801.       var touchedElement;
  6802.       if (eventType == TouchHandler.EventType.TOUCH_START) {
  6803.         touchedElement = touch.target;
  6804.       } else {
  6805.         touchedElement = this.element_.ownerDocument.
  6806.             elementFromPoint(touch.clientX, touch.clientY);
  6807.       }
  6808.  
  6809.       return this.dispatchEventXY_(eventType, touchedElement, touch.clientX,
  6810.           touch.clientY);
  6811.     },
  6812.  
  6813.     /**
  6814.      * Dispatch a TouchHandler event to the element
  6815.      * @param {string} eventType The event to dispatch.
  6816.        @param {number} clientX The X location for the event.
  6817.        @param {number} clientY The Y location for the event.
  6818.      * @return {boolean|undefined} The value of enableDrag after dispatching
  6819.      *         the event.
  6820.      * @private
  6821.      */
  6822.     dispatchEventXY_: function(eventType, touchedElement, clientX, clientY) {
  6823.       var isDrag = (eventType == TouchHandler.EventType.DRAG_START ||
  6824.           eventType == TouchHandler.EventType.DRAG_MOVE ||
  6825.           eventType == TouchHandler.EventType.DRAG_END);
  6826.  
  6827.       // Drag events don't bubble - we're really just dragging the element,
  6828.       // not affecting its parent at all.
  6829.       var bubbles = !isDrag;
  6830.  
  6831.       var event = new TouchHandler.Event(eventType, bubbles, clientX, clientY,
  6832.           touchedElement);
  6833.  
  6834.       // Set enableDrag when it can be overridden
  6835.       if (eventType == TouchHandler.EventType.TOUCH_START)
  6836.         event.enableDrag = false;
  6837.       else if (eventType == TouchHandler.EventType.DRAG_START)
  6838.         event.enableDrag = true;
  6839.  
  6840.       if (isDrag) {
  6841.         event.dragDeltaX = clientX - this.startTouchX_;
  6842.         event.dragDeltaY = clientY - this.startTouchY_;
  6843.       }
  6844.  
  6845.       this.element_.dispatchEvent(event);
  6846.       return event.enableDrag;
  6847.     }
  6848.   };
  6849.  
  6850.   return {
  6851.     TouchHandler: TouchHandler
  6852.   };
  6853. });
  6854. </script>
  6855.  
  6856. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6857. // Use of this source code is governed by a BSD-style license that can be
  6858. // found in the LICENSE file.
  6859.  
  6860. cr.define('ntp', function() {
  6861.   'use strict';
  6862.  
  6863.   // We can't pass the currently dragging tile via dataTransfer because of
  6864.   // http://crbug.com/31037
  6865.   var currentlyDraggingTile = null;
  6866.   function getCurrentlyDraggingTile() {
  6867.     return currentlyDraggingTile;
  6868.   }
  6869.   function setCurrentlyDraggingTile(tile) {
  6870.     currentlyDraggingTile = tile;
  6871.     if (tile)
  6872.       ntp.enterRearrangeMode();
  6873.     else
  6874.       ntp.leaveRearrangeMode();
  6875.   }
  6876.  
  6877.   /**
  6878.    * Changes the current dropEffect of a drag. This modifies the native cursor
  6879.    * and serves as an indicator of what we should do at the end of the drag as
  6880.    * well as give indication to the user if a drop would succeed if they let go.
  6881.    * @param {DataTransfer} dataTransfer A dataTransfer object from a drag event.
  6882.    * @param {string} effect A drop effect to change to (i.e. copy, move, none).
  6883.    */
  6884.   function setCurrentDropEffect(dataTransfer, effect) {
  6885.     dataTransfer.dropEffect = effect;
  6886.     if (currentlyDraggingTile)
  6887.       currentlyDraggingTile.lastDropEffect = dataTransfer.dropEffect;
  6888.   }
  6889.  
  6890.   /**
  6891.    * Creates a new Tile object. Tiles wrap content on a TilePage, providing
  6892.    * some styling and drag functionality.
  6893.    * @constructor
  6894.    * @extends {HTMLDivElement}
  6895.    */
  6896.   function Tile(contents) {
  6897.     var tile = cr.doc.createElement('div');
  6898.     tile.__proto__ = Tile.prototype;
  6899.     tile.initialize(contents);
  6900.  
  6901.     return tile;
  6902.   }
  6903.  
  6904.   Tile.prototype = {
  6905.     __proto__: HTMLDivElement.prototype,
  6906.  
  6907.     initialize: function(contents) {
  6908.       // 'real' as opposed to doppleganger.
  6909.       this.className = 'tile real';
  6910.       this.appendChild(contents);
  6911.       contents.tile = this;
  6912.  
  6913.       this.addEventListener('dragstart', this.onDragStart_);
  6914.       this.addEventListener('drag', this.onDragMove_);
  6915.       this.addEventListener('dragend', this.onDragEnd_);
  6916.  
  6917.       this.firstChild.addEventListener(
  6918.           'webkitAnimationEnd', this.onContentsAnimationEnd_.bind(this));
  6919.  
  6920.       this.eventTracker = new EventTracker();
  6921.     },
  6922.  
  6923.     get index() {
  6924.       return Array.prototype.indexOf.call(this.tilePage.tileElements_, this);
  6925.     },
  6926.  
  6927.     get tilePage() {
  6928.       return findAncestorByClass(this, 'tile-page');
  6929.     },
  6930.  
  6931.     /**
  6932.      * Position the tile at |x, y|, and store this as the grid location, i.e.
  6933.      * where the tile 'belongs' when it's not being dragged.
  6934.      * @param {number} x The x coordinate, in pixels.
  6935.      * @param {number} y The y coordinate, in pixels.
  6936.      */
  6937.     setGridPosition: function(x, y) {
  6938.       this.gridX = x;
  6939.       this.gridY = y;
  6940.       this.moveTo(x, y);
  6941.     },
  6942.  
  6943.     /**
  6944.      * Position the tile at |x, y|.
  6945.      * @param {number} x The x coordinate, in pixels.
  6946.      * @param {number} y The y coordinate, in pixels.
  6947.      */
  6948.     moveTo: function(x, y) {
  6949.       // left overrides right in LTR, and right takes precedence in RTL.
  6950.       this.style.left = toCssPx(x);
  6951.       this.style.right = toCssPx(x);
  6952.       this.style.top = toCssPx(y);
  6953.     },
  6954.  
  6955.     /**
  6956.      * The handler for dragstart events fired on |this|.
  6957.      * @param {Event} e The event for the drag.
  6958.      * @private
  6959.      */
  6960.     onDragStart_: function(e) {
  6961.       // The user may start dragging again during a previous drag's finishing
  6962.       // animation.
  6963.       if (this.classList.contains('dragging'))
  6964.         this.finalizeDrag_();
  6965.  
  6966.       setCurrentlyDraggingTile(this);
  6967.  
  6968.       e.dataTransfer.effectAllowed = 'copyMove';
  6969.       this.firstChild.setDragData(e.dataTransfer);
  6970.  
  6971.       // The drag clone is the node we use as a representation during the drag.
  6972.       // It's attached to the top level document element so that it floats above
  6973.       // image masks.
  6974.       this.dragClone = this.cloneNode(true);
  6975.       this.dragClone.style.right = '';
  6976.       this.dragClone.classList.add('drag-representation');
  6977.       $('card-slider-frame').appendChild(this.dragClone);
  6978.       this.eventTracker.add(this.dragClone, 'webkitTransitionEnd',
  6979.                             this.onDragCloneTransitionEnd_.bind(this));
  6980.  
  6981.       this.classList.add('dragging');
  6982.       // offsetLeft is mirrored in RTL. Un-mirror it.
  6983.       var offsetLeft = isRTL() ?
  6984.           this.parentNode.clientWidth - this.offsetLeft :
  6985.           this.offsetLeft;
  6986.       this.dragOffsetX = e.x - offsetLeft - this.parentNode.offsetLeft;
  6987.       this.dragOffsetY = e.y - this.offsetTop -
  6988.           // Unlike offsetTop, this value takes scroll position into account.
  6989.           this.parentNode.getBoundingClientRect().top;
  6990.  
  6991.       this.onDragMove_(e);
  6992.     },
  6993.  
  6994.     /**
  6995.      * The handler for drag events fired on |this|.
  6996.      * @param {Event} e The event for the drag.
  6997.      * @private
  6998.      */
  6999.     onDragMove_: function(e) {
  7000.       if (e.view != window || (e.x == 0 && e.y == 0)) {
  7001.         this.dragClone.hidden = true;
  7002.         return;
  7003.       }
  7004.  
  7005.       this.dragClone.hidden = false;
  7006.       this.dragClone.style.left = toCssPx(e.x - this.dragOffsetX);
  7007.       this.dragClone.style.top = toCssPx(e.y - this.dragOffsetY);
  7008.     },
  7009.  
  7010.     /**
  7011.      * The handler for dragend events fired on |this|.
  7012.      * @param {Event} e The event for the drag.
  7013.      * @private
  7014.      */
  7015.     onDragEnd_: function(e) {
  7016.       this.dragClone.hidden = false;
  7017.       this.dragClone.classList.add('placing');
  7018.  
  7019.       setCurrentlyDraggingTile(null);
  7020.  
  7021.       // tilePage will be null if we've already been removed.
  7022.       var tilePage = this.tilePage;
  7023.       if (tilePage)
  7024.         tilePage.positionTile_(this.index);
  7025.  
  7026.       // Take an appropriate action with the drag clone.
  7027.       if (this.landedOnTrash) {
  7028.         this.dragClone.classList.add('deleting');
  7029.       } else if (tilePage) {
  7030.         // TODO(dbeam): Until we fix dropEffect to the correct behavior it will
  7031.         // differ on windows - crbug.com/39399.  That's why we use the custom
  7032.         // this.lastDropEffect instead of e.dataTransfer.dropEffect.
  7033.         if (tilePage.selected && this.lastDropEffect != 'copy') {
  7034.           // The drag clone can still be hidden from the last drag move event.
  7035.           this.dragClone.hidden = false;
  7036.           // The tile's contents may have moved following the respositioning;
  7037.           // adjust for that.
  7038.           var contentDiffX = this.dragClone.firstChild.offsetLeft -
  7039.               this.firstChild.offsetLeft;
  7040.           var contentDiffY = this.dragClone.firstChild.offsetTop -
  7041.               this.firstChild.offsetTop;
  7042.           this.dragClone.style.left =
  7043.               toCssPx(this.gridX + this.parentNode.offsetLeft -
  7044.                          contentDiffX);
  7045.           this.dragClone.style.top =
  7046.               toCssPx(this.gridY +
  7047.                          this.parentNode.getBoundingClientRect().top -
  7048.                          contentDiffY);
  7049.         } else if (this.dragClone.hidden) {
  7050.           this.finalizeDrag_();
  7051.         } else {
  7052.           // The CSS3 transitions spec intentionally leaves it up to individual
  7053.           // user agents to determine when styles should be applied. On some
  7054.           // platforms (at the moment, Windows), when you apply both classes
  7055.           // immediately a transition may not occur correctly. That's why we're
  7056.           // using a setTimeout here to queue adding the class until the
  7057.           // previous class (currently: .placing) sets up a transition.
  7058.           // http://dev.w3.org/csswg/css3-transitions/#starting
  7059.           window.setTimeout(function() {
  7060.             if (this.dragClone)
  7061.               this.dragClone.classList.add('dropped-on-other-page');
  7062.           }.bind(this), 0);
  7063.         }
  7064.       }
  7065.  
  7066.       delete this.lastDropEffect;
  7067.       this.landedOnTrash = false;
  7068.     },
  7069.  
  7070.     /**
  7071.      * Creates a clone of this node offset by the coordinates. Used for the
  7072.      * dragging effect where a tile appears to float off one side of the grid
  7073.      * and re-appear on the other.
  7074.      * @param {number} x x-axis offset, in pixels.
  7075.      * @param {number} y y-axis offset, in pixels.
  7076.      */
  7077.     showDoppleganger: function(x, y) {
  7078.       // We always have to clear the previous doppleganger to make sure we get
  7079.       // style updates for the contents of this tile.
  7080.       this.clearDoppleganger();
  7081.  
  7082.       var clone = this.cloneNode(true);
  7083.       clone.classList.remove('real');
  7084.       clone.classList.add('doppleganger');
  7085.       var clonelets = clone.querySelectorAll('.real');
  7086.       for (var i = 0; i < clonelets.length; i++) {
  7087.         clonelets[i].classList.remove('real');
  7088.       }
  7089.  
  7090.       this.appendChild(clone);
  7091.       this.doppleganger_ = clone;
  7092.  
  7093.       if (isRTL())
  7094.         x *= -1;
  7095.  
  7096.       this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' +
  7097.                                                                 y + 'px)';
  7098.     },
  7099.  
  7100.     /**
  7101.      * Destroys the current doppleganger.
  7102.      */
  7103.     clearDoppleganger: function() {
  7104.       if (this.doppleganger_) {
  7105.         this.removeChild(this.doppleganger_);
  7106.         this.doppleganger_ = null;
  7107.       }
  7108.     },
  7109.  
  7110.     /**
  7111.      * Returns status of doppleganger.
  7112.      * @return {boolean} True if there is a doppleganger showing for |this|.
  7113.      */
  7114.     hasDoppleganger: function() {
  7115.       return !!this.doppleganger_;
  7116.     },
  7117.  
  7118.     /**
  7119.      * Cleans up after the drag is over. This is either called when the
  7120.      * drag representation finishes animating to the final position, or when
  7121.      * the next drag starts (if the user starts a 2nd drag very quickly).
  7122.      * @private
  7123.      */
  7124.     finalizeDrag_: function() {
  7125.       assert(this.classList.contains('dragging'));
  7126.  
  7127.       var clone = this.dragClone;
  7128.       this.dragClone = null;
  7129.  
  7130.       clone.parentNode.removeChild(clone);
  7131.       this.eventTracker.remove(clone, 'webkitTransitionEnd');
  7132.       this.classList.remove('dragging');
  7133.       if (this.firstChild.finalizeDrag)
  7134.         this.firstChild.finalizeDrag();
  7135.     },
  7136.  
  7137.     /**
  7138.      * Called when the drag representation node is done migrating to its final
  7139.      * resting spot.
  7140.      * @param {Event} e The transition end event.
  7141.      */
  7142.     onDragCloneTransitionEnd_: function(e) {
  7143.       if (this.classList.contains('dragging') &&
  7144.           (e.propertyName == 'left' || e.propertyName == 'top' ||
  7145.            e.propertyName == '-webkit-transform')) {
  7146.         this.finalizeDrag_();
  7147.       }
  7148.     },
  7149.  
  7150.     /**
  7151.      * Called when an app is removed from Chrome. Animates its disappearance.
  7152.      * @param {boolean=} opt_animate Whether the animation should be animated.
  7153.      */
  7154.     doRemove: function(opt_animate) {
  7155.       if (opt_animate)
  7156.         this.firstChild.classList.add('removing-tile-contents');
  7157.       else
  7158.         this.tilePage.removeTile(this, false);
  7159.     },
  7160.  
  7161.     /**
  7162.      * Callback for the webkitAnimationEnd event on the tile's contents.
  7163.      * @param {Event} e The event object.
  7164.      */
  7165.     onContentsAnimationEnd_: function(e) {
  7166.       if (this.firstChild.classList.contains('new-tile-contents'))
  7167.         this.firstChild.classList.remove('new-tile-contents');
  7168.       if (this.firstChild.classList.contains('removing-tile-contents'))
  7169.         this.tilePage.removeTile(this, true);
  7170.     },
  7171.   };
  7172.  
  7173.   /**
  7174.    * Gives the proportion of the row width that is devoted to a single icon.
  7175.    * @param {number} rowTileCount The number of tiles in a row.
  7176.    * @param {number} tileSpacingFraction The proportion of the tile width which
  7177.    *     will be used as spacing between tiles.
  7178.    * @return {number} The ratio between icon width and row width.
  7179.    */
  7180.   function tileWidthFraction(rowTileCount, tileSpacingFraction) {
  7181.     return rowTileCount + (rowTileCount - 1) * tileSpacingFraction;
  7182.   }
  7183.  
  7184.   /**
  7185.    * Calculates an assortment of tile-related values for a grid with the
  7186.    * given dimensions.
  7187.    * @param {number} width The pixel width of the grid.
  7188.    * @param {number} numRowTiles The number of tiles in a row.
  7189.    * @param {number} tileSpacingFraction The proportion of the tile width which
  7190.    *     will be used as spacing between tiles.
  7191.    * @return {Object} A mapping of pixel values.
  7192.    */
  7193.   function tileValuesForGrid(width, numRowTiles, tileSpacingFraction) {
  7194.     var tileWidth = width / tileWidthFraction(numRowTiles, tileSpacingFraction);
  7195.     var offsetX = tileWidth * (1 + tileSpacingFraction);
  7196.     var interTileSpacing = offsetX - tileWidth;
  7197.  
  7198.     return {
  7199.       tileWidth: tileWidth,
  7200.       offsetX: offsetX,
  7201.       interTileSpacing: interTileSpacing,
  7202.     };
  7203.   }
  7204.  
  7205.   // The smallest amount of horizontal blank space to display on the sides when
  7206.   // displaying a wide arrangement. There is an additional 26px of margin from
  7207.   // the tile page padding.
  7208.   var MIN_WIDE_MARGIN = 18;
  7209.  
  7210.   /**
  7211.    * Creates a new TilePage object. This object contains tiles and controls
  7212.    * their layout.
  7213.    * @param {Object} gridValues Pixel values that define the size and layout
  7214.    *     of the tile grid.
  7215.    * @constructor
  7216.    * @extends {HTMLDivElement}
  7217.    */
  7218.   function TilePage(gridValues) {
  7219.     var el = cr.doc.createElement('div');
  7220.     el.gridValues_ = gridValues;
  7221.     el.__proto__ = TilePage.prototype;
  7222.     el.initialize();
  7223.  
  7224.     return el;
  7225.   }
  7226.  
  7227.   /**
  7228.    * Takes a collection of grid layout pixel values and updates them with
  7229.    * additional tiling values that are calculated from TilePage constants.
  7230.    * @param {Object} grid The grid layout pixel values to update.
  7231.    */
  7232.   TilePage.initGridValues = function(grid) {
  7233.     // The amount of space we need to display a narrow grid (all narrow grids
  7234.     // are this size).
  7235.     grid.narrowWidth =
  7236.         grid.minTileWidth * tileWidthFraction(grid.minColCount,
  7237.                                               grid.tileSpacingFraction);
  7238.     // The minimum amount of space we need to display a wide grid.
  7239.     grid.minWideWidth =
  7240.         grid.minTileWidth * tileWidthFraction(grid.maxColCount,
  7241.                                               grid.tileSpacingFraction);
  7242.     // The largest we will ever display a wide grid.
  7243.     grid.maxWideWidth =
  7244.         grid.maxTileWidth * tileWidthFraction(grid.maxColCount,
  7245.                                               grid.tileSpacingFraction);
  7246.     // Tile-related pixel values for the narrow display.
  7247.     grid.narrowTileValues = tileValuesForGrid(grid.narrowWidth,
  7248.                                               grid.minColCount,
  7249.                                               grid.tileSpacingFraction);
  7250.     // Tile-related pixel values for the minimum narrow display.
  7251.     grid.wideTileValues = tileValuesForGrid(grid.minWideWidth,
  7252.                                             grid.maxColCount,
  7253.                                             grid.tileSpacingFraction);
  7254.   };
  7255.  
  7256.   TilePage.prototype = {
  7257.     __proto__: HTMLDivElement.prototype,
  7258.  
  7259.     initialize: function() {
  7260.       this.className = 'tile-page';
  7261.  
  7262.       // Div that acts as a custom scrollbar. The scrollbar has to live
  7263.       // outside the content div so it doesn't flicker when scrolling (due to
  7264.       // repainting after the scroll, then repainting again when moved in the
  7265.       // onScroll handler). |scrollbar_| is only aesthetic, and it only
  7266.       // represents the thumb. Actual events are still handled by the invisible
  7267.       // native scrollbars. This div gives us more flexibility with the visuals.
  7268.       this.scrollbar_ = this.ownerDocument.createElement('div');
  7269.       this.scrollbar_.className = 'tile-page-scrollbar';
  7270.       this.scrollbar_.hidden = true;
  7271.       this.appendChild(this.scrollbar_);
  7272.  
  7273.       // This contains everything but the scrollbar.
  7274.       this.content_ = this.ownerDocument.createElement('div');
  7275.       this.content_.className = 'tile-page-content';
  7276.       this.appendChild(this.content_);
  7277.  
  7278.       // Div that sets the vertical position of the tile grid.
  7279.       this.topMargin_ = this.ownerDocument.createElement('div');
  7280.       this.topMargin_.className = 'top-margin';
  7281.       this.content_.appendChild(this.topMargin_);
  7282.  
  7283.       // Div that holds the tiles.
  7284.       this.tileGrid_ = this.ownerDocument.createElement('div');
  7285.       this.tileGrid_.className = 'tile-grid';
  7286.       this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px';
  7287.       this.tileGrid_.setAttribute('role', 'menu');
  7288.       this.tileGrid_.setAttribute('aria-label',
  7289.           loadTimeData.getString(
  7290.               'tile_grid_screenreader_accessible_description'));
  7291.  
  7292.       this.content_.appendChild(this.tileGrid_);
  7293.  
  7294.       // Ordered list of our tiles.
  7295.       this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real');
  7296.       // Ordered list of the elements which want to accept keyboard focus. These
  7297.       // elements will not be a part of the normal tab order; the tile grid
  7298.       // initially gets focused and then these elements can be focused via the
  7299.       // arrow keys.
  7300.       this.focusableElements_ =
  7301.           this.tileGrid_.getElementsByClassName('focusable');
  7302.  
  7303.       // These are properties used in updateTopMargin.
  7304.       this.animatedTopMarginPx_ = 0;
  7305.       this.topMarginPx_ = 0;
  7306.  
  7307.       this.eventTracker = new EventTracker();
  7308.       this.eventTracker.add(window, 'resize', this.onResize_.bind(this));
  7309.  
  7310.       this.addEventListener('DOMNodeInsertedIntoDocument',
  7311.                             this.onNodeInsertedIntoDocument_);
  7312.  
  7313.       this.content_.addEventListener('scroll', this.onScroll_.bind(this));
  7314.  
  7315.       this.dragWrapper_ = new cr.ui.DragWrapper(this.tileGrid_, this);
  7316.  
  7317.       this.addEventListener('cardselected', this.handleCardSelection_);
  7318.       this.addEventListener('carddeselected', this.handleCardDeselection_);
  7319.       this.addEventListener('focus', this.handleFocus_);
  7320.       this.addEventListener('keydown', this.handleKeyDown_);
  7321.       this.addEventListener('mousedown', this.handleMouseDown_);
  7322.  
  7323.       this.focusElementIndex_ = -1;
  7324.     },
  7325.  
  7326.     get tiles() {
  7327.       return this.tileElements_;
  7328.     },
  7329.  
  7330.     get tileCount() {
  7331.       return this.tileElements_.length;
  7332.     },
  7333.  
  7334.     get selected() {
  7335.       return Array.prototype.indexOf.call(this.parentNode.children, this) ==
  7336.           ntp.getCardSlider().currentCard;
  7337.     },
  7338.  
  7339.     /**
  7340.      * The size of the margin (unused space) on the sides of the tile grid, in
  7341.      * pixels.
  7342.      * @type {number}
  7343.      */
  7344.     get sideMargin() {
  7345.       return this.layoutValues_.leftMargin;
  7346.     },
  7347.  
  7348.     /**
  7349.      * Returns the width of the scrollbar, in pixels, if it is active, or 0
  7350.      * otherwise.
  7351.      * @type {number}
  7352.      */
  7353.     get scrollbarWidth() {
  7354.       return this.scrollbar_.hidden ? 0 : 13;
  7355.     },
  7356.  
  7357.     /**
  7358.      * Returns any extra padding to insert to the bottom of a tile page.  By
  7359.      * default there is none, but subclasses can override.
  7360.      * @type {number}
  7361.      */
  7362.     get extraBottomPadding() {
  7363.       return 0;
  7364.     },
  7365.  
  7366.     /**
  7367.      * The notification content of this tile (if any, otherwise null).
  7368.      * @type {!HTMLElement}
  7369.      */
  7370.     get notification() {
  7371.       return this.topMargin_.nextElementSibling.id == 'notification-container' ?
  7372.           this.topMargin_.nextElementSibling : null;
  7373.     },
  7374.     /**
  7375.      * The notification content of this tile (if any, otherwise null).
  7376.      * @type {!HTMLElement}
  7377.      */
  7378.     set notification(node) {
  7379.       assert(node instanceof HTMLElement, '|node| isn\'t an HTMLElement!');
  7380.       // NOTE: Implicitly removes from DOM if |node| is inside it.
  7381.       this.content_.insertBefore(node, this.topMargin_.nextElementSibling);
  7382.       this.positionNotification_();
  7383.     },
  7384.  
  7385.     /**
  7386.      * Fetches the size, in pixels, of the padding-top of the tile contents.
  7387.      * @type {number}
  7388.      */
  7389.     get contentPadding() {
  7390.       if (typeof this.contentPadding_ == 'undefined') {
  7391.         this.contentPadding_ =
  7392.             parseInt(getComputedStyle(this.content_).paddingTop, 10);
  7393.       }
  7394.       return this.contentPadding_;
  7395.     },
  7396.  
  7397.     /**
  7398.      * Removes the tilePage from the DOM and cleans up event handlers.
  7399.      */
  7400.     remove: function() {
  7401.       // This checks arguments.length as most remove functions have a boolean
  7402.       // |opt_animate| argument, but that's not necesarilly applicable to
  7403.       // removing a tilePage. Selecting a different card in an animated way and
  7404.       // deleting the card afterward is probably a better choice.
  7405.       assert(typeof arguments[0] != 'boolean',
  7406.              'This function takes no |opt_animate| argument.');
  7407.       this.tearDown_();
  7408.       this.parentNode.removeChild(this);
  7409.     },
  7410.  
  7411.     /**
  7412.      * Cleans up resources that are no longer needed after this TilePage
  7413.      * instance is removed from the DOM.
  7414.      * @private
  7415.      */
  7416.     tearDown_: function() {
  7417.       this.eventTracker.removeAll();
  7418.     },
  7419.  
  7420.     /**
  7421.      * Appends a tile to the end of the tile grid.
  7422.      * @param {HTMLElement} tileElement The contents of the tile.
  7423.      * @param {boolean} animate If true, the append will be animated.
  7424.      * @protected
  7425.      */
  7426.     appendTile: function(tileElement, animate) {
  7427.       this.addTileAt(tileElement, this.tileElements_.length, animate);
  7428.     },
  7429.  
  7430.     /**
  7431.      * Adds the given element to the tile grid.
  7432.      * @param {Node} tileElement The tile object/node to insert.
  7433.      * @param {number} index The location in the tile grid to insert it at.
  7434.      * @param {boolean} animate If true, the tile in question will be
  7435.      *     animated (other tiles, if they must reposition, do not animate).
  7436.      * @protected
  7437.      */
  7438.     addTileAt: function(tileElement, index, animate) {
  7439.       this.classList.remove('animating-tile-page');
  7440.       if (animate)
  7441.         tileElement.classList.add('new-tile-contents');
  7442.  
  7443.       // Make sure the index is positive and either in the the bounds of
  7444.       // this.tileElements_ or at the end (meaning append).
  7445.       assert(index >= 0 && index <= this.tileElements_.length);
  7446.  
  7447.       var wrapperDiv = new Tile(tileElement);
  7448.       // If is out of the bounds of the tile element list, .insertBefore() will
  7449.       // act just like appendChild().
  7450.       this.tileGrid_.insertBefore(wrapperDiv, this.tileElements_[index]);
  7451.       this.calculateLayoutValues_();
  7452.       this.heightChanged_();
  7453.  
  7454.       this.repositionTiles_();
  7455.  
  7456.       // If this is the first tile being added, make it focusable after add.
  7457.       if (this.focusableElements_.length == 1)
  7458.         this.updateFocusableElement_();
  7459.       this.fireAddedEvent(wrapperDiv, index, animate);
  7460.     },
  7461.  
  7462.     /**
  7463.      * Notify interested subscribers that a tile has been removed from this
  7464.      * page.
  7465.      * @param {Tile} tile The newly added tile.
  7466.      * @param {number} index The index of the tile that was added.
  7467.      * @param {boolean} wasAnimated Whether the removal was animated.
  7468.      */
  7469.     fireAddedEvent: function(tile, index, wasAnimated) {
  7470.       var e = document.createEvent('Event');
  7471.       e.initEvent('tilePage:tile_added', true, true);
  7472.       e.addedIndex = index;
  7473.       e.addedTile = tile;
  7474.       e.wasAnimated = wasAnimated;
  7475.       this.dispatchEvent(e);
  7476.     },
  7477.  
  7478.     /**
  7479.      * Removes the given tile and animates the repositioning of the other tiles.
  7480.      * @param {boolean=} opt_animate Whether the removal should be animated.
  7481.      * @param {boolean=} opt_dontNotify Whether a page should be removed if the
  7482.      *     last tile is removed from it.
  7483.      */
  7484.     removeTile: function(tile, opt_animate, opt_dontNotify) {
  7485.       if (opt_animate)
  7486.         this.classList.add('animating-tile-page');
  7487.       var index = tile.index;
  7488.       tile.parentNode.removeChild(tile);
  7489.       this.calculateLayoutValues_();
  7490.       this.cleanupDrag();
  7491.  
  7492.       if (!opt_dontNotify)
  7493.         this.fireRemovedEvent(tile, index, !!opt_animate);
  7494.     },
  7495.  
  7496.     /**
  7497.      * Notify interested subscribers that a tile has been removed from this
  7498.      * page.
  7499.      * @param {Tile} tile The tile that was removed.
  7500.      * @param {number} oldIndex Where the tile was positioned before removal.
  7501.      * @param {boolean} wasAnimated Whether the removal was animated.
  7502.      */
  7503.     fireRemovedEvent: function(tile, oldIndex, wasAnimated) {
  7504.       var e = document.createEvent('Event');
  7505.       e.initEvent('tilePage:tile_removed', true, true);
  7506.       e.removedIndex = oldIndex;
  7507.       e.removedTile = tile;
  7508.       e.wasAnimated = wasAnimated;
  7509.       this.dispatchEvent(e);
  7510.     },
  7511.  
  7512.     /**
  7513.      * Removes all tiles from the page.
  7514.      */
  7515.     removeAllTiles: function() {
  7516.       this.tileGrid_.innerHTML = '';
  7517.     },
  7518.  
  7519.     /**
  7520.      * Called when the page is selected (in the card selector).
  7521.      * @param {Event} e A custom cardselected event.
  7522.      * @private
  7523.      */
  7524.     handleCardSelection_: function(e) {
  7525.       this.updateFocusableElement_();
  7526.  
  7527.       // When we are selected, we re-calculate the layout values. (See comment
  7528.       // in doDrop.)
  7529.       this.calculateLayoutValues_();
  7530.     },
  7531.  
  7532.     /**
  7533.      * Called when the page loses selection (in the card selector).
  7534.      * @param {Event} e A custom carddeselected event.
  7535.      * @private
  7536.      */
  7537.     handleCardDeselection_: function(e) {
  7538.       if (this.currentFocusElement_)
  7539.         this.currentFocusElement_.tabIndex = -1;
  7540.     },
  7541.  
  7542.     /**
  7543.      * When we get focus, pass it on to the focus element.
  7544.      * @param {Event} e The focus event.
  7545.      * @private
  7546.      */
  7547.     handleFocus_: function(e) {
  7548.       if (this.focusableElements_.length == 0)
  7549.         return;
  7550.  
  7551.       this.updateFocusElement_();
  7552.     },
  7553.  
  7554.     /**
  7555.      * Since we are doing custom focus handling, we have to manually
  7556.      * set focusability on click (as well as keyboard nav above).
  7557.      * @param {Event} e The focus event.
  7558.      * @private
  7559.      */
  7560.     handleMouseDown_: function(e) {
  7561.       var focusable = findAncestorByClass(e.target, 'focusable');
  7562.       if (focusable) {
  7563.         this.focusElementIndex_ =
  7564.             Array.prototype.indexOf.call(this.focusableElements_,
  7565.                                          focusable);
  7566.         this.updateFocusElement_();
  7567.       } else {
  7568.         // This prevents the tile page from getting focus when the user clicks
  7569.         // inside the grid but outside of any tile.
  7570.         e.preventDefault();
  7571.       }
  7572.     },
  7573.  
  7574.     /**
  7575.      * Handle arrow key focus nav.
  7576.      * @param {Event} e The focus event.
  7577.      * @private
  7578.      */
  7579.     handleKeyDown_: function(e) {
  7580.       // We only handle up, down, left, right without control keys.
  7581.       if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey)
  7582.         return;
  7583.  
  7584.       // Wrap the given index to |this.focusableElements_|.
  7585.       var wrap = function(idx) {
  7586.         return (idx + this.focusableElements_.length) %
  7587.             this.focusableElements_.length;
  7588.       }.bind(this);
  7589.  
  7590.       switch (e.keyIdentifier) {
  7591.         case 'Right':
  7592.         case 'Left':
  7593.           var direction = e.keyIdentifier == 'Right' ? 1 : -1;
  7594.           this.focusElementIndex_ = wrap(this.focusElementIndex_ + direction);
  7595.           break;
  7596.         case 'Up':
  7597.         case 'Down':
  7598.           // Look through all focusable elements. Find the first one that is
  7599.           // in the same column.
  7600.           var direction = e.keyIdentifier == 'Up' ? -1 : 1;
  7601.           var currentIndex =
  7602.               Array.prototype.indexOf.call(this.focusableElements_,
  7603.                                            this.currentFocusElement_);
  7604.           var newFocusIdx = wrap(currentIndex + direction);
  7605.           var tile = this.currentFocusElement_.parentNode;
  7606.           for (;; newFocusIdx = wrap(newFocusIdx + direction)) {
  7607.             var newTile = this.focusableElements_[newFocusIdx].parentNode;
  7608.             var rowTiles = this.layoutValues_.numRowTiles;
  7609.             if ((newTile.index - tile.index) % rowTiles == 0)
  7610.               break;
  7611.           }
  7612.  
  7613.           this.focusElementIndex_ = newFocusIdx;
  7614.           break;
  7615.  
  7616.         default:
  7617.           return;
  7618.       }
  7619.  
  7620.       this.updateFocusElement_();
  7621.  
  7622.       e.preventDefault();
  7623.       e.stopPropagation();
  7624.     },
  7625.  
  7626.     /**
  7627.      * Ensure 0 <= this.focusElementIndex_ < this.focusableElements_.length,
  7628.      * make the focusable element at this.focusElementIndex_ (if any) eligible
  7629.      * for tab focus, and the previously-focused element not eligible.
  7630.      * @private
  7631.      */
  7632.     updateFocusableElement_: function() {
  7633.       if (this.focusableElements_.length == 0 || !this.selected) {
  7634.         this.focusElementIndex_ = -1;
  7635.         return;
  7636.       }
  7637.  
  7638.       this.focusElementIndex_ = Math.min(this.focusableElements_.length - 1,
  7639.                                          this.focusElementIndex_);
  7640.       this.focusElementIndex_ = Math.max(0, this.focusElementIndex_);
  7641.  
  7642.       var newFocusElement = this.focusableElements_[this.focusElementIndex_];
  7643.       var lastFocusElement = this.currentFocusElement_;
  7644.       if (lastFocusElement && lastFocusElement != newFocusElement)
  7645.         lastFocusElement.tabIndex = -1;
  7646.  
  7647.       newFocusElement.tabIndex = 1;
  7648.     },
  7649.  
  7650.     /**
  7651.      * Focuses the element at |this.focusElementIndex_|. Makes the previous
  7652.      * focus element, if any, no longer eligible for tab focus.
  7653.      * @private
  7654.      */
  7655.     updateFocusElement_: function() {
  7656.       this.updateFocusableElement_();
  7657.       if (this.focusElementIndex_ >= 0)
  7658.         this.focusableElements_[this.focusElementIndex_].focus();
  7659.     },
  7660.  
  7661.     /**
  7662.      * The current focus element is that element which is eligible for focus.
  7663.      * @type {HTMLElement} The node.
  7664.      * @private
  7665.      */
  7666.     get currentFocusElement_() {
  7667.       return this.querySelector('.focusable[tabindex="1"]');
  7668.     },
  7669.  
  7670.     /**
  7671.      * Makes some calculations for tile layout. These change depending on
  7672.      * height, width, and the number of tiles.
  7673.      * TODO(estade): optimize calls to this function. Do nothing if the page is
  7674.      * hidden, but call before being shown.
  7675.      * @private
  7676.      */
  7677.     calculateLayoutValues_: function() {
  7678.       var grid = this.gridValues_;
  7679.       var availableSpace = this.tileGrid_.clientWidth - 2 * MIN_WIDE_MARGIN;
  7680.       var wide = availableSpace >= grid.minWideWidth;
  7681.       var numRowTiles = wide ? grid.maxColCount : grid.minColCount;
  7682.  
  7683.       var effectiveGridWidth = wide ?
  7684.           Math.min(Math.max(availableSpace, grid.minWideWidth),
  7685.                    grid.maxWideWidth) :
  7686.           grid.narrowWidth;
  7687.       var realTileValues = tileValuesForGrid(effectiveGridWidth, numRowTiles,
  7688.                                              grid.tileSpacingFraction);
  7689.  
  7690.       // leftMargin centers the grid within the avaiable space.
  7691.       var minMargin = wide ? MIN_WIDE_MARGIN : 0;
  7692.       var leftMargin =
  7693.           Math.max(minMargin,
  7694.                    (this.tileGrid_.clientWidth - effectiveGridWidth) / 2);
  7695.  
  7696.       var rowHeight = this.heightForWidth(realTileValues.tileWidth) +
  7697.           realTileValues.interTileSpacing;
  7698.  
  7699.       this.layoutValues_ = {
  7700.         colWidth: realTileValues.offsetX,
  7701.         gridWidth: effectiveGridWidth,
  7702.         leftMargin: leftMargin,
  7703.         numRowTiles: numRowTiles,
  7704.         rowHeight: rowHeight,
  7705.         tileWidth: realTileValues.tileWidth,
  7706.         wide: wide,
  7707.       };
  7708.  
  7709.       // We need to update the top margin as well.
  7710.       this.updateTopMargin_();
  7711.  
  7712.       this.firePageLayoutEvent_();
  7713.     },
  7714.  
  7715.     /**
  7716.      * Dispatches the custom pagelayout event.
  7717.      * @private
  7718.      */
  7719.     firePageLayoutEvent_: function() {
  7720.       cr.dispatchSimpleEvent(this, 'pagelayout', true, true);
  7721.     },
  7722.  
  7723.     /**
  7724.      * @return {number} The amount of margin that should be animated (in pixels)
  7725.      *     for the current grid layout.
  7726.      */
  7727.     getAnimatedLeftMargin_: function() {
  7728.       if (this.layoutValues_.wide)
  7729.         return 0;
  7730.  
  7731.       var grid = this.gridValues_;
  7732.       return (grid.minWideWidth - MIN_WIDE_MARGIN - grid.narrowWidth) / 2;
  7733.     },
  7734.  
  7735.     /**
  7736.      * Calculates the x/y coordinates for an element and moves it there.
  7737.      * @param {number} index The index of the element to be positioned.
  7738.      * @param {number} indexOffset If provided, this is added to |index| when
  7739.      *     positioning the tile. The effect is that the tile will be positioned
  7740.      *     in a non-default location.
  7741.      * @private
  7742.      */
  7743.     positionTile_: function(index, indexOffset) {
  7744.       var grid = this.gridValues_;
  7745.       var layout = this.layoutValues_;
  7746.  
  7747.       indexOffset = typeof indexOffset != 'undefined' ? indexOffset : 0;
  7748.       // Add the offset _after_ the modulus division. We might want to show the
  7749.       // tile off the side of the grid.
  7750.       var col = index % layout.numRowTiles + indexOffset;
  7751.       var row = Math.floor(index / layout.numRowTiles);
  7752.       // Calculate the final on-screen position for the tile.
  7753.       var realX = col * layout.colWidth + layout.leftMargin;
  7754.       var realY = row * layout.rowHeight;
  7755.  
  7756.       // Calculate the portion of the tile's position that should be animated.
  7757.       var animatedTileValues = layout.wide ?
  7758.           grid.wideTileValues : grid.narrowTileValues;
  7759.       // Animate the difference between three-wide and six-wide.
  7760.       var animatedLeftMargin = this.getAnimatedLeftMargin_();
  7761.       var animatedX = col * animatedTileValues.offsetX + animatedLeftMargin;
  7762.       var animatedY = row * (this.heightForWidth(animatedTileValues.tileWidth) +
  7763.                              animatedTileValues.interTileSpacing);
  7764.  
  7765.       var tile = this.tileElements_[index];
  7766.       tile.setGridPosition(animatedX, animatedY);
  7767.       tile.firstChild.setBounds(layout.tileWidth,
  7768.                                 realX - animatedX,
  7769.                                 realY - animatedY);
  7770.  
  7771.       // This code calculates whether the tile needs to show a clone of itself
  7772.       // wrapped around the other side of the tile grid.
  7773.       var offTheRight = col == layout.numRowTiles ||
  7774.           (col == layout.numRowTiles - 1 && tile.hasDoppleganger());
  7775.       var offTheLeft = col == -1 || (col == 0 && tile.hasDoppleganger());
  7776.       if (this.isCurrentDragTarget && (offTheRight || offTheLeft)) {
  7777.         var sign = offTheRight ? 1 : -1;
  7778.         tile.showDoppleganger(-layout.numRowTiles * layout.colWidth * sign,
  7779.                               layout.rowHeight * sign);
  7780.       } else {
  7781.         tile.clearDoppleganger();
  7782.       }
  7783.  
  7784.       if (index == this.tileElements_.length - 1) {
  7785.         this.tileGrid_.style.height = (realY + layout.rowHeight) + 'px';
  7786.         this.queueUpdateScrollbars_();
  7787.       }
  7788.     },
  7789.  
  7790.     /**
  7791.      * Gets the index of the tile that should occupy coordinate (x, y). Note
  7792.      * that this function doesn't care where the tiles actually are, and will
  7793.      * return an index even for the space between two tiles. This function is
  7794.      * effectively the inverse of |positionTile_|.
  7795.      * @param {number} x The x coordinate, in pixels, relative to the left of
  7796.      *     |this|.
  7797.      * @param {number} y The y coordinate, in pixels, relative to the top of
  7798.      *     |this|.
  7799.      * @private
  7800.      */
  7801.     getWouldBeIndexForPoint_: function(x, y) {
  7802.       var grid = this.gridValues_;
  7803.       var layout = this.layoutValues_;
  7804.  
  7805.       var gridClientRect = this.tileGrid_.getBoundingClientRect();
  7806.       var col = Math.floor((x - gridClientRect.left - layout.leftMargin) /
  7807.                            layout.colWidth);
  7808.       if (col < 0 || col >= layout.numRowTiles)
  7809.         return -1;
  7810.  
  7811.       if (isRTL())
  7812.         col = layout.numRowTiles - 1 - col;
  7813.  
  7814.       var row = Math.floor((y - gridClientRect.top) / layout.rowHeight);
  7815.       return row * layout.numRowTiles + col;
  7816.     },
  7817.  
  7818.     /**
  7819.      * Window resize event handler. Window resizes may trigger re-layouts.
  7820.      * @param {Object} e The resize event.
  7821.      */
  7822.     onResize_: function(e) {
  7823.       if (this.lastWidth_ == this.clientWidth &&
  7824.           this.lastHeight_ == this.clientHeight) {
  7825.         return;
  7826.       }
  7827.  
  7828.       this.calculateLayoutValues_();
  7829.  
  7830.       this.lastWidth_ = this.clientWidth;
  7831.       this.lastHeight_ = this.clientHeight;
  7832.       this.classList.add('animating-tile-page');
  7833.       this.heightChanged_();
  7834.  
  7835.       this.positionNotification_();
  7836.       this.repositionTiles_();
  7837.     },
  7838.  
  7839.     /**
  7840.      * The tile grid has an image mask which fades at the edges. We only show
  7841.      * the mask when there is an active drag; it obscures doppleganger tiles
  7842.      * as they enter or exit the grid.
  7843.      * @private
  7844.      */
  7845.     updateMask_: function() {
  7846.       if (!this.isCurrentDragTarget) {
  7847.         this.tileGrid_.style.WebkitMaskBoxImage = '';
  7848.         return;
  7849.       }
  7850.  
  7851.       var leftMargin = this.layoutValues_.leftMargin;
  7852.       // The fade distance is the space between tiles.
  7853.       var fadeDistance = (this.gridValues_.tileSpacingFraction *
  7854.           this.layoutValues_.tileWidth);
  7855.       fadeDistance = Math.min(leftMargin, fadeDistance);
  7856.       // On Skia we don't use any fade because it works very poorly. See
  7857.       // http://crbug.com/99373
  7858.       if (!cr.isMac)
  7859.         fadeDistance = 1;
  7860.       var gradient =
  7861.           '-webkit-linear-gradient(left,' +
  7862.               'transparent, ' +
  7863.               'transparent ' + (leftMargin - fadeDistance) + 'px, ' +
  7864.               'black ' + leftMargin + 'px, ' +
  7865.               'black ' + (this.tileGrid_.clientWidth - leftMargin) + 'px, ' +
  7866.               'transparent ' + (this.tileGrid_.clientWidth - leftMargin +
  7867.                                 fadeDistance) + 'px, ' +
  7868.               'transparent)';
  7869.       this.tileGrid_.style.WebkitMaskBoxImage = gradient;
  7870.     },
  7871.  
  7872.     updateTopMargin_: function() {
  7873.       var layout = this.layoutValues_;
  7874.  
  7875.       // The top margin is set so that the vertical midpoint of the grid will
  7876.       // be 1/3 down the page.
  7877.       var numTiles = this.tileCount +
  7878.           (this.isCurrentDragTarget && !this.withinPageDrag_ ? 1 : 0);
  7879.       var numRows = Math.max(1, Math.ceil(numTiles / layout.numRowTiles));
  7880.       var usedHeight = layout.rowHeight * numRows;
  7881.       var newMargin = document.documentElement.clientHeight / 3 -
  7882.           usedHeight / 3 - this.contentPadding;
  7883.       // The 'height' style attribute of topMargin is non-zero to work around
  7884.       // webkit's collapsing margin behavior, so we have to factor that into
  7885.       // our calculations here.
  7886.       newMargin = Math.max(newMargin, 0) - this.topMargin_.offsetHeight;
  7887.  
  7888.       // |newMargin| is the final margin we actually want to show. However,
  7889.       // part of that should be animated and part should not (for the same
  7890.       // reason as with leftMargin). The approach is to consider differences
  7891.       // when the layout changes from wide to narrow or vice versa as
  7892.       // 'animatable'. These differences accumulate in animatedTopMarginPx_,
  7893.       // while topMarginPx_ caches the real (total) margin. Either of these
  7894.       // calculations may come out to be negative, so we use margins as the
  7895.       // css property.
  7896.  
  7897.       if (typeof this.topMarginIsForWide_ == 'undefined')
  7898.         this.topMarginIsForWide_ = layout.wide;
  7899.       if (this.topMarginIsForWide_ != layout.wide) {
  7900.         this.animatedTopMarginPx_ += newMargin - this.topMarginPx_;
  7901.         this.topMargin_.style.marginBottom = toCssPx(this.animatedTopMarginPx_);
  7902.       }
  7903.  
  7904.       this.topMarginIsForWide_ = layout.wide;
  7905.       this.topMarginPx_ = newMargin;
  7906.       this.topMargin_.style.marginTop =
  7907.           toCssPx(this.topMarginPx_ - this.animatedTopMarginPx_);
  7908.     },
  7909.  
  7910.     /**
  7911.      * Position the notification if there's one showing.
  7912.      */
  7913.     positionNotification_: function() {
  7914.       var notification = this.notification;
  7915.       if (!notification || notification.hidden)
  7916.         return;
  7917.  
  7918.       // Update the horizontal position.
  7919.       var animatedLeftMargin = this.getAnimatedLeftMargin_();
  7920.       notification.style.WebkitMarginStart = animatedLeftMargin + 'px';
  7921.       var leftOffset = (this.layoutValues_.leftMargin - animatedLeftMargin) *
  7922.                        (isRTL() ? -1 : 1);
  7923.       notification.style.WebkitTransform = 'translateX(' + leftOffset + 'px)';
  7924.  
  7925.       // Update the allowable widths of the text.
  7926.       var buttonWidth = notification.querySelector('button').offsetWidth + 8;
  7927.       notification.querySelector('span').style.maxWidth =
  7928.           this.layoutValues_.gridWidth - buttonWidth + 'px';
  7929.  
  7930.       // This makes sure the text doesn't condense smaller than the narrow size
  7931.       // of the grid (e.g. when a user makes the window really small).
  7932.       notification.style.minWidth =
  7933.           this.gridValues_.narrowWidth - buttonWidth + 'px';
  7934.  
  7935.       // Update the top position.
  7936.       notification.style.marginTop = -notification.offsetHeight + 'px';
  7937.     },
  7938.  
  7939.     /**
  7940.      * Handles final setup that can only happen after |this| is inserted into
  7941.      * the page.
  7942.      * @private
  7943.      */
  7944.     onNodeInsertedIntoDocument_: function(e) {
  7945.       this.calculateLayoutValues_();
  7946.       this.heightChanged_();
  7947.     },
  7948.  
  7949.     /**
  7950.      * Called when the height of |this| has changed: update the size of
  7951.      * tileGrid.
  7952.      * @private
  7953.      */
  7954.     heightChanged_: function() {
  7955.       // The tile grid will expand to the bottom footer, or enough to hold all
  7956.       // the tiles, whichever is greater. It would be nicer if tilePage were
  7957.       // a flex box, and the tile grid could be box-flex: 1, but this exposes a
  7958.       // bug where repositioning tiles will cause the scroll position to reset.
  7959.       this.tileGrid_.style.minHeight = (this.clientHeight -
  7960.           this.tileGrid_.offsetTop - this.content_.offsetTop -
  7961.           this.extraBottomPadding -
  7962.           (this.footerNode_ ? this.footerNode_.clientHeight : 0)) + 'px';
  7963.     },
  7964.  
  7965.      /**
  7966.       * Places an element at the bottom of the content div. Used in bare-minimum
  7967.       * mode to hold #footer.
  7968.       * @param {HTMLElement} footerNode The node to append to content.
  7969.       */
  7970.     appendFooter: function(footerNode) {
  7971.       this.footerNode_ = footerNode;
  7972.       this.content_.appendChild(footerNode);
  7973.     },
  7974.  
  7975.     /**
  7976.      * Scrolls the page in response to an mousewheel event, although the event
  7977.      * may have been triggered on a different element. Return true if the
  7978.      * event triggered scrolling, and false otherwise.
  7979.      * This is called explicitly, which allows a consistent experience whether
  7980.      * the user scrolls on the page or on the page switcher, because this
  7981.      * function provides a common conversion factor between wheel delta and
  7982.      * scroll delta.
  7983.      * @param {Event} e The mousewheel event.
  7984.      */
  7985.     handleMouseWheel: function(e) {
  7986.       if (e.wheelDeltaY == 0)
  7987.         return false;
  7988.  
  7989.       this.content_.scrollTop -= e.wheelDeltaY / 3;
  7990.       return true;
  7991.     },
  7992.  
  7993.     /**
  7994.      * Handler for the 'scroll' event on |content_|.
  7995.      * @param {Event} e The scroll event.
  7996.      * @private
  7997.      */
  7998.     onScroll_: function(e) {
  7999.       this.queueUpdateScrollbars_();
  8000.     },
  8001.  
  8002.     /**
  8003.      * ID of scrollbar update timer. If 0, there's no scrollbar re-calc queued.
  8004.      * @private
  8005.      */
  8006.     scrollbarUpdate_: 0,
  8007.  
  8008.     /**
  8009.      * Queues an update on the custom scrollbar. Used for two reasons: first,
  8010.      * coalescing of multiple updates, and second, because action like
  8011.      * repositioning a tile can require a delay before they affect values
  8012.      * like clientHeight.
  8013.      * @private
  8014.      */
  8015.     queueUpdateScrollbars_: function() {
  8016.       if (this.scrollbarUpdate_)
  8017.         return;
  8018.  
  8019.       this.scrollbarUpdate_ = window.setTimeout(
  8020.           this.doUpdateScrollbars_.bind(this), 0);
  8021.     },
  8022.  
  8023.     /**
  8024.      * Does the work of calculating the visibility, height and position of the
  8025.      * scrollbar thumb (there is no track or buttons).
  8026.      * @private
  8027.      */
  8028.     doUpdateScrollbars_: function() {
  8029.       this.scrollbarUpdate_ = 0;
  8030.  
  8031.       var content = this.content_;
  8032.  
  8033.       // Adjust scroll-height to account for possible header-bar.
  8034.       var adjustedScrollHeight = content.scrollHeight - content.offsetTop;
  8035.  
  8036.       if (adjustedScrollHeight <= content.clientHeight) {
  8037.         this.scrollbar_.hidden = true;
  8038.         return;
  8039.       } else {
  8040.         this.scrollbar_.hidden = false;
  8041.       }
  8042.  
  8043.       var thumbTop = content.offsetTop +
  8044.           content.scrollTop / adjustedScrollHeight * content.clientHeight;
  8045.       var thumbHeight = content.clientHeight / adjustedScrollHeight *
  8046.           this.clientHeight;
  8047.  
  8048.       this.scrollbar_.style.top = thumbTop + 'px';
  8049.       this.scrollbar_.style.height = thumbHeight + 'px';
  8050.       this.firePageLayoutEvent_();
  8051.     },
  8052.  
  8053.     /**
  8054.      * Get the height for a tile of a certain width. Override this function to
  8055.      * get non-square tiles.
  8056.      * @param {number} width The pixel width of a tile.
  8057.      * @return {number} The height for |width|.
  8058.      */
  8059.     heightForWidth: function(width) {
  8060.       return width;
  8061.     },
  8062.  
  8063.     /** Dragging **/
  8064.  
  8065.     get isCurrentDragTarget() {
  8066.       return this.dragWrapper_.isCurrentDragTarget;
  8067.     },
  8068.  
  8069.     /**
  8070.      * Thunk for dragleave events fired on |tileGrid_|.
  8071.      * @param {Event} e A MouseEvent for the drag.
  8072.      */
  8073.     doDragLeave: function(e) {
  8074.       this.cleanupDrag();
  8075.     },
  8076.  
  8077.     /**
  8078.      * Performs all actions necessary when a drag enters the tile page.
  8079.      * @param {Event} e A mouseover event for the drag enter.
  8080.      */
  8081.     doDragEnter: function(e) {
  8082.       // Applies the mask so doppleganger tiles disappear into the fog.
  8083.       this.updateMask_();
  8084.  
  8085.       this.classList.add('animating-tile-page');
  8086.       this.withinPageDrag_ = this.contains(currentlyDraggingTile);
  8087.       this.dragItemIndex_ = this.withinPageDrag_ ?
  8088.           currentlyDraggingTile.index : this.tileElements_.length;
  8089.       this.currentDropIndex_ = this.dragItemIndex_;
  8090.  
  8091.       // The new tile may change the number of rows, hence the top margin
  8092.       // will change.
  8093.       if (!this.withinPageDrag_)
  8094.         this.updateTopMargin_();
  8095.  
  8096.       this.doDragOver(e);
  8097.     },
  8098.  
  8099.     /**
  8100.      * Performs all actions necessary when the user moves the cursor during
  8101.      * a drag over the tile page.
  8102.      * @param {Event} e A mouseover event for the drag over.
  8103.      */
  8104.     doDragOver: function(e) {
  8105.       e.preventDefault();
  8106.  
  8107.       this.setDropEffect(e.dataTransfer);
  8108.       var newDragIndex = this.getWouldBeIndexForPoint_(e.pageX, e.pageY);
  8109.       if (newDragIndex < 0 || newDragIndex >= this.tileElements_.length)
  8110.         newDragIndex = this.dragItemIndex_;
  8111.       this.updateDropIndicator_(newDragIndex);
  8112.     },
  8113.  
  8114.     /**
  8115.      * Performs all actions necessary when the user completes a drop.
  8116.      * @param {Event} e A mouseover event for the drag drop.
  8117.      */
  8118.     doDrop: function(e) {
  8119.       e.stopPropagation();
  8120.       e.preventDefault();
  8121.  
  8122.       var index = this.currentDropIndex_;
  8123.       // Only change data if this was not a 'null drag'.
  8124.       if (!((index == this.dragItemIndex_) && this.withinPageDrag_)) {
  8125.         var adjustedIndex = this.currentDropIndex_ +
  8126.             (index > this.dragItemIndex_ ? 1 : 0);
  8127.         if (this.withinPageDrag_) {
  8128.           this.tileGrid_.insertBefore(
  8129.               currentlyDraggingTile,
  8130.               this.tileElements_[adjustedIndex]);
  8131.           this.tileMoved(currentlyDraggingTile, this.dragItemIndex_);
  8132.         } else {
  8133.           var originalPage = currentlyDraggingTile ?
  8134.               currentlyDraggingTile.tilePage : null;
  8135.           this.addDragData(e.dataTransfer, adjustedIndex);
  8136.           if (originalPage)
  8137.             originalPage.cleanupDrag();
  8138.         }
  8139.  
  8140.         // Dropping the icon may cause topMargin to change, but changing it
  8141.         // now would cause everything to move (annoying), so we leave it
  8142.         // alone. The top margin will be re-calculated next time the window is
  8143.         // resized or the page is selected.
  8144.       }
  8145.  
  8146.       this.classList.remove('animating-tile-page');
  8147.       this.cleanupDrag();
  8148.     },
  8149.  
  8150.     /**
  8151.      * Appends the currently dragged tile to the end of the page. Called
  8152.      * from outside the page, e.g. when dropping on a nav dot.
  8153.      */
  8154.     appendDraggingTile: function() {
  8155.       var originalPage = currentlyDraggingTile.tilePage;
  8156.       if (originalPage == this)
  8157.         return;
  8158.  
  8159.       this.addDragData(null, this.tileElements_.length);
  8160.       if (originalPage)
  8161.         originalPage.cleanupDrag();
  8162.     },
  8163.  
  8164.     /**
  8165.      * Makes sure all the tiles are in the right place after a drag is over.
  8166.      */
  8167.     cleanupDrag: function() {
  8168.       this.repositionTiles_(currentlyDraggingTile);
  8169.       // Remove the drag mask.
  8170.       this.updateMask_();
  8171.     },
  8172.  
  8173.     /**
  8174.      * Reposition all the tiles (possibly ignoring one).
  8175.      * @param {?Node} ignoreNode An optional node to ignore.
  8176.      * @private
  8177.      */
  8178.     repositionTiles_: function(ignoreNode) {
  8179.       for (var i = 0; i < this.tileElements_.length; i++) {
  8180.         if (!ignoreNode || ignoreNode !== this.tileElements_[i])
  8181.           this.positionTile_(i);
  8182.       }
  8183.     },
  8184.  
  8185.     /**
  8186.      * Updates the visual indicator for the drop location for the active drag.
  8187.      * @param {Event} e A MouseEvent for the drag.
  8188.      * @private
  8189.      */
  8190.     updateDropIndicator_: function(newDragIndex) {
  8191.       var oldDragIndex = this.currentDropIndex_;
  8192.       if (newDragIndex == oldDragIndex)
  8193.         return;
  8194.  
  8195.       var repositionStart = Math.min(newDragIndex, oldDragIndex);
  8196.       var repositionEnd = Math.max(newDragIndex, oldDragIndex);
  8197.  
  8198.       for (var i = repositionStart; i <= repositionEnd; i++) {
  8199.         if (i == this.dragItemIndex_)
  8200.           continue;
  8201.         else if (i > this.dragItemIndex_)
  8202.           var adjustment = i <= newDragIndex ? -1 : 0;
  8203.         else
  8204.           var adjustment = i >= newDragIndex ? 1 : 0;
  8205.  
  8206.         this.positionTile_(i, adjustment);
  8207.       }
  8208.       this.currentDropIndex_ = newDragIndex;
  8209.     },
  8210.  
  8211.     /**
  8212.      * Checks if a page can accept a drag with the given data.
  8213.      * @param {Event} e The drag event if the drag object. Implementations will
  8214.      *     likely want to check |e.dataTransfer|.
  8215.      * @return {boolean} True if this page can handle the drag.
  8216.      */
  8217.     shouldAcceptDrag: function(e) {
  8218.       return false;
  8219.     },
  8220.  
  8221.     /**
  8222.      * Called to accept a drag drop. Will not be called for in-page drops.
  8223.      * @param {Object} dataTransfer The data transfer object that holds the drop
  8224.      *     data. This should only be used if currentlyDraggingTile is null.
  8225.      * @param {number} index The tile index at which the drop occurred.
  8226.      */
  8227.     addDragData: function(dataTransfer, index) {
  8228.       assert(false);
  8229.     },
  8230.  
  8231.     /**
  8232.      * Called when a tile has been moved (via dragging). Override this to make
  8233.      * backend updates.
  8234.      * @param {Node} draggedTile The tile that was dropped.
  8235.      * @param {number} prevIndex The previous index of the tile.
  8236.      */
  8237.     tileMoved: function(draggedTile, prevIndex) {
  8238.     },
  8239.  
  8240.     /**
  8241.      * Sets the drop effect on |dataTransfer| to the desired value (e.g.
  8242.      * 'copy').
  8243.      * @param {Object} dataTransfer The drag event dataTransfer object.
  8244.      */
  8245.     setDropEffect: function(dataTransfer) {
  8246.       assert(false);
  8247.     },
  8248.   };
  8249.  
  8250.   return {
  8251.     getCurrentlyDraggingTile: getCurrentlyDraggingTile,
  8252.     setCurrentDropEffect: setCurrentDropEffect,
  8253.     TilePage: TilePage,
  8254.   };
  8255. });
  8256. </script>
  8257. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8258. // Use of this source code is governed by a BSD-style license that can be
  8259. // found in the LICENSE file.
  8260.  
  8261. cr.define('ntp', function() {
  8262.   'use strict';
  8263.  
  8264.   var APP_LAUNCH = {
  8265.     // The histogram buckets (keep in sync with extension_constants.h).
  8266.     NTP_APPS_MAXIMIZED: 0,
  8267.     NTP_APPS_COLLAPSED: 1,
  8268.     NTP_APPS_MENU: 2,
  8269.     NTP_MOST_VISITED: 3,
  8270.     NTP_RECENTLY_CLOSED: 4,
  8271.     NTP_APP_RE_ENABLE: 16,
  8272.     NTP_WEBSTORE_FOOTER: 18,
  8273.     NTP_WEBSTORE_PLUS_ICON: 19,
  8274.   };
  8275.  
  8276.   // Histogram buckets for UMA tracking of where a DnD drop came from.
  8277.   var DRAG_SOURCE = {
  8278.     SAME_APPS_PANE: 0,
  8279.     OTHER_APPS_PANE: 1,
  8280.     MOST_VISITED_PANE: 2,
  8281.     BOOKMARKS_PANE: 3,
  8282.     OUTSIDE_NTP: 4
  8283.   };
  8284.   var DRAG_SOURCE_LIMIT = DRAG_SOURCE.OUTSIDE_NTP + 1;
  8285.  
  8286.   /**
  8287.    * App context menu. The class is designed to be used as a singleton with
  8288.    * the app that is currently showing a context menu stored in this.app_.
  8289.    * @constructor
  8290.    */
  8291.   function AppContextMenu() {
  8292.     this.__proto__ = AppContextMenu.prototype;
  8293.     this.initialize();
  8294.   }
  8295.   cr.addSingletonGetter(AppContextMenu);
  8296.  
  8297.   AppContextMenu.prototype = {
  8298.     initialize: function() {
  8299.       var menu = new cr.ui.Menu;
  8300.       cr.ui.decorate(menu, cr.ui.Menu);
  8301.       menu.classList.add('app-context-menu');
  8302.       this.menu = menu;
  8303.  
  8304.       this.launch_ = this.appendMenuItem_();
  8305.       this.launch_.addEventListener('activate', this.onLaunch_.bind(this));
  8306.  
  8307.       menu.appendChild(cr.ui.MenuItem.createSeparator());
  8308.       this.launchRegularTab_ = this.appendMenuItem_('applaunchtyperegular');
  8309.       this.launchPinnedTab_ = this.appendMenuItem_('applaunchtypepinned');
  8310.       if (!cr.isMac)
  8311.         this.launchNewWindow_ = this.appendMenuItem_('applaunchtypewindow');
  8312.       this.launchFullscreen_ = this.appendMenuItem_('applaunchtypefullscreen');
  8313.  
  8314.       var self = this;
  8315.       this.forAllLaunchTypes_(function(launchTypeButton, id) {
  8316.         launchTypeButton.addEventListener('activate',
  8317.             self.onLaunchTypeChanged_.bind(self));
  8318.       });
  8319.  
  8320.       this.launchTypeMenuSeparator_ = cr.ui.MenuItem.createSeparator();
  8321.       menu.appendChild(this.launchTypeMenuSeparator_);
  8322.       this.options_ = this.appendMenuItem_('appoptions');
  8323.       this.details_ = this.appendMenuItem_('appdetails');
  8324.       this.disableNotifications_ =
  8325.           this.appendMenuItem_('appdisablenotifications');
  8326.       this.uninstall_ = this.appendMenuItem_('appuninstall');
  8327.       this.options_.addEventListener('activate',
  8328.                                      this.onShowOptions_.bind(this));
  8329.       this.details_.addEventListener('activate',
  8330.                                      this.onShowDetails_.bind(this));
  8331.       this.disableNotifications_.addEventListener(
  8332.           'activate', this.onDisableNotifications_.bind(this));
  8333.       this.uninstall_.addEventListener('activate',
  8334.                                        this.onUninstall_.bind(this));
  8335.  
  8336.       if (!cr.isMac && !cr.isChromeOS) {
  8337.         menu.appendChild(cr.ui.MenuItem.createSeparator());
  8338.         this.createShortcut_ = this.appendMenuItem_('appcreateshortcut');
  8339.         this.createShortcut_.addEventListener(
  8340.             'activate', this.onCreateShortcut_.bind(this));
  8341.       }
  8342.  
  8343.       document.body.appendChild(menu);
  8344.     },
  8345.  
  8346.     /**
  8347.      * Appends a menu item to |this.menu|.
  8348.      * @param {?String} textId If non-null, the ID for the localized string
  8349.      *     that acts as the item's label.
  8350.      */
  8351.     appendMenuItem_: function(textId) {
  8352.       var button = cr.doc.createElement('button');
  8353.       this.menu.appendChild(button);
  8354.       cr.ui.decorate(button, cr.ui.MenuItem);
  8355.       if (textId)
  8356.         button.textContent = loadTimeData.getString(textId);
  8357.       return button;
  8358.     },
  8359.  
  8360.     /**
  8361.      * Iterates over all the launch type menu items.
  8362.      * @param {function(cr.ui.MenuItem, number)} f The function to call for each
  8363.      *     menu item. The parameters to the function include the menu item and
  8364.      *     the associated launch ID.
  8365.      */
  8366.     forAllLaunchTypes_: function(f) {
  8367.       // Order matters: index matches launchType id.
  8368.       var launchTypes = [this.launchPinnedTab_,
  8369.                          this.launchRegularTab_,
  8370.                          this.launchFullscreen_,
  8371.                          this.launchNewWindow_];
  8372.  
  8373.       for (var i = 0; i < launchTypes.length; ++i) {
  8374.         if (!launchTypes[i])
  8375.           continue;
  8376.  
  8377.         f(launchTypes[i], i);
  8378.       }
  8379.     },
  8380.  
  8381.     /**
  8382.      * Does all the necessary setup to show the menu for the given app.
  8383.      * @param {App} app The App object that will be showing a context menu.
  8384.      */
  8385.     setupForApp: function(app) {
  8386.       this.app_ = app;
  8387.  
  8388.       this.launch_.textContent = app.appData.title;
  8389.  
  8390.       this.forAllLaunchTypes_(function(launchTypeButton, id) {
  8391.         launchTypeButton.disabled = false;
  8392.         launchTypeButton.checked = app.appData.launch_type == id;
  8393.         launchTypeButton.hidden = app.appData.packagedApp;
  8394.       });
  8395.  
  8396.       this.launchTypeMenuSeparator_.hidden = app.appData.packagedApp;
  8397.  
  8398.       this.options_.disabled = !app.appData.optionsUrl || !app.appData.enabled;
  8399.       this.details_.disabled = !app.appData.detailsUrl;
  8400.       this.uninstall_.disabled = !app.appData.mayDisable;
  8401.  
  8402.       this.disableNotifications_.hidden = true;
  8403.       var notificationsDisabled = app.appData.notifications_disabled;
  8404.       if (typeof notificationsDisabled != 'undefined') {
  8405.         this.disableNotifications_.hidden = false;
  8406.         this.disableNotifications_.checked = notificationsDisabled;
  8407.       }
  8408.     },
  8409.  
  8410.     /**
  8411.      * Handlers for menu item activation.
  8412.      * @param {Event} e The activation event.
  8413.      * @private
  8414.      */
  8415.     onLaunch_: function(e) {
  8416.       chrome.send('launchApp', [this.app_.appId, APP_LAUNCH.NTP_APPS_MENU]);
  8417.     },
  8418.     onLaunchTypeChanged_: function(e) {
  8419.       var pressed = e.currentTarget;
  8420.       var app = this.app_;
  8421.       this.forAllLaunchTypes_(function(launchTypeButton, id) {
  8422.         if (launchTypeButton == pressed) {
  8423.           chrome.send('setLaunchType', [app.appId, id]);
  8424.           // Manually update the launch type. We will only get
  8425.           // appsPrefChangeCallback calls after changes to other NTP instances.
  8426.           app.appData.launch_type = id;
  8427.         }
  8428.       });
  8429.     },
  8430.     onShowOptions_: function(e) {
  8431.       window.location = this.app_.appData.optionsUrl;
  8432.     },
  8433.     onShowDetails_: function(e) {
  8434.       var url = this.app_.appData.detailsUrl;
  8435.       url = appendParam(url, 'utm_source', 'chrome-ntp-launcher');
  8436.       window.location = url;
  8437.     },
  8438.     onDisableNotifications_: function(e) {
  8439.       var app = this.app_;
  8440.       app.removeBubble();
  8441.       // Toggle the current disable setting.
  8442.       var newSetting = !this.disableNotifications_.checked;
  8443.       app.appData.notifications_disabled = newSetting;
  8444.       chrome.send('setNotificationsDisabled', [app.appData.id, newSetting]);
  8445.     },
  8446.     onUninstall_: function(e) {
  8447.       chrome.send('uninstallApp', [this.app_.appData.id]);
  8448.     },
  8449.     onCreateShortcut_: function(e) {
  8450.       chrome.send('createAppShortcut', [this.app_.appData.id]);
  8451.     },
  8452.   };
  8453.  
  8454.   /**
  8455.    * Creates a new App object.
  8456.    * @param {Object} appData The data object that describes the app.
  8457.    * @constructor
  8458.    * @extends {HTMLDivElement}
  8459.    */
  8460.   function App(appData) {
  8461.     var el = cr.doc.createElement('div');
  8462.     el.__proto__ = App.prototype;
  8463.     el.initialize(appData);
  8464.  
  8465.     return el;
  8466.   }
  8467.  
  8468.   App.prototype = {
  8469.     __proto__: HTMLDivElement.prototype,
  8470.  
  8471.     /**
  8472.      * Initialize the app object.
  8473.      * @param {Object} appData The data object that describes the app.
  8474.      */
  8475.     initialize: function(appData) {
  8476.       this.appData = appData;
  8477.       assert(this.appData_.id, 'Got an app without an ID');
  8478.       this.id = this.appData_.id;
  8479.       this.setAttribute('role', 'menuitem');
  8480.  
  8481.       this.className = 'app focusable';
  8482.  
  8483.       if (!this.appData_.icon_big_exists && this.appData_.icon_small_exists)
  8484.         this.useSmallIcon_ = true;
  8485.  
  8486.       this.appContents_ = this.useSmallIcon_ ?
  8487.           $('app-small-icon-template').cloneNode(true) :
  8488.           $('app-large-icon-template').cloneNode(true);
  8489.       this.appContents_.id = '';
  8490.       this.appendChild(this.appContents_);
  8491.  
  8492.       this.appImgContainer_ = this.querySelector('.app-img-container');
  8493.       this.appImg_ = this.appImgContainer_.querySelector('img');
  8494.       this.setIcon();
  8495.  
  8496.       if (this.useSmallIcon_) {
  8497.         this.imgDiv_ = this.querySelector('.app-icon-div');
  8498.         this.addLaunchClickTarget_(this.imgDiv_);
  8499.         this.imgDiv_.title = this.appData_.title;
  8500.         chrome.send('getAppIconDominantColor', [this.id]);
  8501.       } else {
  8502.         this.addLaunchClickTarget_(this.appImgContainer_);
  8503.         this.appImgContainer_.title = this.appData_.title;
  8504.       }
  8505.  
  8506.       var appSpan = this.appContents_.querySelector('.title');
  8507.       appSpan.textContent = appSpan.title = this.appData_.title;
  8508.       this.addLaunchClickTarget_(appSpan);
  8509.  
  8510.       var notification = this.appData_.notification;
  8511.       var hasNotification = typeof notification != 'undefined' &&
  8512.                             typeof notification['title'] != 'undefined' &&
  8513.                             typeof notification['body'] != 'undefined' &&
  8514.                             !this.appData_.notifications_disabled;
  8515.       if (hasNotification)
  8516.         this.setupNotification_(notification);
  8517.  
  8518.       this.addEventListener('keydown', cr.ui.contextMenuHandler);
  8519.       this.addEventListener('keyup', cr.ui.contextMenuHandler);
  8520.  
  8521.       // This hack is here so that appContents.contextMenu will be the same as
  8522.       // this.contextMenu.
  8523.       var self = this;
  8524.       this.appContents_.__defineGetter__('contextMenu', function() {
  8525.         return self.contextMenu;
  8526.       });
  8527.       this.appContents_.addEventListener('contextmenu',
  8528.                                          cr.ui.contextMenuHandler);
  8529.  
  8530.       this.addEventListener('mousedown', this.onMousedown_, true);
  8531.       this.addEventListener('keydown', this.onKeydown_);
  8532.       this.addEventListener('keyup', this.onKeyup_);
  8533.     },
  8534.  
  8535.     /**
  8536.      * Sets the color of the favicon dominant color bar.
  8537.      * @param {string} color The css-parsable value for the color.
  8538.      */
  8539.     set stripeColor(color) {
  8540.       this.querySelector('.color-stripe').style.backgroundColor = color;
  8541.     },
  8542.  
  8543.     /**
  8544.      * Removes the app tile from the page. Should be called after the app has
  8545.      * been uninstalled.
  8546.      */
  8547.     remove: function(opt_animate) {
  8548.       // Unset the ID immediately, because the app is already gone. But leave
  8549.       // the tile on the page as it animates out.
  8550.       this.id = '';
  8551.       this.tile.doRemove(opt_animate);
  8552.     },
  8553.  
  8554.     /**
  8555.      * Set the URL of the icon from |appData_|. This won't actually show the
  8556.      * icon until loadIcon() is called (for performance reasons; we don't want
  8557.      * to load icons until we have to).
  8558.      */
  8559.     setIcon: function() {
  8560.       var src = this.useSmallIcon_ ? this.appData_.icon_small :
  8561.                                      this.appData_.icon_big;
  8562.       if (!this.appData_.enabled ||
  8563.           (!this.appData_.offlineEnabled && !navigator.onLine)) {
  8564.         src += '?grayscale=true';
  8565.       }
  8566.  
  8567.       this.appImgSrc_ = src;
  8568.       this.classList.add('icon-loading');
  8569.     },
  8570.  
  8571.     /**
  8572.      * Shows the icon for the app. That is, it causes chrome to load the app
  8573.      * icon resource.
  8574.      */
  8575.     loadIcon: function() {
  8576.       if (this.appImgSrc_) {
  8577.         this.appImg_.src = this.appImgSrc_;
  8578.         this.appImg_.classList.remove('invisible');
  8579.         this.appImgSrc_ = null;
  8580.       }
  8581.  
  8582.       this.classList.remove('icon-loading');
  8583.     },
  8584.  
  8585.     /**
  8586.      * Creates a bubble node.
  8587.      * @param {Object} notification The notification to show in the bubble.
  8588.      * @param {boolean} full Whether we want the headline or just the content.
  8589.      * @private
  8590.      */
  8591.     createBubbleNode_: function(notification, full) {
  8592.       if (!full) {
  8593.         var titleItem = this.ownerDocument.createElement('span');
  8594.         titleItem.textContent = notification['title'];
  8595.         return titleItem;
  8596.       } else {
  8597.         var container = this.ownerDocument.createElement('div');
  8598.  
  8599.         var messageItem = this.ownerDocument.createElement('div');
  8600.         messageItem.textContent = notification['body'];
  8601.         container.appendChild(messageItem);
  8602.  
  8603.         if (notification['linkUrl'] && notification['linkText']) {
  8604.           var anchor = this.ownerDocument.createElement('a');
  8605.           anchor.href = notification['linkUrl'];
  8606.           anchor.textContent = notification['linkText'];
  8607.           container.appendChild(anchor);
  8608.         }
  8609.  
  8610.         return container;
  8611.       }
  8612.     },
  8613.  
  8614.     /**
  8615.      * Sets up a notification for the app icon.
  8616.      * @param {Object} notification The notification to show in the bubble.
  8617.      * @private
  8618.      */
  8619.     setupNotification_: function(notification) {
  8620.       if (notification) {
  8621.         var infoBubble;
  8622.         if (!this.currentBubbleShowing_) {
  8623.           // Create a new bubble.
  8624.           infoBubble = new cr.ui.ExpandableBubble;
  8625.           infoBubble.anchorNode = this;
  8626.           infoBubble.appId = this.appData_.id;
  8627.           infoBubble.handleCloseEvent = function() {
  8628.             chrome.send('closeNotification', [this.appId]);
  8629.             infoBubble.hide();
  8630.           };
  8631.         } else {
  8632.           // Reuse the old bubble instead of popping up a new bubble over
  8633.           // the old one.
  8634.           infoBubble = this.currentBubbleShowing_;
  8635.           infoBubble.collapseBubble_();
  8636.         }
  8637.         infoBubble.contentTitle = this.createBubbleNode_(notification, false);
  8638.         infoBubble.content = this.createBubbleNode_(notification, true);
  8639.         infoBubble.show();
  8640.         infoBubble.resizeAndReposition();
  8641.  
  8642.         this.currentBubbleShowing_ = infoBubble;
  8643.       }
  8644.     },
  8645.  
  8646.     /**
  8647.      *  Removes the info bubble if there is one.
  8648.      */
  8649.     removeBubble: function() {
  8650.       if (this.currentBubbleShowing_) {
  8651.         this.currentBubbleShowing_.hide();
  8652.         this.currentBubbleShowing_ = null;
  8653.       }
  8654.     },
  8655.  
  8656.     /**
  8657.      * Set the size and position of the app tile.
  8658.      * @param {number} size The total size of |this|.
  8659.      * @param {number} x The x-position.
  8660.      * @param {number} y The y-position.
  8661.      *     animate.
  8662.      */
  8663.     setBounds: function(size, x, y) {
  8664.       var imgSize = size * APP_IMG_SIZE_FRACTION;
  8665.       this.appImgContainer_.style.width = this.appImgContainer_.style.height =
  8666.           toCssPx(this.useSmallIcon_ ? 16 : imgSize);
  8667.       if (this.useSmallIcon_) {
  8668.         // 3/4 is the ratio of 96px to 128px (the used height and full height
  8669.         // of icons in apps).
  8670.         var iconSize = imgSize * 3 / 4;
  8671.         // The -2 is for the div border to improve the visual alignment for the
  8672.         // icon div.
  8673.         this.imgDiv_.style.width = this.imgDiv_.style.height =
  8674.             toCssPx(iconSize - 2);
  8675.         // Margins set to get the icon placement right and the text to line up.
  8676.         this.imgDiv_.style.marginTop = this.imgDiv_.style.marginBottom =
  8677.             toCssPx((imgSize - iconSize) / 2);
  8678.       }
  8679.  
  8680.       this.style.width = this.style.height = toCssPx(size);
  8681.       this.style.left = toCssPx(x);
  8682.       this.style.right = toCssPx(x);
  8683.       this.style.top = toCssPx(y);
  8684.  
  8685.       if (this.currentBubbleShowing_)
  8686.         this.currentBubbleShowing_.resizeAndReposition();
  8687.     },
  8688.  
  8689.     /**
  8690.      * Invoked when an app is clicked.
  8691.      * @param {Event} e The click event.
  8692.      * @private
  8693.      */
  8694.     onClick_: function(e) {
  8695.       var url = !this.appData_.is_webstore ? '' :
  8696.           appendParam(this.appData_.url,
  8697.                       'utm_source',
  8698.                       'chrome-ntp-icon');
  8699.  
  8700.       chrome.send('launchApp',
  8701.                   [this.appId, APP_LAUNCH.NTP_APPS_MAXIMIZED, url,
  8702.                    e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
  8703.  
  8704.       // Don't allow the click to trigger a link or anything
  8705.       e.preventDefault();
  8706.     },
  8707.  
  8708.     /**
  8709.      * Invoked when the user presses a key while the app is focused.
  8710.      * @param {Event} e The key event.
  8711.      * @private
  8712.      */
  8713.     onKeydown_: function(e) {
  8714.       if (e.keyIdentifier == 'Enter') {
  8715.         chrome.send('launchApp',
  8716.                     [this.appId, APP_LAUNCH.NTP_APPS_MAXIMIZED, '',
  8717.                      0, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
  8718.         e.preventDefault();
  8719.         e.stopPropagation();
  8720.       }
  8721.       this.onKeyboardUsed_(e.keyCode);
  8722.     },
  8723.  
  8724.     /**
  8725.      * Invoked when the user releases a key while the app is focused.
  8726.      * @param {Event} e The key event.
  8727.      * @private
  8728.      */
  8729.     onKeyup_: function(e) {
  8730.       this.onKeyboardUsed_(e.keyCode);
  8731.     },
  8732.  
  8733.     /**
  8734.      * Called when the keyboard has been used (key down or up). The .click-focus
  8735.      * hack is removed if the user presses a key that can change focus.
  8736.      * @param {number} keyCode The key code of the keyboard event.
  8737.      * @private
  8738.      */
  8739.     onKeyboardUsed_: function(keyCode) {
  8740.       switch (keyCode) {
  8741.         case 9:  // Tab.
  8742.         case 37:  // Left arrow.
  8743.         case 38:  // Up arrow.
  8744.         case 39:  // Right arrow.
  8745.         case 40:  // Down arrow.
  8746.           this.classList.remove('click-focus');
  8747.       }
  8748.     },
  8749.  
  8750.     /**
  8751.      * Adds a node to the list of targets that will launch the app. This list
  8752.      * is also used in onMousedown to determine whether the app contents should
  8753.      * be shown as active (if we don't do this, then clicking anywhere in
  8754.      * appContents, even a part that is outside the ideally clickable region,
  8755.      * will cause the app icon to look active).
  8756.      * @param {HTMLElement} node The node that should be clickable.
  8757.      */
  8758.     addLaunchClickTarget_: function(node) {
  8759.       node.classList.add('launch-click-target');
  8760.       node.addEventListener('click', this.onClick_.bind(this));
  8761.     },
  8762.  
  8763.     /**
  8764.      * Handler for mousedown on the App. Adds a class that allows us to
  8765.      * not display as :active for right clicks and clicks on app notifications
  8766.      * (specifically, don't pulse on these occasions). Also, we don't pulse
  8767.      * for clicks that aren't within the clickable regions.
  8768.      * @param {Event} e The mousedown event.
  8769.      */
  8770.     onMousedown_: function(e) {
  8771.       // If the current platform uses middle click to autoscroll and this
  8772.       // mousedown isn't handled, onClick_() will never fire. crbug.com/142939
  8773.       if (e.button == 1)
  8774.         e.preventDefault();
  8775.  
  8776.       if (e.button == 2 ||
  8777.           !findAncestorByClass(e.target, 'launch-click-target')) {
  8778.         this.appContents_.classList.add('suppress-active');
  8779.       } else {
  8780.         this.appContents_.classList.remove('suppress-active');
  8781.       }
  8782.  
  8783.       // This class is here so we don't show the focus state for apps that
  8784.       // gain keyboard focus via mouse clicking.
  8785.       this.classList.add('click-focus');
  8786.     },
  8787.  
  8788.     /**
  8789.      * Change the appData and update the appearance of the app.
  8790.      * @param {Object} appData The new data object that describes the app.
  8791.      */
  8792.     replaceAppData: function(appData) {
  8793.       this.appData_ = appData;
  8794.       this.setIcon();
  8795.       this.loadIcon();
  8796.     },
  8797.  
  8798.     /**
  8799.      * The data and preferences for this app.
  8800.      * @type {Object}
  8801.      */
  8802.     set appData(data) {
  8803.       this.appData_ = data;
  8804.     },
  8805.     get appData() {
  8806.       return this.appData_;
  8807.     },
  8808.  
  8809.     get appId() {
  8810.       return this.appData_.id;
  8811.     },
  8812.  
  8813.     /**
  8814.      * Returns a pointer to the context menu for this app. All apps share the
  8815.      * singleton AppContextMenu. This function is called by the
  8816.      * ContextMenuHandler in response to the 'contextmenu' event.
  8817.      * @type {cr.ui.Menu}
  8818.      */
  8819.     get contextMenu() {
  8820.       var menu = AppContextMenu.getInstance();
  8821.       menu.setupForApp(this);
  8822.       return menu.menu;
  8823.     },
  8824.  
  8825.     /**
  8826.      * Returns whether this element can be 'removed' from chrome (i.e. whether
  8827.      * the user can drag it onto the trash and expect something to happen).
  8828.      * @return {boolean} True if the app can be uninstalled.
  8829.      */
  8830.     canBeRemoved: function() {
  8831.       return this.appData_.mayDisable;
  8832.     },
  8833.  
  8834.     /**
  8835.      * Uninstalls the app after it's been dropped on the trash.
  8836.      */
  8837.     removeFromChrome: function() {
  8838.       chrome.send('uninstallApp', [this.appData_.id, true]);
  8839.       this.tile.tilePage.removeTile(this.tile, true);
  8840.       if (this.currentBubbleShowing_)
  8841.         currentBubbleShowing_.hide();
  8842.     },
  8843.  
  8844.     /**
  8845.      * Called when a drag is starting on the tile. Updates dataTransfer with
  8846.      * data for this tile.
  8847.      */
  8848.     setDragData: function(dataTransfer) {
  8849.       dataTransfer.setData('Text', this.appData_.title);
  8850.       dataTransfer.setData('URL', this.appData_.url);
  8851.     },
  8852.   };
  8853.  
  8854.   var TilePage = ntp.TilePage;
  8855.  
  8856.   // The fraction of the app tile size that the icon uses.
  8857.   var APP_IMG_SIZE_FRACTION = 4 / 5;
  8858.  
  8859.   var appsPageGridValues = {
  8860.     // The fewest tiles we will show in a row.
  8861.     minColCount: 3,
  8862.     // The most tiles we will show in a row.
  8863.     maxColCount: 6,
  8864.  
  8865.     // The smallest a tile can be.
  8866.     minTileWidth: 64 / APP_IMG_SIZE_FRACTION,
  8867.     // The biggest a tile can be.
  8868.     maxTileWidth: 128 / APP_IMG_SIZE_FRACTION,
  8869.  
  8870.     // The padding between tiles, as a fraction of the tile width.
  8871.     tileSpacingFraction: 1 / 8,
  8872.   };
  8873.   TilePage.initGridValues(appsPageGridValues);
  8874.  
  8875.   /**
  8876.    * Creates a new AppsPage object.
  8877.    * @constructor
  8878.    * @extends {TilePage}
  8879.    */
  8880.   function AppsPage() {
  8881.     var el = new TilePage(appsPageGridValues);
  8882.     el.__proto__ = AppsPage.prototype;
  8883.     el.initialize();
  8884.  
  8885.     return el;
  8886.   }
  8887.  
  8888.   AppsPage.prototype = {
  8889.     __proto__: TilePage.prototype,
  8890.  
  8891.     initialize: function() {
  8892.       this.classList.add('apps-page');
  8893.  
  8894.       this.addEventListener('cardselected', this.onCardSelected_);
  8895.       // Add event listeners for two events, so we can temporarily suppress
  8896.       // the app notification bubbles when the app card slides in and out of
  8897.       // view.
  8898.       this.addEventListener('carddeselected', this.onCardDeselected_);
  8899.       this.addEventListener('cardSlider:card_change_ended',
  8900.                             this.onCardChangeEnded_);
  8901.  
  8902.       this.addEventListener('tilePage:tile_added', this.onTileAdded_);
  8903.  
  8904.       this.content_.addEventListener('scroll', this.onScroll_.bind(this));
  8905.     },
  8906.  
  8907.     /**
  8908.      * Highlight a newly installed app as it's added to the NTP.
  8909.      * @param {Object} appData The data object that describes the app.
  8910.      */
  8911.     insertAndHighlightApp: function(appData) {
  8912.       ntp.getCardSlider().selectCardByValue(this);
  8913.       this.content_.scrollTop = this.content_.scrollHeight;
  8914.       this.insertApp(appData, true);
  8915.     },
  8916.  
  8917.     /**
  8918.      * Similar to appendApp, but it respects the app_launch_ordinal field of
  8919.      * |appData|.
  8920.      * @param {Object} appData The data that describes the app.
  8921.      * @param {boolean} animate Whether to animate the insertion.
  8922.      */
  8923.     insertApp: function(appData, animate) {
  8924.       var index = this.tileElements_.length;
  8925.       for (var i = 0; i < this.tileElements_.length; i++) {
  8926.         if (appData.app_launch_ordinal <
  8927.             this.tileElements_[i].firstChild.appData.app_launch_ordinal) {
  8928.           index = i;
  8929.           break;
  8930.         }
  8931.       }
  8932.  
  8933.       this.addTileAt(new App(appData), index, animate);
  8934.     },
  8935.  
  8936.     /**
  8937.      * Handler for 'cardselected' event, fired when |this| is selected. The
  8938.      * first time this is called, we load all the app icons.
  8939.      * @private
  8940.      */
  8941.     onCardSelected_: function(e) {
  8942.       var apps = this.querySelectorAll('.app.icon-loading');
  8943.       for (var i = 0; i < apps.length; i++) {
  8944.         apps[i].loadIcon();
  8945.         if (apps[i].currentBubbleShowing_)
  8946.           apps[i].currentBubbleShowing_.suppressed = false;
  8947.       }
  8948.     },
  8949.  
  8950.     /**
  8951.      * Handler for tile additions to this page.
  8952.      * @param {Event} e The tilePage:tile_added event.
  8953.      */
  8954.     onTileAdded_: function(e) {
  8955.       assert(e.currentTarget == this);
  8956.       assert(e.addedTile.firstChild instanceof App);
  8957.       if (this.classList.contains('selected-card'))
  8958.         e.addedTile.firstChild.loadIcon();
  8959.     },
  8960.  
  8961.     /**
  8962.      * Handler for the when this.cardSlider ends change its card. If animated,
  8963.      * this happens when the -webkit-transition is done, otherwise happens
  8964.      * immediately (but after cardSlider:card_changed).
  8965.      * @private
  8966.      */
  8967.     onCardChangeEnded_: function(e) {
  8968.       for (var i = 0; i < this.tileElements_.length; i++) {
  8969.         var app = this.tileElements_[i].firstChild;
  8970.         assert(app instanceof App);
  8971.         if (app.currentBubbleShowing_)
  8972.           app.currentBubbleShowing_.suppressed = false;
  8973.       }
  8974.     },
  8975.  
  8976.     /**
  8977.      * Handler for the 'carddeselected' event, fired when the user switches
  8978.      * to another 'card' than the App 'card' on the NTP (|this| gets
  8979.      * deselected).
  8980.      * @private
  8981.      */
  8982.     onCardDeselected_: function(e) {
  8983.       for (var i = 0; i < this.tileElements_.length; i++) {
  8984.         var app = this.tileElements_[i].firstChild;
  8985.         assert(app instanceof App);
  8986.         if (app.currentBubbleShowing_)
  8987.           app.currentBubbleShowing_.suppressed = true;
  8988.       }
  8989.     },
  8990.  
  8991.     /**
  8992.      * A handler for when the apps page is scrolled (then we need to reposition
  8993.      * the bubbles.
  8994.      * @private
  8995.      */
  8996.     onScroll_: function(e) {
  8997.       if (!this.selected)
  8998.         return;
  8999.       for (var i = 0; i < this.tileElements_.length; i++) {
  9000.         var app = this.tileElements_[i].firstChild;
  9001.         assert(app instanceof App);
  9002.         if (app.currentBubbleShowing_)
  9003.           app.currentBubbleShowing_.resizeAndReposition();
  9004.         }
  9005.     },
  9006.  
  9007.     /** @override */
  9008.     doDragOver: function(e) {
  9009.       // Only animatedly re-arrange if the user is currently dragging an app.
  9010.       var tile = ntp.getCurrentlyDraggingTile();
  9011.       if (tile && tile.querySelector('.app')) {
  9012.         TilePage.prototype.doDragOver.call(this, e);
  9013.       } else {
  9014.         e.preventDefault();
  9015.         this.setDropEffect(e.dataTransfer);
  9016.       }
  9017.     },
  9018.  
  9019.     /** @override */
  9020.     shouldAcceptDrag: function(e) {
  9021.       if (ntp.getCurrentlyDraggingTile())
  9022.         return true;
  9023.       if (!e.dataTransfer || !e.dataTransfer.types)
  9024.         return false;
  9025.       return Array.prototype.indexOf.call(e.dataTransfer.types,
  9026.                                           'text/uri-list') != -1;
  9027.     },
  9028.  
  9029.     /** @override */
  9030.     addDragData: function(dataTransfer, index) {
  9031.       var sourceId = -1;
  9032.       var currentlyDraggingTile = ntp.getCurrentlyDraggingTile();
  9033.       if (currentlyDraggingTile) {
  9034.         var tileContents = currentlyDraggingTile.firstChild;
  9035.         if (tileContents.classList.contains('app')) {
  9036.           var originalPage = currentlyDraggingTile.tilePage;
  9037.           var samePageDrag = originalPage == this;
  9038.           sourceId = samePageDrag ? DRAG_SOURCE.SAME_APPS_PANE :
  9039.                                     DRAG_SOURCE.OTHER_APPS_PANE;
  9040.           this.tileGrid_.insertBefore(currentlyDraggingTile,
  9041.                                       this.tileElements_[index]);
  9042.           this.tileMoved(currentlyDraggingTile);
  9043.           if (!samePageDrag) {
  9044.             originalPage.fireRemovedEvent(currentlyDraggingTile, index, true);
  9045.             this.fireAddedEvent(currentlyDraggingTile, index, true);
  9046.           }
  9047.         } else if (currentlyDraggingTile.querySelector('.most-visited')) {
  9048.           this.generateAppForLink(tileContents.data);
  9049.           sourceId = DRAG_SOURCE.MOST_VISITED_PANE;
  9050.         }
  9051.       } else {
  9052.         this.addOutsideData_(dataTransfer);
  9053.         sourceId = DRAG_SOURCE.OUTSIDE_NTP;
  9054.       }
  9055.  
  9056.       assert(sourceId != -1);
  9057.       chrome.send('metricsHandler:recordInHistogram',
  9058.           ['NewTabPage.AppsPageDragSource', sourceId, DRAG_SOURCE_LIMIT]);
  9059.     },
  9060.  
  9061.     /**
  9062.      * Adds drag data that has been dropped from a source that is not a tile.
  9063.      * @param {Object} dataTransfer The data transfer object that holds drop
  9064.      *     data.
  9065.      * @private
  9066.      */
  9067.     addOutsideData_: function(dataTransfer) {
  9068.       var url = dataTransfer.getData('url');
  9069.       assert(url);
  9070.  
  9071.       // If the dataTransfer has html data, use that html's text contents as the
  9072.       // title of the new link.
  9073.       var html = dataTransfer.getData('text/html');
  9074.       var title;
  9075.       if (html) {
  9076.         // It's important that we don't attach this node to the document
  9077.         // because it might contain scripts.
  9078.         var node = this.ownerDocument.createElement('div');
  9079.         node.innerHTML = html;
  9080.         title = node.textContent;
  9081.       }
  9082.  
  9083.       // Make sure title is >=1 and <=45 characters for Chrome app limits.
  9084.       if (!title)
  9085.         title = url;
  9086.       if (title.length > 45)
  9087.         title = title.substring(0, 45);
  9088.       var data = {url: url, title: title};
  9089.  
  9090.       // Synthesize an app.
  9091.       this.generateAppForLink(data);
  9092.     },
  9093.  
  9094.     /**
  9095.      * Creates a new crx-less app manifest and installs it.
  9096.      * @param {Object} data The data object describing the link. Must have |url|
  9097.      *     and |title| members.
  9098.      */
  9099.     generateAppForLink: function(data) {
  9100.       assert(data.url != undefined);
  9101.       assert(data.title != undefined);
  9102.       var pageIndex = ntp.getAppsPageIndex(this);
  9103.       chrome.send('generateAppForLink', [data.url, data.title, pageIndex]);
  9104.     },
  9105.  
  9106.     /** @override */
  9107.     tileMoved: function(draggedTile) {
  9108.       if (!(draggedTile.firstChild instanceof App))
  9109.         return;
  9110.  
  9111.       var pageIndex = ntp.getAppsPageIndex(this);
  9112.       chrome.send('setPageIndex', [draggedTile.firstChild.appId, pageIndex]);
  9113.  
  9114.       var appIds = [];
  9115.       for (var i = 0; i < this.tileElements_.length; i++) {
  9116.         var tileContents = this.tileElements_[i].firstChild;
  9117.         if (tileContents instanceof App)
  9118.           appIds.push(tileContents.appId);
  9119.       }
  9120.  
  9121.       chrome.send('reorderApps', [draggedTile.firstChild.appId, appIds]);
  9122.     },
  9123.  
  9124.     /** @override */
  9125.     setDropEffect: function(dataTransfer) {
  9126.       var tile = ntp.getCurrentlyDraggingTile();
  9127.       if (tile && tile.querySelector('.app'))
  9128.         ntp.setCurrentDropEffect(dataTransfer, 'move');
  9129.       else
  9130.         ntp.setCurrentDropEffect(dataTransfer, 'copy');
  9131.     },
  9132.   };
  9133.  
  9134.   /**
  9135.    * Launches the specified app using the APP_LAUNCH_NTP_APP_RE_ENABLE
  9136.    * histogram. This should only be invoked from the AppLauncherHandler.
  9137.    * @param {String} appID The ID of the app.
  9138.    */
  9139.   function launchAppAfterEnable(appId) {
  9140.     chrome.send('launchApp', [appId, APP_LAUNCH.NTP_APP_RE_ENABLE]);
  9141.   }
  9142.  
  9143.   function appNotificationChanged(id, notification) {
  9144.     var app = $(id);
  9145.     // The app might have been uninstalled, or notifications might be disabled.
  9146.     if (app && !app.appData.notifications_disabled)
  9147.       app.setupNotification_(notification);
  9148.   }
  9149.  
  9150.   return {
  9151.     APP_LAUNCH: APP_LAUNCH,
  9152.     appNotificationChanged: appNotificationChanged,
  9153.     AppsPage: AppsPage,
  9154.     launchAppAfterEnable: launchAppAfterEnable,
  9155.   };
  9156. });
  9157. </script>
  9158. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9159. // Use of this source code is governed by a BSD-style license that can be
  9160. // found in the LICENSE file.
  9161.  
  9162. /**
  9163.  * @fileoverview DotList implementation
  9164.  */
  9165.  
  9166. cr.define('ntp', function() {
  9167.   'use strict';
  9168.  
  9169.   /**
  9170.    * Live list of the navigation dots.
  9171.    * @type {!NodeList|undefined}
  9172.    */
  9173.   var navDots;
  9174.  
  9175.   /**
  9176.    * Creates a new DotList object.
  9177.    * @constructor
  9178.    * @extends {HTMLUListElement}
  9179.    */
  9180.   var DotList = cr.ui.define('ul');
  9181.  
  9182.   DotList.prototype = {
  9183.     __proto__: HTMLUListElement.prototype,
  9184.  
  9185.     /** @override */
  9186.     decorate: function() {
  9187.       this.addEventListener('keydown', this.onKeyDown_.bind(this));
  9188.       navDots = this.getElementsByClassName('dot');
  9189.     },
  9190.  
  9191.     /**
  9192.      * Live list of the navigation dots.
  9193.      * @type {!NodeList|undefined}
  9194.      */
  9195.     get dots() {
  9196.       return navDots;
  9197.     },
  9198.  
  9199.     /**
  9200.      * Handler for key events on the dot list. These keys will change the focus
  9201.      * element.
  9202.      * @param {Event} e The KeyboardEvent.
  9203.      */
  9204.     onKeyDown_: function(e) {
  9205.       if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey)
  9206.         return;
  9207.  
  9208.       var direction = 0;
  9209.       if (e.keyIdentifier == 'Left')
  9210.         direction = -1;
  9211.       else if (e.keyIdentifier == 'Right')
  9212.         direction = 1;
  9213.       else
  9214.         return;
  9215.  
  9216.       var focusDot = this.querySelector('.dot:focus');
  9217.       if (!focusDot)
  9218.         return;
  9219.       var focusIndex = Array.prototype.indexOf.call(navDots, focusDot);
  9220.       var newFocusIndex = focusIndex + direction;
  9221.       if (focusIndex == newFocusIndex)
  9222.         return;
  9223.  
  9224.       newFocusIndex = (newFocusIndex + navDots.length) % navDots.length;
  9225.       navDots[newFocusIndex].tabIndex = 3;
  9226.       navDots[newFocusIndex].focus();
  9227.       focusDot.tabIndex = -1;
  9228.  
  9229.       e.stopPropagation();
  9230.       e.preventDefault();
  9231.     }
  9232.   };
  9233.  
  9234.   return {
  9235.     DotList: DotList
  9236.   };
  9237. });
  9238. </script>
  9239. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9240. // Use of this source code is governed by a BSD-style license that can be
  9241. // found in the LICENSE file.
  9242.  
  9243. cr.define('ntp', function() {
  9244.   'use strict';
  9245.  
  9246.   var TilePage = ntp.TilePage;
  9247.  
  9248.   /**
  9249.    * A counter for generating unique tile IDs.
  9250.    */
  9251.   var tileID = 0;
  9252.  
  9253.   /**
  9254.    * Creates a new Most Visited object for tiling.
  9255.    * @constructor
  9256.    * @extends {HTMLAnchorElement}
  9257.    */
  9258.   function MostVisited() {
  9259.     var el = cr.doc.createElement('a');
  9260.     el.__proto__ = MostVisited.prototype;
  9261.     el.initialize();
  9262.  
  9263.     return el;
  9264.   }
  9265.  
  9266.   MostVisited.prototype = {
  9267.     __proto__: HTMLAnchorElement.prototype,
  9268.  
  9269.     initialize: function() {
  9270.       this.reset();
  9271.  
  9272.       this.addEventListener('click', this.handleClick_);
  9273.       this.addEventListener('keydown', this.handleKeyDown_);
  9274.     },
  9275.  
  9276.     get index() {
  9277.       assert(this.tile);
  9278.       return this.tile.index;
  9279.     },
  9280.  
  9281.     get data() {
  9282.       return this.data_;
  9283.     },
  9284.  
  9285.     /**
  9286.      * Clears the DOM hierarchy for this node, setting it back to the default
  9287.      * for a blank thumbnail.
  9288.      */
  9289.     reset: function() {
  9290.       this.className = 'most-visited filler real';
  9291.       this.innerHTML =
  9292.           '<span class="thumbnail-wrapper fills-parent">' +
  9293.             '<div class="close-button"></div>' +
  9294.             '<span class="thumbnail fills-parent">' +
  9295.               // thumbnail-shield provides a gradient fade effect.
  9296.               '<div class="thumbnail-shield fills-parent"></div>' +
  9297.             '</span>' +
  9298.             '<span class="favicon"></span>' +
  9299.           '</span>' +
  9300.           '<div class="color-stripe"></div>' +
  9301.           '<span class="title"></span>';
  9302.  
  9303.       this.querySelector('.close-button').title =
  9304.           loadTimeData.getString('removethumbnailtooltip');
  9305.  
  9306.       this.tabIndex = -1;
  9307.       this.data_ = null;
  9308.       this.removeAttribute('id');
  9309.       this.title = '';
  9310.     },
  9311.  
  9312.     /**
  9313.      * Update the appearance of this tile according to |data|.
  9314.      * @param {Object} data A dictionary of relevant data for the page.
  9315.      */
  9316.     updateForData: function(data) {
  9317.       if (this.classList.contains('blacklisted') && data) {
  9318.         // Animate appearance of new tile.
  9319.         this.classList.add('new-tile-contents');
  9320.       }
  9321.       this.classList.remove('blacklisted');
  9322.  
  9323.       if (!data || data.filler) {
  9324.         if (this.data_)
  9325.           this.reset();
  9326.         return;
  9327.       }
  9328.  
  9329.       var id = tileID++;
  9330.       this.id = 'most-visited-tile-' + id;
  9331.       this.data_ = data;
  9332.       this.classList.add('focusable');
  9333.  
  9334.       var faviconDiv = this.querySelector('.favicon');
  9335.       var faviconUrl = getFaviconURL(data.url);
  9336.       faviconDiv.style.backgroundImage = url(faviconUrl);
  9337.       chrome.send('getFaviconDominantColor', [faviconUrl, this.id]);
  9338.  
  9339.       var title = this.querySelector('.title');
  9340.       title.textContent = data.title;
  9341.       title.dir = data.direction;
  9342.  
  9343.       // Sets the tooltip.
  9344.       this.title = data.title;
  9345.  
  9346.       var thumbnailUrl = 'chrome://thumb/' + data.url;
  9347.       this.querySelector('.thumbnail').style.backgroundImage =
  9348.           url(thumbnailUrl);
  9349.  
  9350.       this.href = data.url;
  9351.  
  9352.       this.classList.remove('filler');
  9353.     },
  9354.  
  9355.     /**
  9356.      * Sets the color of the favicon dominant color bar.
  9357.      * @param {string} color The css-parsable value for the color.
  9358.      */
  9359.     set stripeColor(color) {
  9360.       this.querySelector('.color-stripe').style.backgroundColor = color;
  9361.     },
  9362.  
  9363.     /**
  9364.      * Handles a click on the tile.
  9365.      * @param {Event} e The click event.
  9366.      */
  9367.     handleClick_: function(e) {
  9368.       if (e.target.classList.contains('close-button')) {
  9369.         this.blacklist_();
  9370.         e.preventDefault();
  9371.       } else {
  9372.         ntp.logTimeToClick('MostVisited');
  9373.         // Records an app launch from the most visited page (Chrome will decide
  9374.         // whether the url is an app). TODO(estade): this only works for clicks;
  9375.         // other actions like "open in new tab" from the context menu won't be
  9376.         // recorded. Can this be fixed?
  9377.         chrome.send('recordAppLaunchByURL',
  9378.                     [encodeURIComponent(this.href),
  9379.                      ntp.APP_LAUNCH.NTP_MOST_VISITED]);
  9380.         // Records the index of this tile.
  9381.         chrome.send('metricsHandler:recordInHistogram',
  9382.                     ['NewTabPage.MostVisited', this.index, 8]);
  9383.         chrome.send('mostVisitedAction',
  9384.                     [ntp.NtpFollowAction.CLICKED_TILE]);
  9385.       }
  9386.     },
  9387.  
  9388.     /**
  9389.      * Allow blacklisting most visited site using the keyboard.
  9390.      */
  9391.     handleKeyDown_: function(e) {
  9392.       if (!cr.isMac && e.keyCode == 46 || // Del
  9393.           cr.isMac && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
  9394.         this.blacklist_();
  9395.       }
  9396.     },
  9397.  
  9398.     /**
  9399.      * Permanently removes a page from Most Visited.
  9400.      */
  9401.     blacklist_: function() {
  9402.       this.showUndoNotification_();
  9403.       chrome.send('blacklistURLFromMostVisited', [this.data_.url]);
  9404.       this.reset();
  9405.       chrome.send('getMostVisited');
  9406.       this.classList.add('blacklisted');
  9407.     },
  9408.  
  9409.     showUndoNotification_: function() {
  9410.       var data = this.data_;
  9411.       var self = this;
  9412.       var doUndo = function() {
  9413.         chrome.send('removeURLsFromMostVisitedBlacklist', [data.url]);
  9414.         self.updateForData(data);
  9415.       }
  9416.  
  9417.       var undo = {
  9418.         action: doUndo,
  9419.         text: loadTimeData.getString('undothumbnailremove'),
  9420.       };
  9421.  
  9422.       var undoAll = {
  9423.         action: function() {
  9424.           chrome.send('clearMostVisitedURLsBlacklist');
  9425.         },
  9426.         text: loadTimeData.getString('restoreThumbnailsShort'),
  9427.       };
  9428.  
  9429.       ntp.showNotification(
  9430.           loadTimeData.getString('thumbnailremovednotification'),
  9431.           [undo, undoAll]);
  9432.     },
  9433.  
  9434.     /**
  9435.      * Set the size and position of the most visited tile.
  9436.      * @param {number} size The total size of |this|.
  9437.      * @param {number} x The x-position.
  9438.      * @param {number} y The y-position.
  9439.      *     animate.
  9440.      */
  9441.     setBounds: function(size, x, y) {
  9442.       this.style.width = toCssPx(size);
  9443.       this.style.height = toCssPx(heightForWidth(size));
  9444.  
  9445.       this.style.left = toCssPx(x);
  9446.       this.style.right = toCssPx(x);
  9447.       this.style.top = toCssPx(y);
  9448.     },
  9449.  
  9450.     /**
  9451.      * Returns whether this element can be 'removed' from chrome (i.e. whether
  9452.      * the user can drag it onto the trash and expect something to happen).
  9453.      * @return {boolean} True, since most visited pages can always be
  9454.      *     blacklisted.
  9455.      */
  9456.     canBeRemoved: function() {
  9457.       return true;
  9458.     },
  9459.  
  9460.     /**
  9461.      * Removes this element from chrome, i.e. blacklists it.
  9462.      */
  9463.     removeFromChrome: function() {
  9464.       this.blacklist_();
  9465.       this.parentNode.classList.add('finishing-drag');
  9466.     },
  9467.  
  9468.     /**
  9469.      * Called when a drag of this tile has ended (after all animations have
  9470.      * finished).
  9471.      */
  9472.     finalizeDrag: function() {
  9473.       this.parentNode.classList.remove('finishing-drag');
  9474.     },
  9475.  
  9476.     /**
  9477.      * Called when a drag is starting on the tile. Updates dataTransfer with
  9478.      * data for this tile (for dragging outside of the NTP).
  9479.      */
  9480.     setDragData: function(dataTransfer) {
  9481.       dataTransfer.setData('Text', this.data_.title);
  9482.       dataTransfer.setData('URL', this.data_.url);
  9483.     },
  9484.   };
  9485.  
  9486.   var mostVisitedPageGridValues = {
  9487.     // The fewest tiles we will show in a row.
  9488.     minColCount: 2,
  9489.     // The most tiles we will show in a row.
  9490.     maxColCount: 4,
  9491.  
  9492.     // The smallest a tile can be.
  9493.     minTileWidth: 122,
  9494.     // The biggest a tile can be. 212 (max thumbnail width) + 2.
  9495.     maxTileWidth: 214,
  9496.  
  9497.     // The padding between tiles, as a fraction of the tile width.
  9498.     tileSpacingFraction: 1 / 8,
  9499.   };
  9500.   TilePage.initGridValues(mostVisitedPageGridValues);
  9501.  
  9502.   /**
  9503.    * Calculates the height for a Most Visited tile for a given width. The size
  9504.    * is based on the thumbnail, which should have a 212:132 ratio.
  9505.    * @return {number} The height.
  9506.    */
  9507.   function heightForWidth(width) {
  9508.     // The 2s are for borders, the 31 is for the title.
  9509.     return (width - 2) * 132 / 212 + 2 + 31;
  9510.   }
  9511.  
  9512.   var THUMBNAIL_COUNT = 8;
  9513.  
  9514.   /**
  9515.    * Creates a new MostVisitedPage object.
  9516.    * @constructor
  9517.    * @extends {TilePage}
  9518.    */
  9519.   function MostVisitedPage() {
  9520.     var el = new TilePage(mostVisitedPageGridValues);
  9521.     el.__proto__ = MostVisitedPage.prototype;
  9522.     el.initialize();
  9523.  
  9524.     return el;
  9525.   }
  9526.  
  9527.   MostVisitedPage.prototype = {
  9528.     __proto__: TilePage.prototype,
  9529.  
  9530.     initialize: function() {
  9531.       this.classList.add('most-visited-page');
  9532.       this.data_ = null;
  9533.       this.mostVisitedTiles_ = this.getElementsByClassName('most-visited real');
  9534.  
  9535.       this.addEventListener('carddeselected', this.handleCardDeselected_);
  9536.       this.addEventListener('cardselected', this.handleCardSelected_);
  9537.     },
  9538.  
  9539.     /**
  9540.      * Create blank (filler) tiles.
  9541.      * @private
  9542.      */
  9543.     createTiles_: function() {
  9544.       for (var i = 0; i < THUMBNAIL_COUNT; i++) {
  9545.         this.appendTile(new MostVisited());
  9546.       }
  9547.     },
  9548.  
  9549.     /**
  9550.      * Update the tiles after a change to |data_|.
  9551.      */
  9552.     updateTiles_: function() {
  9553.       for (var i = 0; i < THUMBNAIL_COUNT; i++) {
  9554.         var page = this.data_[i];
  9555.         var tile = this.mostVisitedTiles_[i];
  9556.  
  9557.         if (i >= this.data_.length)
  9558.           tile.reset();
  9559.         else
  9560.           tile.updateForData(page);
  9561.       }
  9562.     },
  9563.  
  9564.     /**
  9565.      * Handles the 'card deselected' event (i.e. the user clicked to another
  9566.      * pane).
  9567.      * @param {Event} e The CardChanged event.
  9568.      */
  9569.     handleCardDeselected_: function(e) {
  9570.       if (!document.documentElement.classList.contains('starting-up')) {
  9571.         chrome.send('mostVisitedAction',
  9572.                     [ntp.NtpFollowAction.CLICKED_OTHER_NTP_PANE]);
  9573.       }
  9574.     },
  9575.  
  9576.     /**
  9577.      * Handles the 'card selected' event (i.e. the user clicked to select the
  9578.      * Most Visited pane).
  9579.      * @param {Event} e The CardChanged event.
  9580.      */
  9581.     handleCardSelected_: function(e) {
  9582.       if (!document.documentElement.classList.contains('starting-up'))
  9583.         chrome.send('mostVisitedSelected');
  9584.     },
  9585.  
  9586.     /**
  9587.      * Array of most visited data objects.
  9588.      * @type {Array}
  9589.      */
  9590.     get data() {
  9591.       return this.data_;
  9592.     },
  9593.     set data(data) {
  9594.       var startTime = Date.now();
  9595.  
  9596.       // The first time data is set, create the tiles.
  9597.       if (!this.data_) {
  9598.         this.createTiles_();
  9599.         this.data_ = data.slice(0, THUMBNAIL_COUNT);
  9600.       } else {
  9601.         this.data_ = refreshData(this.data_, data);
  9602.       }
  9603.  
  9604.       this.updateTiles_();
  9605.       logEvent('mostVisited.layout: ' + (Date.now() - startTime));
  9606.     },
  9607.  
  9608.     /** @override */
  9609.     shouldAcceptDrag: function(e) {
  9610.       return false;
  9611.     },
  9612.  
  9613.     /** @override */
  9614.     heightForWidth: heightForWidth,
  9615.   };
  9616.  
  9617.   /**
  9618.    * Executed once the NTP has loaded. Checks if the Most Visited pane is
  9619.    * shown or not. If it is shown, the 'mostVisitedSelected' message is sent
  9620.    * to the C++ code, to record the fact that the user has seen this pane.
  9621.    */
  9622.   MostVisitedPage.onLoaded = function() {
  9623.     if (ntp.getCardSlider() &&
  9624.         ntp.getCardSlider().currentCardValue &&
  9625.         ntp.getCardSlider().currentCardValue.classList
  9626.         .contains('most-visited-page')) {
  9627.       chrome.send('mostVisitedSelected');
  9628.     }
  9629.   }
  9630.  
  9631.   /**
  9632.    * We've gotten additional Most Visited data. Update our old data with the
  9633.    * new data. The ordering of the new data is not important, except when a
  9634.    * page is pinned. Thus we try to minimize re-ordering.
  9635.    * @param {Array} oldData The current Most Visited page list.
  9636.    * @param {Array} newData The new Most Visited page list.
  9637.    * @return {Array} The merged page list that should replace the current page
  9638.    *     list.
  9639.    */
  9640.   function refreshData(oldData, newData) {
  9641.     oldData = oldData.slice(0, THUMBNAIL_COUNT);
  9642.     newData = newData.slice(0, THUMBNAIL_COUNT);
  9643.  
  9644.     // Copy over pinned sites directly.
  9645.     for (var j = 0; j < newData.length; j++) {
  9646.       if (newData[j].pinned) {
  9647.         oldData[j] = newData[j];
  9648.         // Mark the entry as 'updated' so we don't try to update again.
  9649.         oldData[j].updated = true;
  9650.         // Mark the newData page as 'used' so we don't try to re-use it.
  9651.         newData[j].used = true;
  9652.       }
  9653.     }
  9654.  
  9655.     // Look through old pages; if they exist in the newData list, keep them
  9656.     // where they are.
  9657.     for (var i = 0; i < oldData.length; i++) {
  9658.       if (!oldData[i] || oldData[i].updated)
  9659.         continue;
  9660.  
  9661.       for (var j = 0; j < newData.length; j++) {
  9662.         if (newData[j].used)
  9663.           continue;
  9664.  
  9665.         if (newData[j].url == oldData[i].url) {
  9666.           // The background image and other data may have changed.
  9667.           oldData[i] = newData[j];
  9668.           oldData[i].updated = true;
  9669.           newData[j].used = true;
  9670.           break;
  9671.         }
  9672.       }
  9673.     }
  9674.  
  9675.     // Look through old pages that haven't been updated yet; replace them.
  9676.     for (var i = 0; i < oldData.length; i++) {
  9677.       if (oldData[i] && oldData[i].updated)
  9678.         continue;
  9679.  
  9680.       for (var j = 0; j < newData.length; j++) {
  9681.         if (newData[j].used)
  9682.           continue;
  9683.  
  9684.         oldData[i] = newData[j];
  9685.         oldData[i].updated = true;
  9686.         newData[j].used = true;
  9687.         break;
  9688.       }
  9689.  
  9690.       if (oldData[i] && !oldData[i].updated)
  9691.         oldData[i] = null;
  9692.     }
  9693.  
  9694.     // Clear 'updated' flags so this function will work next time it's called.
  9695.     for (var i = 0; i < THUMBNAIL_COUNT; i++) {
  9696.       if (oldData[i])
  9697.         oldData[i].updated = false;
  9698.     }
  9699.  
  9700.     return oldData;
  9701.   };
  9702.  
  9703.   return {
  9704.     MostVisitedPage: MostVisitedPage,
  9705.     refreshData: refreshData,
  9706.   };
  9707. });
  9708.  
  9709. document.addEventListener('ntpLoaded', ntp.MostVisitedPage.onLoaded);
  9710. </script>
  9711. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9712. // Use of this source code is governed by a BSD-style license that can be
  9713. // found in the LICENSE file.
  9714.  
  9715. /**
  9716.  * @fileoverview Trash
  9717.  * This is the class for the trash can that appears when dragging an app.
  9718.  */
  9719.  
  9720. cr.define('ntp', function() {
  9721.   'use strict';
  9722.  
  9723.   function Trash(trash) {
  9724.     trash.__proto__ = Trash.prototype;
  9725.     trash.initialize();
  9726.     return trash;
  9727.   }
  9728.  
  9729.   Trash.prototype = {
  9730.     __proto__: HTMLDivElement.prototype,
  9731.  
  9732.     initialize: function(element) {
  9733.       this.dragWrapper_ = new cr.ui.DragWrapper(this, this);
  9734.     },
  9735.  
  9736.     /**
  9737.      * Determines whether we are interested in the drag data for |e|.
  9738.      * @param {Event} e The event from drag enter.
  9739.      * @return {boolean} True if we are interested in the drag data for |e|.
  9740.      */
  9741.     shouldAcceptDrag: function(e) {
  9742.       var tile = ntp.getCurrentlyDraggingTile();
  9743.       if (!tile)
  9744.         return false;
  9745.  
  9746.       return tile.firstChild.canBeRemoved();
  9747.     },
  9748.  
  9749.     /**
  9750.      * Drag over handler.
  9751.      * @param {Event} e The drag event.
  9752.      */
  9753.     doDragOver: function(e) {
  9754.       ntp.getCurrentlyDraggingTile().dragClone.classList.add(
  9755.           'hovering-on-trash');
  9756.       ntp.setCurrentDropEffect(e.dataTransfer, 'move');
  9757.       e.preventDefault();
  9758.     },
  9759.  
  9760.     /**
  9761.      * Drag enter handler.
  9762.      * @param {Event} e The drag event.
  9763.      */
  9764.     doDragEnter: function(e) {
  9765.       this.doDragOver(e);
  9766.     },
  9767.  
  9768.     /**
  9769.      * Drop handler.
  9770.      * @param {Event} e The drag event.
  9771.      */
  9772.     doDrop: function(e) {
  9773.       e.preventDefault();
  9774.  
  9775.       var tile = ntp.getCurrentlyDraggingTile();
  9776.       tile.firstChild.removeFromChrome();
  9777.       tile.landedOnTrash = true;
  9778.     },
  9779.  
  9780.     /**
  9781.      * Drag leave handler.
  9782.      * @param {Event} e The drag event.
  9783.      */
  9784.     doDragLeave: function(e) {
  9785.       ntp.getCurrentlyDraggingTile().dragClone.classList.remove(
  9786.           'hovering-on-trash');
  9787.     },
  9788.   };
  9789.  
  9790.   return {
  9791.     Trash: Trash,
  9792.   };
  9793. });
  9794. </script>
  9795. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9796. // Use of this source code is governed by a BSD-style license that can be
  9797. // found in the LICENSE file.
  9798.  
  9799. /**
  9800.  * @fileoverview PageListView implementation.
  9801.  * PageListView manages page list, dot list, switcher buttons and handles apps
  9802.  * pages callbacks from backend.
  9803.  *
  9804.  * Note that you need to have AppLauncherHandler in your WebUI to use this code.
  9805.  */
  9806.  
  9807. cr.define('ntp', function() {
  9808.   'use strict';
  9809.  
  9810.   /**
  9811.    * Creates a PageListView object.
  9812.    * @constructor
  9813.    * @extends {Object}
  9814.    */
  9815.   function PageListView() {
  9816.   }
  9817.  
  9818.   PageListView.prototype = {
  9819.     /**
  9820.      * The CardSlider object to use for changing app pages.
  9821.      * @type {CardSlider|undefined}
  9822.      */
  9823.     cardSlider: undefined,
  9824.  
  9825.     /**
  9826.      * The frame div for this.cardSlider.
  9827.      * @type {!Element|undefined}
  9828.      */
  9829.     sliderFrame: undefined,
  9830.  
  9831.     /**
  9832.      * The 'page-list' element.
  9833.      * @type {!Element|undefined}
  9834.      */
  9835.     pageList: undefined,
  9836.  
  9837.     /**
  9838.      * A list of all 'tile-page' elements.
  9839.      * @type {!NodeList|undefined}
  9840.      */
  9841.     tilePages: undefined,
  9842.  
  9843.     /**
  9844.      * A list of all 'apps-page' elements.
  9845.      * @type {!NodeList|undefined}
  9846.      */
  9847.     appsPages: undefined,
  9848.  
  9849.     /**
  9850.      * The Suggestions page.
  9851.      * @type {!Element|undefined}
  9852.      */
  9853.     suggestionsPage: undefined,
  9854.  
  9855.     /**
  9856.      * The Most Visited page.
  9857.      * @type {!Element|undefined}
  9858.      */
  9859.     mostVisitedPage: undefined,
  9860.  
  9861.     /**
  9862.      * The 'dots-list' element.
  9863.      * @type {!Element|undefined}
  9864.      */
  9865.     dotList: undefined,
  9866.  
  9867.     /**
  9868.      * The left and right paging buttons.
  9869.      * @type {!Element|undefined}
  9870.      */
  9871.     pageSwitcherStart: undefined,
  9872.     pageSwitcherEnd: undefined,
  9873.  
  9874.     /**
  9875.      * The 'trash' element.  Note that technically this is unnecessary,
  9876.      * JavaScript creates the object for us based on the id.  But I don't want
  9877.      * to rely on the ID being the same, and JSCompiler doesn't know about it.
  9878.      * @type {!Element|undefined}
  9879.      */
  9880.     trash: undefined,
  9881.  
  9882.     /**
  9883.      * The type of page that is currently shown. The value is a numerical ID.
  9884.      * @type {number}
  9885.      */
  9886.     shownPage: 0,
  9887.  
  9888.     /**
  9889.      * The index of the page that is currently shown, within the page type.
  9890.      * For example if the third Apps page is showing, this will be 2.
  9891.      * @type {number}
  9892.      */
  9893.     shownPageIndex: 0,
  9894.  
  9895.     /**
  9896.      * EventTracker for managing event listeners for page events.
  9897.      * @type {!EventTracker}
  9898.      */
  9899.     eventTracker: new EventTracker,
  9900.  
  9901.     /**
  9902.      * If non-null, this is the ID of the app to highlight to the user the next
  9903.      * time getAppsCallback runs. "Highlight" in this case means to switch to
  9904.      * the page and run the new tile animation.
  9905.      * @type {?string}
  9906.      */
  9907.     highlightAppId: null,
  9908.  
  9909.     /**
  9910.      * Initializes page list view.
  9911.      * @param {!Element} pageList A DIV element to host all pages.
  9912.      * @param {!Element} dotList An UL element to host nav dots. Each dot
  9913.      *     represents a page.
  9914.      * @param {!Element} cardSliderFrame The card slider frame that hosts
  9915.      *     pageList and switcher buttons.
  9916.      * @param {!Element|undefined} opt_trash Optional trash element.
  9917.      * @param {!Element|undefined} opt_pageSwitcherStart Optional start page
  9918.      *     switcher button.
  9919.      * @param {!Element|undefined} opt_pageSwitcherEnd Optional end page
  9920.      *     switcher button.
  9921.      */
  9922.     initialize: function(pageList, dotList, cardSliderFrame, opt_trash,
  9923.                          opt_pageSwitcherStart, opt_pageSwitcherEnd) {
  9924.       this.pageList = pageList;
  9925.  
  9926.       this.dotList = dotList;
  9927.       cr.ui.decorate(this.dotList, ntp.DotList);
  9928.  
  9929.       this.trash = opt_trash;
  9930.       if (this.trash)
  9931.         new ntp.Trash(this.trash);
  9932.  
  9933.       this.pageSwitcherStart = opt_pageSwitcherStart;
  9934.       if (this.pageSwitcherStart)
  9935.         ntp.initializePageSwitcher(this.pageSwitcherStart);
  9936.  
  9937.       this.pageSwitcherEnd = opt_pageSwitcherEnd;
  9938.       if (this.pageSwitcherEnd)
  9939.         ntp.initializePageSwitcher(this.pageSwitcherEnd);
  9940.  
  9941.       this.shownPage = loadTimeData.getInteger('shown_page_type');
  9942.       this.shownPageIndex = loadTimeData.getInteger('shown_page_index');
  9943.  
  9944.       if (loadTimeData.getBoolean('showApps')) {
  9945.         // Request data on the apps so we can fill them in.
  9946.         // Note that this is kicked off asynchronously.  'getAppsCallback' will
  9947.         // be invoked at some point after this function returns.
  9948.         chrome.send('getApps');
  9949.       } else {
  9950.         // No apps page.
  9951.         if (this.shownPage == loadTimeData.getInteger('apps_page_id')) {
  9952.           this.setShownPage_(
  9953.               loadTimeData.getInteger('most_visited_page_id'), 0);
  9954.         }
  9955.  
  9956.         document.body.classList.add('bare-minimum');
  9957.       }
  9958.  
  9959.       document.addEventListener('keydown', this.onDocKeyDown_.bind(this));
  9960.  
  9961.       this.tilePages = this.pageList.getElementsByClassName('tile-page');
  9962.       this.appsPages = this.pageList.getElementsByClassName('apps-page');
  9963.  
  9964.       // Initialize the cardSlider without any cards at the moment.
  9965.       this.sliderFrame = cardSliderFrame;
  9966.       this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList,
  9967.           this.sliderFrame.offsetWidth);
  9968.  
  9969.       // Prevent touch events from triggering any sort of native scrolling if
  9970.       // there are multiple cards in the slider frame.
  9971.       var cardSlider = this.cardSlider;
  9972.       cardSliderFrame.addEventListener('touchmove', function(e) {
  9973.         if (cardSlider.cardCount <= 1)
  9974.           return;
  9975.         e.preventDefault();
  9976.       }, true);
  9977.  
  9978.       // Handle mousewheel events anywhere in the card slider, so that wheel
  9979.       // events on the page switchers will still scroll the page.
  9980.       // This listener must be added before the card slider is initialized,
  9981.       // because it needs to be called before the card slider's handler.
  9982.       cardSliderFrame.addEventListener('mousewheel', function(e) {
  9983.         if (cardSlider.currentCardValue.handleMouseWheel(e)) {
  9984.           e.preventDefault();  // Prevent default scroll behavior.
  9985.           e.stopImmediatePropagation();  // Prevent horizontal card flipping.
  9986.         }
  9987.       });
  9988.  
  9989.       this.cardSlider.initialize(
  9990.           loadTimeData.getBoolean('isSwipeTrackingFromScrollEventsEnabled'));
  9991.  
  9992.       // Handle events from the card slider.
  9993.       this.pageList.addEventListener('cardSlider:card_changed',
  9994.                                      this.onCardChanged_.bind(this));
  9995.       this.pageList.addEventListener('cardSlider:card_added',
  9996.                                      this.onCardAdded_.bind(this));
  9997.       this.pageList.addEventListener('cardSlider:card_removed',
  9998.                                      this.onCardRemoved_.bind(this));
  9999.  
  10000.       // Ensure the slider is resized appropriately with the window.
  10001.       window.addEventListener('resize', this.onWindowResize_.bind(this));
  10002.  
  10003.       // Update apps when online state changes.
  10004.       window.addEventListener('online',
  10005.           this.updateOfflineEnabledApps_.bind(this));
  10006.       window.addEventListener('offline',
  10007.           this.updateOfflineEnabledApps_.bind(this));
  10008.     },
  10009.  
  10010.     /**
  10011.      * Appends a tile page.
  10012.      *
  10013.      * @param {TilePage} page The page element.
  10014.      * @param {string} title The title of the tile page.
  10015.      * @param {bool} titleIsEditable If true, the title can be changed.
  10016.      * @param {TilePage} opt_refNode Optional reference node to insert in front
  10017.      *     of.
  10018.      * When opt_refNode is falsey, |page| will just be appended to the end of
  10019.      * the page list.
  10020.      */
  10021.     appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
  10022.       if (opt_refNode) {
  10023.         var refIndex = this.getTilePageIndex(opt_refNode);
  10024.         this.cardSlider.addCardAtIndex(page, refIndex);
  10025.       } else {
  10026.         this.cardSlider.appendCard(page);
  10027.       }
  10028.  
  10029.       // Remember special MostVisitedPage.
  10030.       if (typeof ntp.MostVisitedPage != 'undefined' &&
  10031.           page instanceof ntp.MostVisitedPage) {
  10032.         assert(this.tilePages.length == 1,
  10033.                'MostVisitedPage should be added as first tile page');
  10034.         this.mostVisitedPage = page;
  10035.       }
  10036.  
  10037.       if (typeof ntp.SuggestionsPage != 'undefined' &&
  10038.           page instanceof ntp.SuggestionsPage) {
  10039.         this.suggestionsPage = page;
  10040.       }
  10041.  
  10042.       // If we're appending an AppsPage and it's a temporary page, animate it.
  10043.       var animate = page instanceof ntp.AppsPage &&
  10044.                     page.classList.contains('temporary');
  10045.       // Make a deep copy of the dot template to add a new one.
  10046.       var newDot = new ntp.NavDot(page, title, titleIsEditable, animate);
  10047.       page.navigationDot = newDot;
  10048.       this.dotList.insertBefore(newDot,
  10049.                                 opt_refNode ? opt_refNode.navigationDot : null);
  10050.       // Set a tab index on the first dot.
  10051.       if (this.dotList.dots.length == 1)
  10052.         newDot.tabIndex = 3;
  10053.  
  10054.       this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this));
  10055.     },
  10056.  
  10057.     /**
  10058.      * Called by chrome when an app has changed positions.
  10059.      * @param {Object} appData The data for the app. This contains page and
  10060.      *     position indices.
  10061.      */
  10062.     appMoved: function(appData) {
  10063.       assert(loadTimeData.getBoolean('showApps'));
  10064.  
  10065.       var app = $(appData.id);
  10066.       assert(app, 'trying to move an app that doesn\'t exist');
  10067.       app.remove(false);
  10068.  
  10069.       this.appsPages[appData.page_index].insertApp(appData, false);
  10070.     },
  10071.  
  10072.     /**
  10073.      * Called by chrome when an existing app has been disabled or
  10074.      * removed/uninstalled from chrome.
  10075.      * @param {Object} appData A data structure full of relevant information for
  10076.      *     the app.
  10077.      * @param {boolean} isUninstall True if the app is being uninstalled;
  10078.      *     false if the app is being disabled.
  10079.      * @param {boolean} fromPage True if the removal was from the current page.
  10080.      */
  10081.     appRemoved: function(appData, isUninstall, fromPage) {
  10082.       assert(loadTimeData.getBoolean('showApps'));
  10083.  
  10084.       var app = $(appData.id);
  10085.       assert(app, 'trying to remove an app that doesn\'t exist');
  10086.  
  10087.       if (!isUninstall)
  10088.         app.replaceAppData(appData);
  10089.       else
  10090.         app.remove(!!fromPage);
  10091.     },
  10092.  
  10093.     /**
  10094.      * @return {boolean} If the page is still starting up.
  10095.      * @private
  10096.      */
  10097.     isStartingUp_: function() {
  10098.       return document.documentElement.classList.contains('starting-up');
  10099.     },
  10100.  
  10101.     /**
  10102.      * Tracks whether apps have been loaded at least once.
  10103.      * @type {boolean}
  10104.      * @private
  10105.      */
  10106.     appsLoaded_: false,
  10107.  
  10108.     /**
  10109.      * Callback invoked by chrome with the apps available.
  10110.      *
  10111.      * Note that calls to this function can occur at any time, not just in
  10112.      * response to a getApps request. For example, when a user
  10113.      * installs/uninstalls an app on another synchronized devices.
  10114.      * @param {Object} data An object with all the data on available
  10115.      *        applications.
  10116.      */
  10117.     getAppsCallback: function(data) {
  10118.       assert(loadTimeData.getBoolean('showApps'));
  10119.  
  10120.       var startTime = Date.now();
  10121.  
  10122.       // Remember this to select the correct card when done rebuilding.
  10123.       var prevCurrentCard = this.cardSlider.currentCard;
  10124.  
  10125.       // Make removal of pages and dots as quick as possible with less DOM
  10126.       // operations, reflows, or repaints. We set currentCard = 0 and remove
  10127.       // from the end to not encounter any auto-magic card selections in the
  10128.       // process and we hide the card slider throughout.
  10129.       this.cardSlider.currentCard = 0;
  10130.  
  10131.       // Clear any existing apps pages and dots.
  10132.       // TODO(rbyers): It might be nice to preserve animation of dots after an
  10133.       // uninstall. Could we re-use the existing page and dot elements?  It
  10134.       // seems unfortunate to have Chrome send us the entire apps list after an
  10135.       // uninstall.
  10136.       while (this.appsPages.length > 0)
  10137.         this.removeTilePageAndDot_(this.appsPages[this.appsPages.length - 1]);
  10138.  
  10139.       // Get the array of apps and add any special synthesized entries
  10140.       var apps = data.apps;
  10141.  
  10142.       // Get a list of page names
  10143.       var pageNames = data.appPageNames;
  10144.  
  10145.       function stringListIsEmpty(list) {
  10146.         for (var i = 0; i < list.length; i++) {
  10147.           if (list[i])
  10148.             return false;
  10149.         }
  10150.         return true;
  10151.       }
  10152.  
  10153.       // Sort by launch ordinal
  10154.       apps.sort(function(a, b) {
  10155.         return a.app_launch_ordinal > b.app_launch_ordinal ? 1 :
  10156.           a.app_launch_ordinal < b.app_launch_ordinal ? -1 : 0;
  10157.       });
  10158.  
  10159.       // An app to animate (in case it was just installed).
  10160.       var highlightApp;
  10161.  
  10162.       // If there are any pages after the apps, add new pages before them.
  10163.       var lastAppsPage = (this.appsPages.length > 0) ?
  10164.           this.appsPages[this.appsPages.length - 1] : null;
  10165.       var lastAppsPageIndex = (lastAppsPage != null) ?
  10166.           Array.prototype.indexOf.call(this.tilePages, lastAppsPage) : -1;
  10167.       var nextPageAfterApps = lastAppsPageIndex != -1 ?
  10168.           this.tilePages[lastAppsPageIndex + 1] : null;
  10169.  
  10170.       // Add the apps, creating pages as necessary
  10171.       for (var i = 0; i < apps.length; i++) {
  10172.         var app = apps[i];
  10173.         var pageIndex = app.page_index || 0;
  10174.         while (pageIndex >= this.appsPages.length) {
  10175.           var pageName = loadTimeData.getString('appDefaultPageName');
  10176.           if (this.appsPages.length < pageNames.length)
  10177.             pageName = pageNames[this.appsPages.length];
  10178.  
  10179.           var origPageCount = this.appsPages.length;
  10180.           this.appendTilePage(new ntp.AppsPage(), pageName, true,
  10181.                               nextPageAfterApps);
  10182.           // Confirm that appsPages is a live object, updated when a new page is
  10183.           // added (otherwise we'd have an infinite loop)
  10184.           assert(this.appsPages.length == origPageCount + 1,
  10185.                  'expected new page');
  10186.         }
  10187.  
  10188.         if (app.id == this.highlightAppId)
  10189.           highlightApp = app;
  10190.         else
  10191.           this.appsPages[pageIndex].insertApp(app, false);
  10192.       }
  10193.  
  10194.       this.cardSlider.currentCard = prevCurrentCard;
  10195.  
  10196.       if (highlightApp)
  10197.         this.appAdded(highlightApp, true);
  10198.  
  10199.       logEvent('apps.layout: ' + (Date.now() - startTime));
  10200.  
  10201.       // Tell the slider about the pages and mark the current page.
  10202.       this.updateSliderCards();
  10203.       this.cardSlider.currentCardValue.navigationDot.classList.add('selected');
  10204.  
  10205.       if (!this.appsLoaded_) {
  10206.         this.appsLoaded_ = true;
  10207.         cr.dispatchSimpleEvent(document, 'sectionready', true, true);
  10208.       }
  10209.     },
  10210.  
  10211.     /**
  10212.      * Called by chrome when a new app has been added to chrome or has been
  10213.      * enabled if previously disabled.
  10214.      * @param {Object} appData A data structure full of relevant information for
  10215.      *     the app.
  10216.      * @param {boolean=} opt_highlight Whether the app about to be added should
  10217.      *     be highlighted.
  10218.      */
  10219.     appAdded: function(appData, opt_highlight) {
  10220.       assert(loadTimeData.getBoolean('showApps'));
  10221.  
  10222.       if (appData.id == this.highlightAppId) {
  10223.         opt_highlight = true;
  10224.         this.highlightAppId = null;
  10225.       }
  10226.  
  10227.       var pageIndex = appData.page_index || 0;
  10228.  
  10229.       if (pageIndex >= this.appsPages.length) {
  10230.         while (pageIndex >= this.appsPages.length) {
  10231.           this.appendTilePage(new ntp.AppsPage(),
  10232.                               loadTimeData.getString('appDefaultPageName'),
  10233.                               true);
  10234.         }
  10235.         this.updateSliderCards();
  10236.       }
  10237.  
  10238.       var page = this.appsPages[pageIndex];
  10239.       var app = $(appData.id);
  10240.       if (app) {
  10241.         app.replaceAppData(appData);
  10242.       } else if (opt_highlight) {
  10243.         page.insertAndHighlightApp(appData);
  10244.         this.setShownPage_(loadTimeData.getInteger('apps_page_id'),
  10245.                            appData.page_index);
  10246.       } else {
  10247.         page.insertApp(appData, false);
  10248.       }
  10249.     },
  10250.  
  10251.     /**
  10252.      * Callback invoked by chrome whenever an app preference changes.
  10253.      * @param {Object} data An object with all the data on available
  10254.      *     applications.
  10255.      */
  10256.     appsPrefChangedCallback: function(data) {
  10257.       assert(loadTimeData.getBoolean('showApps'));
  10258.  
  10259.       for (var i = 0; i < data.apps.length; ++i) {
  10260.         $(data.apps[i].id).appData = data.apps[i];
  10261.       }
  10262.  
  10263.       // Set the App dot names. Skip the first dot (Most Visited).
  10264.       var dots = this.dotList.getElementsByClassName('dot');
  10265.       var start = this.mostVisitedPage ? 1 : 0;
  10266.       for (var i = start; i < dots.length; ++i) {
  10267.         dots[i].displayTitle = data.appPageNames[i - start] || '';
  10268.       }
  10269.     },
  10270.  
  10271.     /**
  10272.      * Invoked whenever the pages in apps-page-list have changed so that
  10273.      * the Slider knows about the new elements.
  10274.      */
  10275.     updateSliderCards: function() {
  10276.       var pageNo = Math.max(0, Math.min(this.cardSlider.currentCard,
  10277.                                         this.tilePages.length - 1));
  10278.       this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages),
  10279.                                pageNo);
  10280.       switch (this.shownPage) {
  10281.         case loadTimeData.getInteger('apps_page_id'):
  10282.           this.cardSlider.selectCardByValue(
  10283.               this.appsPages[Math.min(this.shownPageIndex,
  10284.                                       this.appsPages.length - 1)]);
  10285.           break;
  10286.         case loadTimeData.getInteger('most_visited_page_id'):
  10287.           if (this.mostVisitedPage)
  10288.             this.cardSlider.selectCardByValue(this.mostVisitedPage);
  10289.           break;
  10290.         case loadTimeData.getInteger('suggestions_page_id'):
  10291.           if (this.suggestionsPage)
  10292.             this.cardSlider.selectCardByValue(this.suggestionsPage);
  10293.           break;
  10294.       }
  10295.     },
  10296.  
  10297.     /**
  10298.      * Called whenever tiles should be re-arranging themselves out of the way
  10299.      * of a moving or insert tile.
  10300.      */
  10301.     enterRearrangeMode: function() {
  10302.       if (loadTimeData.getBoolean('showApps')) {
  10303.         var tempPage = new ntp.AppsPage();
  10304.         tempPage.classList.add('temporary');
  10305.         var pageName = loadTimeData.getString('appDefaultPageName');
  10306.         this.appendTilePage(tempPage, pageName, true);
  10307.       }
  10308.  
  10309.       if (ntp.getCurrentlyDraggingTile().firstChild.canBeRemoved()) {
  10310.         $('footer').classList.add('showing-trash-mode');
  10311.         $('footer-menu-container').style.minWidth = $('trash').offsetWidth -
  10312.             $('chrome-web-store-link').offsetWidth + 'px';
  10313.       }
  10314.  
  10315.       document.documentElement.classList.add('dragging-mode');
  10316.     },
  10317.  
  10318.     /**
  10319.      * Invoked whenever some app is released
  10320.      */
  10321.     leaveRearrangeMode: function() {
  10322.       var tempPage = document.querySelector('.tile-page.temporary');
  10323.       if (tempPage) {
  10324.         var dot = tempPage.navigationDot;
  10325.         if (!tempPage.tileCount &&
  10326.             tempPage != this.cardSlider.currentCardValue) {
  10327.           this.removeTilePageAndDot_(tempPage, true);
  10328.         } else {
  10329.           tempPage.classList.remove('temporary');
  10330.           this.saveAppPageName(tempPage,
  10331.                                loadTimeData.getString('appDefaultPageName'));
  10332.         }
  10333.       }
  10334.  
  10335.       $('footer').classList.remove('showing-trash-mode');
  10336.       $('footer-menu-container').style.minWidth = '';
  10337.       document.documentElement.classList.remove('dragging-mode');
  10338.     },
  10339.  
  10340.     /**
  10341.      * Callback for the 'pagelayout' event.
  10342.      * @param {Event} e The event.
  10343.      */
  10344.     onPageLayout_: function(e) {
  10345.       if (Array.prototype.indexOf.call(this.tilePages, e.currentTarget) !=
  10346.           this.cardSlider.currentCard) {
  10347.         return;
  10348.       }
  10349.  
  10350.       this.updatePageSwitchers();
  10351.     },
  10352.  
  10353.     /**
  10354.      * Adjusts the size and position of the page switchers according to the
  10355.      * layout of the current card, and updates the aria-label attributes of
  10356.      * the page switchers.
  10357.      */
  10358.     updatePageSwitchers: function() {
  10359.       if (!this.pageSwitcherStart || !this.pageSwitcherEnd)
  10360.         return;
  10361.  
  10362.       var page = this.cardSlider.currentCardValue;
  10363.  
  10364.       this.pageSwitcherStart.hidden = !page ||
  10365.           (this.cardSlider.currentCard == 0);
  10366.       this.pageSwitcherEnd.hidden = !page ||
  10367.           (this.cardSlider.currentCard == this.cardSlider.cardCount - 1);
  10368.  
  10369.       if (!page)
  10370.         return;
  10371.  
  10372.       var pageSwitcherLeft = isRTL() ? this.pageSwitcherEnd :
  10373.                                        this.pageSwitcherStart;
  10374.       var pageSwitcherRight = isRTL() ? this.pageSwitcherStart :
  10375.                                         this.pageSwitcherEnd;
  10376.       var scrollbarWidth = page.scrollbarWidth;
  10377.       pageSwitcherLeft.style.width =
  10378.           (page.sideMargin + 13) + 'px';
  10379.       pageSwitcherLeft.style.left = '0';
  10380.       pageSwitcherRight.style.width =
  10381.           (page.sideMargin - scrollbarWidth + 13) + 'px';
  10382.       pageSwitcherRight.style.right = scrollbarWidth + 'px';
  10383.  
  10384.       var offsetTop = page.querySelector('.tile-page-content').offsetTop + 'px';
  10385.       pageSwitcherLeft.style.top = offsetTop;
  10386.       pageSwitcherRight.style.top = offsetTop;
  10387.       pageSwitcherLeft.style.paddingBottom = offsetTop;
  10388.       pageSwitcherRight.style.paddingBottom = offsetTop;
  10389.  
  10390.       // Update the aria-label attributes of the two page switchers.
  10391.       this.pageSwitcherStart.updateButtonAccessibleLabel(this.dotList.dots);
  10392.       this.pageSwitcherEnd.updateButtonAccessibleLabel(this.dotList.dots);
  10393.     },
  10394.  
  10395.     /**
  10396.      * Returns the index of the given apps page.
  10397.      * @param {AppsPage} page The AppsPage we wish to find.
  10398.      * @return {number} The index of |page| or -1 if it is not in the
  10399.      *    collection.
  10400.      */
  10401.     getAppsPageIndex: function(page) {
  10402.       return Array.prototype.indexOf.call(this.appsPages, page);
  10403.     },
  10404.  
  10405.     /**
  10406.      * Handler for cardSlider:card_changed events from this.cardSlider.
  10407.      * @param {Event} e The cardSlider:card_changed event.
  10408.      * @private
  10409.      */
  10410.     onCardChanged_: function(e) {
  10411.       var page = e.cardSlider.currentCardValue;
  10412.  
  10413.       // Don't change shownPage until startup is done (and page changes actually
  10414.       // reflect user actions).
  10415.       if (!this.isStartingUp_()) {
  10416.         if (page.classList.contains('apps-page')) {
  10417.           this.setShownPage_(loadTimeData.getInteger('apps_page_id'),
  10418.                              this.getAppsPageIndex(page));
  10419.         } else if (page.classList.contains('most-visited-page')) {
  10420.           this.setShownPage_(
  10421.               loadTimeData.getInteger('most_visited_page_id'), 0);
  10422.         } else if (page.classList.contains('suggestions-page')) {
  10423.           this.setShownPage_(loadTimeData.getInteger('suggestions_page_id'), 0);
  10424.         } else {
  10425.           console.error('unknown page selected');
  10426.         }
  10427.       }
  10428.  
  10429.       // Update the active dot
  10430.       var curDot = this.dotList.getElementsByClassName('selected')[0];
  10431.       if (curDot)
  10432.         curDot.classList.remove('selected');
  10433.       page.navigationDot.classList.add('selected');
  10434.       this.updatePageSwitchers();
  10435.     },
  10436.  
  10437.     /**
  10438.      * Saves/updates the newly selected page to open when first loading the NTP.
  10439.      * @type {number} shownPage The new shown page type.
  10440.      * @type {number} shownPageIndex The new shown page index.
  10441.      * @private
  10442.      */
  10443.     setShownPage_: function(shownPage, shownPageIndex) {
  10444.       assert(shownPageIndex >= 0);
  10445.       this.shownPage = shownPage;
  10446.       this.shownPageIndex = shownPageIndex;
  10447.       chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
  10448.     },
  10449.  
  10450.     /**
  10451.      * Listen for card additions to update the page switchers or the current
  10452.      * card accordingly.
  10453.      * @param {Event} e A card removed or added event.
  10454.      */
  10455.     onCardAdded_: function(e) {
  10456.       // When the second arg passed to insertBefore is falsey, it acts just like
  10457.       // appendChild.
  10458.       this.pageList.insertBefore(e.addedCard, this.tilePages[e.addedIndex]);
  10459.       this.onCardAddedOrRemoved_();
  10460.     },
  10461.  
  10462.     /**
  10463.      * Listen for card removals to update the page switchers or the current card
  10464.      * accordingly.
  10465.      * @param {Event} e A card removed or added event.
  10466.      */
  10467.     onCardRemoved_: function(e) {
  10468.       e.removedCard.parentNode.removeChild(e.removedCard);
  10469.       this.onCardAddedOrRemoved_();
  10470.     },
  10471.  
  10472.     /**
  10473.      * Called when a card is removed or added.
  10474.      * @private
  10475.      */
  10476.     onCardAddedOrRemoved_: function() {
  10477.       if (this.isStartingUp_())
  10478.         return;
  10479.  
  10480.       // Without repositioning there were issues - http://crbug.com/133457.
  10481.       this.cardSlider.repositionFrame();
  10482.       this.updatePageSwitchers();
  10483.     },
  10484.  
  10485.     /**
  10486.      * Save the name of an apps page.
  10487.      * Store the apps page name into the preferences store.
  10488.      * @param {AppsPage} appsPage The app page for which we wish to save.
  10489.      * @param {string} name The name of the page.
  10490.      */
  10491.     saveAppPageName: function(appPage, name) {
  10492.       var index = this.getAppsPageIndex(appPage);
  10493.       assert(index != -1);
  10494.       chrome.send('saveAppPageName', [name, index]);
  10495.     },
  10496.  
  10497.     /**
  10498.      * Window resize handler.
  10499.      * @private
  10500.      */
  10501.     onWindowResize_: function(e) {
  10502.       this.cardSlider.resize(this.sliderFrame.offsetWidth);
  10503.       this.updatePageSwitchers();
  10504.     },
  10505.  
  10506.     /**
  10507.      * Listener for offline status change events. Updates apps that are
  10508.      * not offline-enabled to be grayscale if the browser is offline.
  10509.      * @private
  10510.      */
  10511.     updateOfflineEnabledApps_: function() {
  10512.       var apps = document.querySelectorAll('.app');
  10513.       for (var i = 0; i < apps.length; ++i) {
  10514.         if (apps[i].appData.enabled && !apps[i].appData.offline_enabled) {
  10515.           apps[i].setIcon();
  10516.           apps[i].loadIcon();
  10517.         }
  10518.       }
  10519.     },
  10520.  
  10521.     /**
  10522.      * Handler for key events on the page. Ctrl-Arrow will switch the visible
  10523.      * page.
  10524.      * @param {Event} e The KeyboardEvent.
  10525.      * @private
  10526.      */
  10527.     onDocKeyDown_: function(e) {
  10528.       if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey)
  10529.         return;
  10530.  
  10531.       var direction = 0;
  10532.       if (e.keyIdentifier == 'Left')
  10533.         direction = -1;
  10534.       else if (e.keyIdentifier == 'Right')
  10535.         direction = 1;
  10536.       else
  10537.         return;
  10538.  
  10539.       var cardIndex =
  10540.           (this.cardSlider.currentCard + direction +
  10541.            this.cardSlider.cardCount) % this.cardSlider.cardCount;
  10542.       this.cardSlider.selectCard(cardIndex, true);
  10543.  
  10544.       e.stopPropagation();
  10545.     },
  10546.  
  10547.     /**
  10548.      * Returns the index of a given tile page.
  10549.      * @param {TilePage} page The TilePage we wish to find.
  10550.      * @return {number} The index of |page| or -1 if it is not in the
  10551.      *    collection.
  10552.      */
  10553.     getTilePageIndex: function(page) {
  10554.       return Array.prototype.indexOf.call(this.tilePages, page);
  10555.     },
  10556.  
  10557.     /**
  10558.      * Removes a page and navigation dot (if the navdot exists).
  10559.      * @param {TilePage} page The page to be removed.
  10560.      * @param {boolean=} opt_animate If the removal should be animated.
  10561.      */
  10562.     removeTilePageAndDot_: function(page, opt_animate) {
  10563.       if (page.navigationDot)
  10564.         page.navigationDot.remove(opt_animate);
  10565.       this.cardSlider.removeCard(page);
  10566.     },
  10567.   };
  10568.  
  10569.   return {
  10570.     PageListView: PageListView
  10571.   };
  10572. });
  10573. </script>
  10574. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  10575. // Use of this source code is governed by a BSD-style license that can be
  10576. // found in the LICENSE file.
  10577.  
  10578. /**
  10579.  * @fileoverview Page switcher
  10580.  * This is the class for the left and right navigation arrows that switch
  10581.  * between pages.
  10582.  */
  10583. cr.define('ntp', function() {
  10584.  
  10585.   function PageSwitcher() {
  10586.   }
  10587.  
  10588.   PageSwitcher.template = {
  10589.     __proto__: HTMLButtonElement.prototype,
  10590.  
  10591.     decorate: function(el) {
  10592.       el.__proto__ = PageSwitcher.template;
  10593.  
  10594.       el.addEventListener('click', el.activate_);
  10595.  
  10596.       el.direction_ = el.id == 'page-switcher-start' ? -1 : 1;
  10597.  
  10598.       el.dragWrapper_ = new cr.ui.DragWrapper(el, el);
  10599.     },
  10600.  
  10601.     /**
  10602.      * Activate the switcher (go to the next card).
  10603.      * @private
  10604.      */
  10605.     activate_: function() {
  10606.       ntp.getCardSlider().selectCard(this.nextCardIndex_(), true);
  10607.     },
  10608.  
  10609.     /**
  10610.      * Calculate the index of the card that this button will switch to.
  10611.      * @private
  10612.      */
  10613.     nextCardIndex_: function() {
  10614.       var cardSlider = ntp.getCardSlider();
  10615.       var index = cardSlider.currentCard + this.direction_;
  10616.       var numCards = cardSlider.cardCount - 1;
  10617.       return Math.max(0, Math.min(index, numCards));
  10618.     },
  10619.  
  10620.     /**
  10621.      * Update the accessible label attribute of this button, based on the
  10622.      * current position in the card slider and the names of the cards.
  10623.      * @param {NodeList} dots The dot elements which display the names of the
  10624.      *     cards.
  10625.      */
  10626.     updateButtonAccessibleLabel: function(dots) {
  10627.       var currentIndex = ntp.getCardSlider().currentCard;
  10628.       var nextCardIndex = this.nextCardIndex_();
  10629.       if (nextCardIndex == currentIndex) {
  10630.         this.setAttribute('aria-label', '');  // No next card.
  10631.         return;
  10632.       }
  10633.  
  10634.       var currentDot = dots[currentIndex];
  10635.       var nextDot = dots[nextCardIndex];
  10636.       if (!currentDot || !nextDot) {
  10637.         this.setAttribute('aria-label', '');  // Dots not initialised yet.
  10638.         return;
  10639.       }
  10640.  
  10641.       var currentPageTitle = currentDot.displayTitle;
  10642.       var nextPageTitle = nextDot.displayTitle;
  10643.       var msgName = (currentPageTitle == nextPageTitle) ?
  10644.           'page_switcher_same_title' : 'page_switcher_change_title';
  10645.       var ariaLabel = loadTimeData.getStringF(msgName, nextPageTitle);
  10646.       this.setAttribute('aria-label', ariaLabel);
  10647.     },
  10648.  
  10649.     shouldAcceptDrag: function(e) {
  10650.       // Only allow page switching when a drop could happen on the page being
  10651.       // switched to.
  10652.       var nextPage = ntp.getCardSlider().getCardAtIndex(this.nextCardIndex_());
  10653.       return nextPage.shouldAcceptDrag(e);
  10654.     },
  10655.  
  10656.     doDragEnter: function(e) {
  10657.       this.scheduleDelayedSwitch_(e);
  10658.       this.doDragOver(e);
  10659.     },
  10660.  
  10661.     doDragLeave: function(e) {
  10662.       this.cancelDelayedSwitch_();
  10663.     },
  10664.  
  10665.     doDragOver: function(e) {
  10666.       e.preventDefault();
  10667.       var targetPage = ntp.getCardSlider().currentCardValue;
  10668.       if (targetPage.shouldAcceptDrag(e))
  10669.         targetPage.setDropEffect(e.dataTransfer);
  10670.     },
  10671.  
  10672.     doDrop: function(e) {
  10673.       e.stopPropagation();
  10674.       this.cancelDelayedSwitch_();
  10675.  
  10676.       var tile = ntp.getCurrentlyDraggingTile();
  10677.       if (!tile)
  10678.         return;
  10679.  
  10680.       var sourcePage = tile.tilePage;
  10681.       var targetPage = ntp.getCardSlider().currentCardValue;
  10682.       if (targetPage == sourcePage || !targetPage.shouldAcceptDrag(e))
  10683.         return;
  10684.  
  10685.       targetPage.appendDraggingTile();
  10686.     },
  10687.  
  10688.     /**
  10689.      * Starts a timer to activate the switcher. The timer repeats until
  10690.      * cancelled by cancelDelayedSwitch_.
  10691.      * @private
  10692.      */
  10693.     scheduleDelayedSwitch_: function(e) {
  10694.       // Stop switching when the next page can't be dropped onto.
  10695.       var nextPage = ntp.getCardSlider().getCardAtIndex(this.nextCardIndex_());
  10696.       if (!nextPage.shouldAcceptDrag(e))
  10697.         return;
  10698.  
  10699.       var self = this;
  10700.       function navPageClearTimeout() {
  10701.         self.activate_();
  10702.         self.dragNavTimeout_ = null;
  10703.         self.scheduleDelayedSwitch_(e);
  10704.       }
  10705.       this.dragNavTimeout_ = window.setTimeout(navPageClearTimeout, 500);
  10706.     },
  10707.  
  10708.     /**
  10709.      * Cancels the timer that activates the switcher while dragging.
  10710.      * @private
  10711.      */
  10712.     cancelDelayedSwitch_: function() {
  10713.       if (this.dragNavTimeout_) {
  10714.         window.clearTimeout(this.dragNavTimeout_);
  10715.         this.dragNavTimeout_ = null;
  10716.       }
  10717.     },
  10718.  
  10719.   };
  10720.  
  10721.   return {
  10722.     initializePageSwitcher: PageSwitcher.template.decorate
  10723.   };
  10724. });
  10725. </script>
  10726.  
  10727. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  10728. // Use of this source code is governed by a BSD-style license that can be
  10729. // found in the LICENSE file.
  10730.  
  10731. /**
  10732.  * @fileoverview Nav dot
  10733.  * This is the class for the navigation controls that appear along the bottom
  10734.  * of the NTP.
  10735.  */
  10736.  
  10737. cr.define('ntp', function() {
  10738.   'use strict';
  10739.  
  10740.   /**
  10741.    * Creates a new navigation dot.
  10742.    * @param {TilePage} page The associated TilePage.
  10743.    * @param {string} title The title of the navigation dot.
  10744.    * @param {bool} titleIsEditable If true, the title can be changed.
  10745.    * @param {bool} animate If true, animates into existence.
  10746.    * @constructor
  10747.    * @extends {HTMLLIElement}
  10748.    */
  10749.   function NavDot(page, title, titleIsEditable, animate) {
  10750.     var dot = cr.doc.createElement('li');
  10751.     dot.__proto__ = NavDot.prototype;
  10752.     dot.initialize(page, title, titleIsEditable, animate);
  10753.  
  10754.     return dot;
  10755.   }
  10756.  
  10757.   NavDot.prototype = {
  10758.     __proto__: HTMLLIElement.prototype,
  10759.  
  10760.     initialize: function(page, title, titleIsEditable, animate) {
  10761.       this.className = 'dot';
  10762.       this.setAttribute('role', 'button');
  10763.  
  10764.       this.page_ = page;
  10765.  
  10766.       var selectionBar = this.ownerDocument.createElement('div');
  10767.       selectionBar.className = 'selection-bar';
  10768.       this.appendChild(selectionBar);
  10769.  
  10770.       // TODO(estade): should there be some limit to the number of characters?
  10771.       this.input_ = this.ownerDocument.createElement('input');
  10772.       this.input_.setAttribute('spellcheck', false);
  10773.       this.input_.value = title;
  10774.       // Take the input out of the tab-traversal focus order.
  10775.       this.input_.disabled = true;
  10776.       this.appendChild(this.input_);
  10777.  
  10778.       this.displayTitle = title;
  10779.       this.titleIsEditable_ = titleIsEditable;
  10780.  
  10781.       this.addEventListener('keydown', this.onKeyDown_);
  10782.       this.addEventListener('click', this.onClick_);
  10783.       this.addEventListener('dblclick', this.onDoubleClick_);
  10784.       this.dragWrapper_ = new cr.ui.DragWrapper(this, this);
  10785.       this.addEventListener('webkitTransitionEnd', this.onTransitionEnd_);
  10786.  
  10787.       this.input_.addEventListener('blur', this.onInputBlur_.bind(this));
  10788.       this.input_.addEventListener('mousedown',
  10789.                                    this.onInputMouseDown_.bind(this));
  10790.       this.input_.addEventListener('keydown', this.onInputKeyDown_.bind(this));
  10791.  
  10792.       if (animate) {
  10793.         this.classList.add('small');
  10794.         var self = this;
  10795.         window.setTimeout(function() {
  10796.           self.classList.remove('small');
  10797.         }, 0);
  10798.       }
  10799.     },
  10800.  
  10801.     /**
  10802.      * @return {TilePage} The associated TilePage.
  10803.      */
  10804.     get page() {
  10805.       return this.page_;
  10806.     },
  10807.  
  10808.     /**
  10809.      * Sets/gets the display title.
  10810.      * @type {String} title The display name for this nav dot.
  10811.      */
  10812.     get displayTitle() {
  10813.       return this.title;
  10814.     },
  10815.     set displayTitle(title) {
  10816.       this.title = this.input_.value = title;
  10817.     },
  10818.  
  10819.     /**
  10820.      * Removes the dot from the page. If |opt_animate| is truthy, we first
  10821.      * transition the element to 0 width.
  10822.      * @param {boolean=} opt_animate Whether to animate the removal or not.
  10823.      */
  10824.     remove: function(opt_animate) {
  10825.       if (opt_animate)
  10826.         this.classList.add('small');
  10827.       else
  10828.         this.parentNode.removeChild(this);
  10829.     },
  10830.  
  10831.     /**
  10832.      * Navigates the card slider to the page for this dot.
  10833.      */
  10834.     switchToPage: function() {
  10835.       ntp.getCardSlider().selectCardByValue(this.page_, true);
  10836.     },
  10837.  
  10838.     /**
  10839.      * Handler for keydown event on the dot.
  10840.      * @param {Event} e The KeyboardEvent.
  10841.      */
  10842.     onKeyDown_: function(e) {
  10843.       if (e.keyIdentifier == 'Enter') {
  10844.         this.onClick_(e);
  10845.         e.stopPropagation();
  10846.       }
  10847.     },
  10848.  
  10849.     /**
  10850.      * Clicking causes the associated page to show.
  10851.      * @param {Event} e The click event.
  10852.      * @private
  10853.      */
  10854.     onClick_: function(e) {
  10855.       this.switchToPage();
  10856.       // The explicit focus call is necessary because of overriding the default
  10857.       // handling in onInputMouseDown_.
  10858.       if (this.ownerDocument.activeElement != this.input_)
  10859.         this.focus();
  10860.  
  10861.       chrome.send('introMessageDismissed');
  10862.       e.stopPropagation();
  10863.     },
  10864.  
  10865.     /**
  10866.      * Double clicks allow the user to edit the page title.
  10867.      * @param {Event} e The click event.
  10868.      * @private
  10869.      */
  10870.     onDoubleClick_: function(e) {
  10871.       if (this.titleIsEditable_) {
  10872.         this.input_.disabled = false;
  10873.         this.input_.focus();
  10874.         this.input_.select();
  10875.       }
  10876.     },
  10877.  
  10878.     /**
  10879.      * Prevent mouse down on the input from selecting it.
  10880.      * @param {Event} e The click event.
  10881.      * @private
  10882.      */
  10883.     onInputMouseDown_: function(e) {
  10884.       if (this.ownerDocument.activeElement != this.input_)
  10885.         e.preventDefault();
  10886.     },
  10887.  
  10888.     /**
  10889.      * Handle keypresses on the input.
  10890.      * @param {Event} e The click event.
  10891.      * @private
  10892.      */
  10893.     onInputKeyDown_: function(e) {
  10894.       switch (e.keyIdentifier) {
  10895.         case 'U+001B':  // Escape cancels edits.
  10896.           this.input_.value = this.displayTitle;
  10897.         case 'Enter':  // Fall through.
  10898.           this.input_.blur();
  10899.           break;
  10900.       }
  10901.     },
  10902.  
  10903.     /**
  10904.      * When the input blurs, commit the edited changes.
  10905.      * @param {Event} e The blur event.
  10906.      * @private
  10907.      */
  10908.     onInputBlur_: function(e) {
  10909.       window.getSelection().removeAllRanges();
  10910.       this.displayTitle = this.input_.value;
  10911.       ntp.saveAppPageName(this.page_, this.displayTitle);
  10912.       this.input_.disabled = true;
  10913.     },
  10914.  
  10915.     shouldAcceptDrag: function(e) {
  10916.       return this.page_.shouldAcceptDrag(e);
  10917.     },
  10918.  
  10919.     /**
  10920.      * A drag has entered the navigation dot. If the user hovers long enough,
  10921.      * we will navigate to the relevant page.
  10922.      * @param {Event} e The MouseOver event for the drag.
  10923.      * @private
  10924.      */
  10925.     doDragEnter: function(e) {
  10926.       var self = this;
  10927.       function navPageClearTimeout() {
  10928.         self.switchToPage();
  10929.         self.dragNavTimeout = null;
  10930.       }
  10931.       this.dragNavTimeout = window.setTimeout(navPageClearTimeout, 500);
  10932.  
  10933.       this.doDragOver(e);
  10934.     },
  10935.  
  10936.     /**
  10937.      * A dragged element has moved over the navigation dot. Show the correct
  10938.      * indicator and prevent default handling so the <input> won't act as a drag
  10939.      * target.
  10940.      * @param {Event} e The MouseOver event for the drag.
  10941.      * @private
  10942.      */
  10943.     doDragOver: function(e) {
  10944.       e.preventDefault();
  10945.  
  10946.       if (!this.dragWrapper_.isCurrentDragTarget)
  10947.         ntp.setCurrentDropEffect(e.dataTransfer, 'none');
  10948.       else
  10949.         this.page_.setDropEffect(e.dataTransfer);
  10950.     },
  10951.  
  10952.     /**
  10953.      * A dragged element has been dropped on the navigation dot. Tell the page
  10954.      * to append it.
  10955.      * @param {Event} e The MouseOver event for the drag.
  10956.      * @private
  10957.      */
  10958.     doDrop: function(e) {
  10959.       e.stopPropagation();
  10960.       var tile = ntp.getCurrentlyDraggingTile();
  10961.       if (tile && tile.tilePage != this.page_)
  10962.         this.page_.appendDraggingTile();
  10963.       // TODO(estade): handle non-tile drags.
  10964.  
  10965.       this.cancelDelayedSwitch_();
  10966.     },
  10967.  
  10968.     /**
  10969.      * The drag has left the navigation dot.
  10970.      * @param {Event} e The MouseOver event for the drag.
  10971.      * @private
  10972.      */
  10973.     doDragLeave: function(e) {
  10974.       this.cancelDelayedSwitch_();
  10975.     },
  10976.  
  10977.     /**
  10978.      * Cancels the timer for page switching.
  10979.      * @private
  10980.      */
  10981.     cancelDelayedSwitch_: function() {
  10982.       if (this.dragNavTimeout) {
  10983.         window.clearTimeout(this.dragNavTimeout);
  10984.         this.dragNavTimeout = null;
  10985.       }
  10986.     },
  10987.  
  10988.     /**
  10989.      * A transition has ended.
  10990.      * @param {Event} e The transition end event.
  10991.      * @private
  10992.      */
  10993.     onTransitionEnd_: function(e) {
  10994.       if (e.propertyName === 'max-width' && this.classList.contains('small'))
  10995.         this.parentNode.removeChild(this);
  10996.     },
  10997.   };
  10998.  
  10999.   return {
  11000.     NavDot: NavDot,
  11001.   };
  11002. });
  11003. </script>
  11004. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  11005. // Use of this source code is governed by a BSD-style license that can be
  11006. // found in the LICENSE file.
  11007.  
  11008. /**
  11009.  * @fileoverview New tab page
  11010.  * This is the main code for the new tab page used by touch-enabled Chrome
  11011.  * browsers.  For now this is still a prototype.
  11012.  */
  11013.  
  11014. // Use an anonymous function to enable strict mode just for this file (which
  11015. // will be concatenated with other files when embedded in Chrome
  11016. cr.define('ntp', function() {
  11017.   'use strict';
  11018.  
  11019.   /**
  11020.    * NewTabView instance.
  11021.    * @type {!Object|undefined}
  11022.    */
  11023.   var newTabView;
  11024.  
  11025.   /**
  11026.    * The 'notification-container' element.
  11027.    * @type {!Element|undefined}
  11028.    */
  11029.   var notificationContainer;
  11030.  
  11031.   /**
  11032.    * If non-null, an info bubble for showing messages to the user. It points at
  11033.    * the Most Visited label, and is used to draw more attention to the
  11034.    * navigation dot UI.
  11035.    * @type {!Element|undefined}
  11036.    */
  11037.   var promoBubble;
  11038.  
  11039.   /**
  11040.    * If non-null, an bubble confirming that the user has signed into sync. It
  11041.    * points at the login status at the top of the page.
  11042.    * @type {!Element|undefined}
  11043.    */
  11044.   var loginBubble;
  11045.  
  11046.   /**
  11047.    * true if |loginBubble| should be shown.
  11048.    * @type {Boolean}
  11049.    */
  11050.   var shouldShowLoginBubble = false;
  11051.  
  11052.   /**
  11053.    * The 'other-sessions-menu-button' element.
  11054.    * @type {!Element|undefined}
  11055.    */
  11056.   var otherSessionsButton;
  11057.  
  11058.   /**
  11059.    * The time when all sections are ready.
  11060.    * @type {number|undefined}
  11061.    * @private
  11062.    */
  11063.   var startTime;
  11064.  
  11065.   /**
  11066.    * The time in milliseconds for most transitions.  This should match what's
  11067.    * in new_tab.css.  Unfortunately there's no better way to try to time
  11068.    * something to occur until after a transition has completed.
  11069.    * @type {number}
  11070.    * @const
  11071.    */
  11072.   var DEFAULT_TRANSITION_TIME = 500;
  11073.  
  11074.   /**
  11075.    * See description for these values in ntp_stats.h.
  11076.    * @enum {number}
  11077.    */
  11078.   var NtpFollowAction = {
  11079.     CLICKED_TILE: 11,
  11080.     CLICKED_OTHER_NTP_PANE: 12,
  11081.     OTHER: 13
  11082.   };
  11083.  
  11084.   /**
  11085.    * Creates a NewTabView object. NewTabView extends PageListView with
  11086.    * new tab UI specific logics.
  11087.    * @constructor
  11088.    * @extends {PageListView}
  11089.    */
  11090.   function NewTabView() {
  11091.     var pageSwitcherStart = null;
  11092.     var pageSwitcherEnd = null;
  11093.     if (loadTimeData.getValue('showApps')) {
  11094.       pageSwitcherStart = getRequiredElement('page-switcher-start');
  11095.       pageSwitcherEnd = getRequiredElement('page-switcher-end');
  11096.     }
  11097.     this.initialize(getRequiredElement('page-list'),
  11098.                     getRequiredElement('dot-list'),
  11099.                     getRequiredElement('card-slider-frame'),
  11100.                     getRequiredElement('trash'),
  11101.                     pageSwitcherStart, pageSwitcherEnd);
  11102.   }
  11103.  
  11104.   NewTabView.prototype = {
  11105.     __proto__: ntp.PageListView.prototype,
  11106.  
  11107.     /** @override */
  11108.     appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
  11109.       ntp.PageListView.prototype.appendTilePage.apply(this, arguments);
  11110.  
  11111.       if (promoBubble)
  11112.         window.setTimeout(promoBubble.reposition.bind(promoBubble), 0);
  11113.     }
  11114.   };
  11115.  
  11116.   /**
  11117.    * Invoked at startup once the DOM is available to initialize the app.
  11118.    */
  11119.   function onLoad() {
  11120.     sectionsToWaitFor = loadTimeData.getBoolean('showApps') ? 2 : 1;
  11121.     if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled'))
  11122.       sectionsToWaitFor++;
  11123.     measureNavDots();
  11124.  
  11125.     // Load the current theme colors.
  11126.     themeChanged();
  11127.  
  11128.     newTabView = new NewTabView();
  11129.  
  11130.     notificationContainer = getRequiredElement('notification-container');
  11131.     notificationContainer.addEventListener(
  11132.         'webkitTransitionEnd', onNotificationTransitionEnd);
  11133.  
  11134.     cr.ui.decorate($('recently-closed-menu-button'), ntp.RecentMenuButton);
  11135.     chrome.send('getRecentlyClosedTabs');
  11136.  
  11137.     if (loadTimeData.getBoolean('showOtherSessionsMenu')) {
  11138.       otherSessionsButton = getRequiredElement('other-sessions-menu-button');
  11139.       cr.ui.decorate(otherSessionsButton, ntp.OtherSessionsMenuButton);
  11140.       otherSessionsButton.initialize(loadTimeData.getBoolean('isUserSignedIn'));
  11141.     }
  11142.  
  11143.     var mostVisited = new ntp.MostVisitedPage();
  11144.     // Move the footer into the most visited page if we are in "bare minimum"
  11145.     // mode.
  11146.     if (document.body.classList.contains('bare-minimum'))
  11147.       mostVisited.appendFooter(getRequiredElement('footer'));
  11148.     newTabView.appendTilePage(mostVisited,
  11149.                               loadTimeData.getString('mostvisited'),
  11150.                               false);
  11151.     chrome.send('getMostVisited');
  11152.  
  11153.     if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled')) {
  11154.       var suggestions_script = document.createElement('script');
  11155.       suggestions_script.src = 'suggestions_page.js';
  11156.       suggestions_script.onload = function() {
  11157.          newTabView.appendTilePage(new ntp.SuggestionsPage(),
  11158.                                    loadTimeData.getString('suggestions'),
  11159.                                    false,
  11160.                                    (newTabView.appsPages.length > 0) ?
  11161.                                        newTabView.appsPages[0] : null);
  11162.          chrome.send('getSuggestions');
  11163.          cr.dispatchSimpleEvent(document, 'sectionready', true, true);
  11164.       };
  11165.       document.querySelector('head').appendChild(suggestions_script);
  11166.     }
  11167.  
  11168.     var webStoreLink = loadTimeData.getString('webStoreLink');
  11169.     var url = appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher');
  11170.     $('chrome-web-store-link').href = url;
  11171.     $('chrome-web-store-link').addEventListener('click',
  11172.         onChromeWebStoreButtonClick);
  11173.  
  11174.     if (loadTimeData.getString('login_status_message')) {
  11175.       loginBubble = new cr.ui.Bubble;
  11176.       loginBubble.anchorNode = $('login-container');
  11177.       loginBubble.arrowLocation = cr.ui.ArrowLocation.TOP_END;
  11178.       loginBubble.bubbleAlignment =
  11179.           cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE;
  11180.       loginBubble.deactivateToDismissDelay = 2000;
  11181.       loginBubble.closeButtonVisible = false;
  11182.  
  11183.       $('login-status-advanced').onclick = function() {
  11184.         chrome.send('showAdvancedLoginUI');
  11185.       };
  11186.       $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble);
  11187.  
  11188.       var bubbleContent = $('login-status-bubble-contents');
  11189.       loginBubble.content = bubbleContent;
  11190.  
  11191.       // The anchor node won't be updated until updateLogin is called so don't
  11192.       // show the bubble yet.
  11193.       shouldShowLoginBubble = true;
  11194.     }
  11195.  
  11196.     if (loadTimeData.valueExists('bubblePromoText')) {
  11197.       promoBubble = new cr.ui.Bubble;
  11198.       promoBubble.anchorNode = getRequiredElement('promo-bubble-anchor');
  11199.       promoBubble.arrowLocation = cr.ui.ArrowLocation.BOTTOM_START;
  11200.       promoBubble.bubbleAlignment = cr.ui.BubbleAlignment.ENTIRELY_VISIBLE;
  11201.       promoBubble.deactivateToDismissDelay = 2000;
  11202.       promoBubble.content = parseHtmlSubset(
  11203.           loadTimeData.getString('bubblePromoText'), ['BR']);
  11204.  
  11205.       var bubbleLink = promoBubble.querySelector('a');
  11206.       if (bubbleLink) {
  11207.         bubbleLink.addEventListener('click', function(e) {
  11208.           chrome.send('bubblePromoLinkClicked');
  11209.         });
  11210.       }
  11211.  
  11212.       promoBubble.handleCloseEvent = function() {
  11213.         promoBubble.hide();
  11214.         chrome.send('bubblePromoClosed');
  11215.       };
  11216.       promoBubble.show();
  11217.       chrome.send('bubblePromoViewed');
  11218.     }
  11219.  
  11220.     var loginContainer = getRequiredElement('login-container');
  11221.     loginContainer.addEventListener('click', showSyncLoginUI);
  11222.     chrome.send('initializeSyncLogin');
  11223.  
  11224.     doWhenAllSectionsReady(function() {
  11225.       // Tell the slider about the pages.
  11226.       newTabView.updateSliderCards();
  11227.       // Mark the current page.
  11228.       newTabView.cardSlider.currentCardValue.navigationDot.classList.add(
  11229.           'selected');
  11230.  
  11231.       if (loadTimeData.valueExists('notificationPromoText')) {
  11232.         var promoText = loadTimeData.getString('notificationPromoText');
  11233.         var tags = ['IMG'];
  11234.         var attrs = {
  11235.           src: function(node, value) {
  11236.             return node.tagName == 'IMG' &&
  11237.                    /^data\:image\/(?:png|gif|jpe?g)/.test(value);
  11238.           },
  11239.         };
  11240.  
  11241.         var promo = parseHtmlSubset(promoText, tags, attrs);
  11242.         var promoLink = promo.querySelector('a');
  11243.         if (promoLink) {
  11244.           promoLink.addEventListener('click', function(e) {
  11245.             chrome.send('notificationPromoLinkClicked');
  11246.           });
  11247.         }
  11248.  
  11249.         showNotification(promo, [], function() {
  11250.           chrome.send('notificationPromoClosed');
  11251.         }, 60000);
  11252.         chrome.send('notificationPromoViewed');
  11253.       }
  11254.  
  11255.       cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true);
  11256.       document.documentElement.classList.remove('starting-up');
  11257.  
  11258.       startTime = Date.now();
  11259.     });
  11260.  
  11261.     preventDefaultOnPoundLinkClicks();  // From shared/js/util.js.
  11262.   }
  11263.  
  11264.   /**
  11265.    * Launches the chrome web store app with the chrome-ntp-launcher
  11266.    * source.
  11267.    * @param {Event} e The click event.
  11268.    */
  11269.   function onChromeWebStoreButtonClick(e) {
  11270.     chrome.send('recordAppLaunchByURL',
  11271.                 [encodeURIComponent(this.href),
  11272.                  ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]);
  11273.   }
  11274.  
  11275.   /*
  11276.    * The number of sections to wait on.
  11277.    * @type {number}
  11278.    */
  11279.   var sectionsToWaitFor = -1;
  11280.  
  11281.   /**
  11282.    * Queued callbacks which lie in wait for all sections to be ready.
  11283.    * @type {array}
  11284.    */
  11285.   var readyCallbacks = [];
  11286.  
  11287.   /**
  11288.    * Fired as each section of pages becomes ready.
  11289.    * @param {Event} e Each page's synthetic DOM event.
  11290.    */
  11291.   document.addEventListener('sectionready', function(e) {
  11292.     if (--sectionsToWaitFor <= 0) {
  11293.       while (readyCallbacks.length) {
  11294.         readyCallbacks.shift()();
  11295.       }
  11296.     }
  11297.   });
  11298.  
  11299.   /**
  11300.    * This is used to simulate a fire-once event (i.e. $(document).ready() in
  11301.    * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback
  11302.    * is fired right away. If all pages are not ready yet, the function is queued
  11303.    * for later execution.
  11304.    * @param {function} callback The work to be done when ready.
  11305.    */
  11306.   function doWhenAllSectionsReady(callback) {
  11307.     assert(typeof callback == 'function');
  11308.     if (sectionsToWaitFor > 0)
  11309.       readyCallbacks.push(callback);
  11310.     else
  11311.       window.setTimeout(callback, 0);  // Do soon after, but asynchronously.
  11312.   }
  11313.  
  11314.   /**
  11315.    * Fills in an invisible div with the 'Most Visited' string so that
  11316.    * its length may be measured and the nav dots sized accordingly.
  11317.    */
  11318.   function measureNavDots() {
  11319.     var measuringDiv = $('fontMeasuringDiv');
  11320.     measuringDiv.textContent = loadTimeData.getString('mostvisited');
  11321.     // The 4 is for border and padding.
  11322.     var pxWidth = Math.max(measuringDiv.clientWidth * 1.15 + 4, 80);
  11323.  
  11324.     var styleElement = document.createElement('style');
  11325.     styleElement.type = 'text/css';
  11326.     // max-width is used because if we run out of space, the nav dots will be
  11327.     // shrunk.
  11328.     styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }';
  11329.     document.querySelector('head').appendChild(styleElement);
  11330.   }
  11331.  
  11332.   function themeChanged(opt_hasAttribution) {
  11333.     $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now();
  11334.  
  11335.     if (typeof opt_hasAttribution != 'undefined') {
  11336.       document.documentElement.setAttribute('hasattribution',
  11337.                                             opt_hasAttribution);
  11338.     }
  11339.  
  11340.     updateAttribution();
  11341.   }
  11342.  
  11343.   function setBookmarkBarAttached(attached) {
  11344.     document.documentElement.setAttribute('bookmarkbarattached', attached);
  11345.   }
  11346.  
  11347.   /**
  11348.    * Attributes the attribution image at the bottom left.
  11349.    */
  11350.   function updateAttribution() {
  11351.     var attribution = $('attribution');
  11352.     if (document.documentElement.getAttribute('hasattribution') == 'true') {
  11353.       $('attribution-img').src =
  11354.           'chrome://theme/IDR_THEME_NTP_ATTRIBUTION?' + Date.now();
  11355.       attribution.hidden = false;
  11356.     } else {
  11357.       attribution.hidden = true;
  11358.     }
  11359.   }
  11360.  
  11361.   /**
  11362.    * Timeout ID.
  11363.    * @type {number}
  11364.    */
  11365.   var notificationTimeout = 0;
  11366.  
  11367.   /**
  11368.    * Shows the notification bubble.
  11369.    * @param {string|Node} message The notification message or node to use as
  11370.    *     message.
  11371.    * @param {Array.<{text: string, action: function()}>} links An array of
  11372.    *     records describing the links in the notification. Each record should
  11373.    *     have a 'text' attribute (the display string) and an 'action' attribute
  11374.    *     (a function to run when the link is activated).
  11375.    * @param {Function} opt_closeHandler The callback invoked if the user
  11376.    *     manually dismisses the notification.
  11377.    */
  11378.   function showNotification(message, links, opt_closeHandler, opt_timeout) {
  11379.     window.clearTimeout(notificationTimeout);
  11380.  
  11381.     var span = document.querySelector('#notification > span');
  11382.     if (typeof message == 'string') {
  11383.       span.textContent = message;
  11384.     } else {
  11385.       span.textContent = '';  // Remove all children.
  11386.       span.appendChild(message);
  11387.     }
  11388.  
  11389.     var linksBin = $('notificationLinks');
  11390.     linksBin.textContent = '';
  11391.     for (var i = 0; i < links.length; i++) {
  11392.       var link = linksBin.ownerDocument.createElement('div');
  11393.       link.textContent = links[i].text;
  11394.       link.action = links[i].action;
  11395.       link.onclick = function() {
  11396.         this.action();
  11397.         hideNotification();
  11398.       };
  11399.       link.setAttribute('role', 'button');
  11400.       link.setAttribute('tabindex', 0);
  11401.       link.className = 'link-button';
  11402.       linksBin.appendChild(link);
  11403.     }
  11404.  
  11405.     function closeFunc(e) {
  11406.       if (opt_closeHandler)
  11407.         opt_closeHandler();
  11408.       hideNotification();
  11409.     }
  11410.  
  11411.     document.querySelector('#notification button').onclick = closeFunc;
  11412.     document.addEventListener('dragstart', closeFunc);
  11413.  
  11414.     notificationContainer.hidden = false;
  11415.     showNotificationOnCurrentPage();
  11416.  
  11417.     newTabView.cardSlider.frame.addEventListener(
  11418.         'cardSlider:card_change_ended', onCardChangeEnded);
  11419.  
  11420.     var timeout = opt_timeout || 10000;
  11421.     notificationTimeout = window.setTimeout(hideNotification, timeout);
  11422.   }
  11423.  
  11424.   /**
  11425.    * Hide the notification bubble.
  11426.    */
  11427.   function hideNotification() {
  11428.     notificationContainer.classList.add('inactive');
  11429.  
  11430.     newTabView.cardSlider.frame.removeEventListener(
  11431.         'cardSlider:card_change_ended', onCardChangeEnded);
  11432.   }
  11433.  
  11434.   /**
  11435.    * Happens when 1 or more consecutive card changes end.
  11436.    * @param {Event} e The cardSlider:card_change_ended event.
  11437.    */
  11438.   function onCardChangeEnded(e) {
  11439.     // If we ended on the same page as we started, ignore.
  11440.     if (newTabView.cardSlider.currentCardValue.notification)
  11441.       return;
  11442.  
  11443.     // Hide the notification the old page.
  11444.     notificationContainer.classList.add('card-changed');
  11445.  
  11446.     showNotificationOnCurrentPage();
  11447.   }
  11448.  
  11449.   /**
  11450.    * Move and show the notification on the current page.
  11451.    */
  11452.   function showNotificationOnCurrentPage() {
  11453.     var page = newTabView.cardSlider.currentCardValue;
  11454.     doWhenAllSectionsReady(function() {
  11455.       if (page != newTabView.cardSlider.currentCardValue)
  11456.         return;
  11457.  
  11458.       // NOTE: This moves the notification to inside of the current page.
  11459.       page.notification = notificationContainer;
  11460.  
  11461.       // Reveal the notification and instruct it to hide itself if ignored.
  11462.       notificationContainer.classList.remove('inactive');
  11463.  
  11464.       // Gives the browser time to apply this rule before we remove it (causing
  11465.       // a transition).
  11466.       window.setTimeout(function() {
  11467.         notificationContainer.classList.remove('card-changed');
  11468.       }, 0);
  11469.     });
  11470.   }
  11471.  
  11472.   /**
  11473.    * When done fading out, set hidden to true so the notification can't be
  11474.    * tabbed to or clicked.
  11475.    * @param {Event} e The webkitTransitionEnd event.
  11476.    */
  11477.   function onNotificationTransitionEnd(e) {
  11478.     if (notificationContainer.classList.contains('inactive'))
  11479.       notificationContainer.hidden = true;
  11480.   }
  11481.  
  11482.   function setRecentlyClosedTabs(dataItems) {
  11483.     $('recently-closed-menu-button').dataItems = dataItems;
  11484.   }
  11485.  
  11486.   function setMostVisitedPages(data, hasBlacklistedUrls) {
  11487.     newTabView.mostVisitedPage.data = data;
  11488.     cr.dispatchSimpleEvent(document, 'sectionready', true, true);
  11489.   }
  11490.  
  11491.   function setSuggestionsPages(data, hasBlacklistedUrls) {
  11492.     newTabView.suggestionsPage.data = data;
  11493.   }
  11494.  
  11495.   /**
  11496.    * Set the dominant color for a node. This will be called in response to
  11497.    * getFaviconDominantColor. The node represented by |id| better have a setter
  11498.    * for stripeColor.
  11499.    * @param {string} id The ID of a node.
  11500.    * @param {string} color The color represented as a CSS string.
  11501.    */
  11502.   function setFaviconDominantColor(id, color) {
  11503.     var node = $(id);
  11504.     if (node)
  11505.       node.stripeColor = color;
  11506.   }
  11507.  
  11508.   /**
  11509.    * Updates the text displayed in the login container. If there is no text then
  11510.    * the login container is hidden.
  11511.    * @param {string} loginHeader The first line of text.
  11512.    * @param {string} loginSubHeader The second line of text.
  11513.    * @param {string} iconURL The url for the login status icon. If this is null
  11514.         then the login status icon is hidden.
  11515.    * @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
  11516.    */
  11517.   function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) {
  11518.     if (loginHeader || loginSubHeader) {
  11519.       $('login-container').hidden = false;
  11520.       $('login-status-header').innerHTML = loginHeader;
  11521.       $('login-status-sub-header').innerHTML = loginSubHeader;
  11522.       $('card-slider-frame').classList.add('showing-login-area');
  11523.  
  11524.       if (iconURL) {
  11525.         $('login-status-header-container').style.backgroundImage = url(iconURL);
  11526.         $('login-status-header-container').classList.add('login-status-icon');
  11527.       } else {
  11528.         $('login-status-header-container').style.backgroundImage = 'none';
  11529.         $('login-status-header-container').classList.remove(
  11530.             'login-status-icon');
  11531.       }
  11532.     } else {
  11533.       $('login-container').hidden = true;
  11534.       $('card-slider-frame').classList.remove('showing-login-area');
  11535.     }
  11536.     if (shouldShowLoginBubble) {
  11537.       window.setTimeout(loginBubble.show.bind(loginBubble), 0);
  11538.       chrome.send('loginMessageSeen');
  11539.       shouldShowLoginBubble = false;
  11540.     } else if (loginBubble) {
  11541.       loginBubble.reposition();
  11542.     }
  11543.     if (otherSessionsButton)
  11544.       otherSessionsButton.updateSignInState(isUserSignedIn);
  11545.   }
  11546.  
  11547.   /**
  11548.    * Show the sync login UI.
  11549.    * @param {Event} e The click event.
  11550.    */
  11551.   function showSyncLoginUI(e) {
  11552.     var rect = e.currentTarget.getBoundingClientRect();
  11553.     chrome.send('showSyncLoginUI',
  11554.                 [rect.left, rect.top, rect.width, rect.height]);
  11555.   }
  11556.  
  11557.   /**
  11558.    * Logs the time to click for the specified item.
  11559.    * @param {string} item The item to log the time-to-click.
  11560.    */
  11561.   function logTimeToClick(item) {
  11562.     var timeToClick = Date.now() - startTime;
  11563.     chrome.send('logTimeToClick',
  11564.         ['NewTabPage.TimeToClick' + item, timeToClick]);
  11565.   }
  11566.  
  11567.   /**
  11568.    * Wrappers to forward the callback to corresponding PageListView member.
  11569.    */
  11570.   function appAdded() {
  11571.     return newTabView.appAdded.apply(newTabView, arguments);
  11572.   }
  11573.  
  11574.   function appMoved() {
  11575.     return newTabView.appMoved.apply(newTabView, arguments);
  11576.   }
  11577.  
  11578.   function appRemoved() {
  11579.     return newTabView.appRemoved.apply(newTabView, arguments);
  11580.   }
  11581.  
  11582.   function appsPrefChangeCallback() {
  11583.     return newTabView.appsPrefChangedCallback.apply(newTabView, arguments);
  11584.   }
  11585.  
  11586.   function appsReordered() {
  11587.     return newTabView.appsReordered.apply(newTabView, arguments);
  11588.   }
  11589.  
  11590.   function enterRearrangeMode() {
  11591.     return newTabView.enterRearrangeMode.apply(newTabView, arguments);
  11592.   }
  11593.  
  11594.   function setForeignSessions(sessionList, isTabSyncEnabled) {
  11595.     if (otherSessionsButton)
  11596.       otherSessionsButton.setForeignSessions(sessionList, isTabSyncEnabled);
  11597.   }
  11598.  
  11599.   function getAppsCallback() {
  11600.     return newTabView.getAppsCallback.apply(newTabView, arguments);
  11601.   }
  11602.  
  11603.   function getAppsPageIndex() {
  11604.     return newTabView.getAppsPageIndex.apply(newTabView, arguments);
  11605.   }
  11606.  
  11607.   function getCardSlider() {
  11608.     return newTabView.cardSlider;
  11609.   }
  11610.  
  11611.   function leaveRearrangeMode() {
  11612.     return newTabView.leaveRearrangeMode.apply(newTabView, arguments);
  11613.   }
  11614.  
  11615.   function saveAppPageName() {
  11616.     return newTabView.saveAppPageName.apply(newTabView, arguments);
  11617.   }
  11618.  
  11619.   function setAppToBeHighlighted(appId) {
  11620.     newTabView.highlightAppId = appId;
  11621.   }
  11622.  
  11623.   // Return an object with all the exports
  11624.   return {
  11625.     appAdded: appAdded,
  11626.     appMoved: appMoved,
  11627.     appRemoved: appRemoved,
  11628.     appsPrefChangeCallback: appsPrefChangeCallback,
  11629.     enterRearrangeMode: enterRearrangeMode,
  11630.     getAppsCallback: getAppsCallback,
  11631.     getAppsPageIndex: getAppsPageIndex,
  11632.     getCardSlider: getCardSlider,
  11633.     onLoad: onLoad,
  11634.     leaveRearrangeMode: leaveRearrangeMode,
  11635.     logTimeToClick: logTimeToClick,
  11636.     NtpFollowAction: NtpFollowAction,
  11637.     saveAppPageName: saveAppPageName,
  11638.     setAppToBeHighlighted: setAppToBeHighlighted,
  11639.     setBookmarkBarAttached: setBookmarkBarAttached,
  11640.     setForeignSessions: setForeignSessions,
  11641.     setMostVisitedPages: setMostVisitedPages,
  11642.     setSuggestionsPages: setSuggestionsPages,
  11643.     setRecentlyClosedTabs: setRecentlyClosedTabs,
  11644.     setFaviconDominantColor: setFaviconDominantColor,
  11645.     showNotification: showNotification,
  11646.     themeChanged: themeChanged,
  11647.     updateLogin: updateLogin
  11648.   };
  11649. });
  11650.  
  11651. document.addEventListener('DOMContentLoaded', ntp.onLoad);
  11652.  
  11653. var toCssPx = cr.ui.toCssPx;
  11654. </script>
  11655. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  11656. // Use of this source code is governed by a BSD-style license that can be
  11657. // found in the LICENSE file.
  11658.  
  11659. /**
  11660.  * @fileoverview The recently closed menu: button, model data, and menu.
  11661.  */
  11662.  
  11663. cr.define('ntp', function() {
  11664.   'use strict';
  11665.  
  11666.   /**
  11667.    * Returns the text used for a recently closed window.
  11668.    * @param {number} numTabs Number of tabs in the window.
  11669.    * @return {string} The text to use.
  11670.    */
  11671.   function formatTabsText(numTabs) {
  11672.     if (numTabs == 1)
  11673.       return loadTimeData.getString('closedwindowsingle');
  11674.     return loadTimeData.getStringF('closedwindowmultiple', numTabs);
  11675.   }
  11676.  
  11677.   var Menu = cr.ui.Menu;
  11678.   var MenuItem = cr.ui.MenuItem;
  11679.   var MenuButton = cr.ui.MenuButton;
  11680.   var RecentMenuButton = cr.ui.define('button');
  11681.  
  11682.   RecentMenuButton.prototype = {
  11683.     __proto__: MenuButton.prototype,
  11684.  
  11685.     decorate: function() {
  11686.       MenuButton.prototype.decorate.call(this);
  11687.       this.menu = new Menu;
  11688.       cr.ui.decorate(this.menu, Menu);
  11689.       this.menu.classList.add('footer-menu');
  11690.       document.body.appendChild(this.menu);
  11691.  
  11692.       this.needsRebuild_ = true;
  11693.       this.hidden = true;
  11694.       this.anchorType = cr.ui.AnchorType.ABOVE;
  11695.       this.invertLeftRight = true;
  11696.     },
  11697.  
  11698.     /**
  11699.      * Shows the menu, first rebuilding it if necessary.
  11700.      * TODO(estade): the right of the menu should align with the right of the
  11701.      * button.
  11702.      * @override
  11703.      */
  11704.     showMenu: function() {
  11705.       if (this.needsRebuild_) {
  11706.         this.menu.textContent = '';
  11707.         this.dataItems_.forEach(this.addItem_, this);
  11708.         this.needsRebuild_ = false;
  11709.       }
  11710.  
  11711.       MenuButton.prototype.showMenu.call(this);
  11712.     },
  11713.  
  11714.     /**
  11715.      * Sets the menu model data.
  11716.      * @param {Array} dataItems Array of objects that describe the apps.
  11717.      */
  11718.     set dataItems(dataItems) {
  11719.       this.dataItems_ = dataItems;
  11720.       this.needsRebuild_ = true;
  11721.       this.hidden = !dataItems.length;
  11722.     },
  11723.  
  11724.     /**
  11725.      * Adds an app to the menu.
  11726.      * @param {Object} data An object encapsulating all data about the app.
  11727.      * @private
  11728.      */
  11729.     addItem_: function(data) {
  11730.       var isWindow = data.type == 'window';
  11731.       var a = this.ownerDocument.createElement('a');
  11732.       a.className = 'footer-menu-item';
  11733.       if (isWindow) {
  11734.         a.href = '';
  11735.         a.classList.add('recent-window');
  11736.         a.textContent = formatTabsText(data.tabs.length);
  11737.         a.title = data.tabs.map(function(tab) { return tab.title; }).join('\n');
  11738.       } else {
  11739.         a.href = data.url;
  11740.         a.style.backgroundImage = url(getFaviconURL(data.url));
  11741.         a.textContent = data.title;
  11742.       }
  11743.  
  11744.       function onActivated(e) {
  11745.         ntp.logTimeToClick('RecentlyClosed');
  11746.         chrome.send('recordAppLaunchByURL',
  11747.                     [encodeURIComponent(data.url),
  11748.                      ntp.APP_LAUNCH.NTP_RECENTLY_CLOSED]);
  11749.         var index = Array.prototype.indexOf.call(a.parentNode.children, a);
  11750.         var orig = e.originalEvent;
  11751.         var params = [data.sessionId,
  11752.                       index,
  11753.                       orig.type == 'click' ? orig.button : 0,
  11754.                       orig.altKey,
  11755.                       orig.ctrlKey,
  11756.                       orig.metaKey,
  11757.                       orig.shiftKey];
  11758.         chrome.send('reopenTab', params);
  11759.  
  11760.         // We are likely deleted by this point!
  11761.         e.stopPropagation();
  11762.         e.preventDefault();
  11763.       }
  11764.       a.addEventListener('activate', onActivated);
  11765.  
  11766.       this.menu.appendChild(a);
  11767.       cr.ui.decorate(a, MenuItem);
  11768.     },
  11769.   };
  11770.  
  11771.   return {
  11772.     RecentMenuButton: RecentMenuButton,
  11773.   };
  11774. });
  11775. </script>
  11776. <script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  11777. // Use of this source code is governed by a BSD-style license that can be
  11778. // found in the LICENSE file.
  11779.  
  11780. /**
  11781.  * @fileoverview The menu that shows tabs from sessions on other devices.
  11782.  */
  11783.  
  11784. cr.define('ntp', function() {
  11785.   'use strict';
  11786.  
  11787.   /** @const */ var ContextMenuButton = cr.ui.ContextMenuButton;
  11788.   /** @const */ var Menu = cr.ui.Menu;
  11789.   /** @const */ var MenuItem = cr.ui.MenuItem;
  11790.   /** @const */ var MenuButton = cr.ui.MenuButton;
  11791.   /** @const */ var OtherSessionsMenuButton = cr.ui.define('button');
  11792.  
  11793.   // Histogram buckets for UMA tracking of menu usage.
  11794.   /** @const */ var HISTOGRAM_EVENT = {
  11795.       INITIALIZED: 0,
  11796.       SHOW_MENU: 1,
  11797.       LINK_CLICKED: 2,
  11798.       LINK_RIGHT_CLICKED: 3,
  11799.       SESSION_NAME_RIGHT_CLICKED: 4,
  11800.       SHOW_SESSION_MENU: 5,
  11801.       COLLAPSE_SESSION: 6,
  11802.       EXPAND_SESSION: 7,
  11803.       OPEN_ALL: 8
  11804.   };
  11805.   /** @const */ var HISTOGRAM_EVENT_LIMIT =
  11806.       HISTOGRAM_EVENT.OPEN_ALL + 1;
  11807.  
  11808.   /**
  11809.    * Record an event in the UMA histogram.
  11810.    * @param {Number} eventId The id of the event to be recorded.
  11811.    * @private
  11812.    */
  11813.   function recordUmaEvent_(eventId) {
  11814.     chrome.send('metricsHandler:recordInHistogram',
  11815.         ['NewTabPage.OtherSessionsMenu', eventId, HISTOGRAM_EVENT_LIMIT]);
  11816.   }
  11817.  
  11818.   OtherSessionsMenuButton.prototype = {
  11819.     __proto__: MenuButton.prototype,
  11820.  
  11821.     decorate: function() {
  11822.       MenuButton.prototype.decorate.call(this);
  11823.       this.menu = new Menu;
  11824.       cr.ui.decorate(this.menu, Menu);
  11825.       this.menu.classList.add('footer-menu');
  11826.       this.menu.addEventListener('contextmenu',
  11827.                                  this.onContextMenu_.bind(this), true);
  11828.       document.body.appendChild(this.menu);
  11829.  
  11830.       // Create the context menu that appears when the user right clicks
  11831.       // on a device name.
  11832.       this.deviceContextMenu_ = DeviceContextMenuController.getInstance().menu;
  11833.       document.body.appendChild(this.deviceContextMenu_);
  11834.  
  11835.       this.promoMessage_ = $('other-sessions-promo-template').cloneNode(true);
  11836.       this.promoMessage_.removeAttribute('id');  // Prevent a duplicate id.
  11837.  
  11838.       this.sessions_ = [];
  11839.       this.anchorType = cr.ui.AnchorType.ABOVE;
  11840.       this.invertLeftRight = true;
  11841.  
  11842.       // Initialize the images for the drop-down buttons that appear beside the
  11843.       // session names.
  11844.       MenuButton.createDropDownArrows();
  11845.  
  11846.       recordUmaEvent_(HISTOGRAM_EVENT.INITIALIZED);
  11847.     },
  11848.  
  11849.     /**
  11850.      * Initialize this element.
  11851.      * @param {boolean} signedIn Is the current user signed in?
  11852.      */
  11853.     initialize: function(signedIn) {
  11854.       this.updateSignInState(signedIn);
  11855.     },
  11856.  
  11857.     /**
  11858.      * Handle a context menu event for an object in the menu's DOM subtree.
  11859.      */
  11860.     onContextMenu_: function(e) {
  11861.       // Only record the action if it occurred in one of the menu items or
  11862.       // on one of the session headings.
  11863.       if (findAncestorByClass(e.target, 'footer-menu-item')) {
  11864.         recordUmaEvent_(HISTOGRAM_EVENT.LINK_RIGHT_CLICKED);
  11865.       } else {
  11866.         var heading = findAncestorByClass(e.target, 'session-heading');
  11867.         if (heading) {
  11868.           recordUmaEvent_(HISTOGRAM_EVENT.SESSION_NAME_RIGHT_CLICKED);
  11869.  
  11870.           // Let the context menu know which session it was invoked on,
  11871.           // since they all share the same instance of the menu.
  11872.           DeviceContextMenuController.getInstance().setSession(
  11873.               heading.sessionData_);
  11874.         }
  11875.       }
  11876.     },
  11877.  
  11878.     /**
  11879.      * Hides the menu.
  11880.      * @override
  11881.      */
  11882.     hideMenu: function() {
  11883.       // Don't hide if the device context menu is currently showing.
  11884.       if (this.deviceContextMenu_.hidden)
  11885.         MenuButton.prototype.hideMenu.call(this);
  11886.     },
  11887.  
  11888.     /**
  11889.      * Shows the menu, first rebuilding it if necessary.
  11890.      * TODO(estade): the right of the menu should align with the right of the
  11891.      * button.
  11892.      * @override
  11893.      */
  11894.     showMenu: function() {
  11895.       if (this.sessions_.length == 0)
  11896.         chrome.send('getForeignSessions');
  11897.       recordUmaEvent_(HISTOGRAM_EVENT.SHOW_MENU);
  11898.       MenuButton.prototype.showMenu.call(this);
  11899.  
  11900.       // Work around https://bugs.webkit.org/show_bug.cgi?id=85884.
  11901.       this.menu.scrollTop = 0;
  11902.     },
  11903.  
  11904.     /**
  11905.      * Reset the menu contents to the default state.
  11906.      * @private
  11907.      */
  11908.     resetMenuContents_: function() {
  11909.       this.menu.innerHTML = '';
  11910.       this.menu.appendChild(this.promoMessage_);
  11911.     },
  11912.  
  11913.     /**
  11914.      * Create a custom click handler for a link, so that clicking on a link
  11915.      * restores the session (including back stack) rather than just opening
  11916.      * the URL.
  11917.      */
  11918.     makeClickHandler_: function(sessionTag, windowId, tabId) {
  11919.       var self = this;
  11920.       return function(e) {
  11921.         recordUmaEvent_(HISTOGRAM_EVENT.LINK_CLICKED);
  11922.         chrome.send('openForeignSession', [sessionTag, windowId, tabId,
  11923.             e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
  11924.         e.preventDefault();
  11925.       };
  11926.     },
  11927.  
  11928.     /**
  11929.      * Add the UI for a foreign session to the menu.
  11930.      * @param {Object} session Object describing the foreign session.
  11931.      */
  11932.     addSession_: function(session) {
  11933.       var doc = this.ownerDocument;
  11934.  
  11935.       var section = doc.createElement('section');
  11936.       this.menu.appendChild(section);
  11937.  
  11938.       var heading = doc.createElement('h3');
  11939.       heading.className = 'session-heading';
  11940.       heading.textContent = session.name;
  11941.       heading.sessionData_ = session;
  11942.       section.appendChild(heading);
  11943.  
  11944.       var dropDownButton = new ContextMenuButton;
  11945.       dropDownButton.classList.add('drop-down');
  11946.       // Keep track of the drop down that triggered the menu, so we know
  11947.       // which element to apply the command to.
  11948.       function handleDropDownFocus(e) {
  11949.         DeviceContextMenuController.getInstance().setSession(session);
  11950.       }
  11951.       dropDownButton.addEventListener('mousedown', handleDropDownFocus);
  11952.       dropDownButton.addEventListener('focus', handleDropDownFocus);
  11953.       heading.appendChild(dropDownButton);
  11954.  
  11955.       var timeSpan = doc.createElement('span');
  11956.       timeSpan.className = 'details';
  11957.       timeSpan.textContent = session.modifiedTime;
  11958.       heading.appendChild(timeSpan);
  11959.  
  11960.       cr.ui.contextMenuHandler.setContextMenu(heading,
  11961.                                               this.deviceContextMenu_);
  11962.  
  11963.       if (!session.collapsed)
  11964.         section.appendChild(this.createSessionContents_(session));
  11965.     },
  11966.  
  11967.     /**
  11968.      * Create the DOM tree representing the tabs and windows in a session.
  11969.      * @param {Object} session The session model object.
  11970.      * @return {Element} A single div containing the list of tabs & windows.
  11971.      * @private
  11972.      */
  11973.     createSessionContents_: function(session) {
  11974.       var doc = this.ownerDocument;
  11975.       var contents = doc.createElement('div');
  11976.  
  11977.       for (var i = 0; i < session.windows.length; i++) {
  11978.         var window = session.windows[i];
  11979.  
  11980.         // Show a separator between multiple windows in the same session.
  11981.         if (i > 0)
  11982.           contents.appendChild(doc.createElement('hr'));
  11983.  
  11984.         for (var j = 0; j < window.tabs.length; j++) {
  11985.           var tab = window.tabs[j];
  11986.           var a = doc.createElement('a');
  11987.           a.className = 'footer-menu-item';
  11988.           a.textContent = tab.title;
  11989.           a.href = tab.url;
  11990.           a.style.backgroundImage = url('chrome://session-favicon/' + tab.url);
  11991.  
  11992.           var clickHandler = this.makeClickHandler_(
  11993.               session.tag, String(window.sessionId), String(tab.sessionId));
  11994.           a.addEventListener('click', clickHandler);
  11995.           contents.appendChild(a);
  11996.         }
  11997.       }
  11998.  
  11999.       return contents;
  12000.     },
  12001.  
  12002.     /**
  12003.      * Sets the menu model data. An empty list means that either there are no
  12004.      * foreign sessions, or tab sync is disabled for this profile.
  12005.      * |isTabSyncEnabled| makes it possible to distinguish between the cases.
  12006.      *
  12007.      * @param {Array} sessionList Array of objects describing the sessions
  12008.      *     from other devices.
  12009.      * @param {boolean} isTabSyncEnabled Is tab sync enabled for this profile?
  12010.      */
  12011.     setForeignSessions: function(sessionList, isTabSyncEnabled) {
  12012.       this.sessions_ = sessionList;
  12013.       this.resetMenuContents_();
  12014.       if (sessionList.length > 0) {
  12015.         // Rebuild the menu with the new data.
  12016.         for (var i = 0; i < sessionList.length; i++) {
  12017.           this.addSession_(sessionList[i]);
  12018.         }
  12019.       }
  12020.  
  12021.       // The menu button is shown iff tab sync is enabled.
  12022.       this.hidden = !isTabSyncEnabled;
  12023.     },
  12024.  
  12025.     /**
  12026.      * Called when this element is initialized, and from the new tab page when
  12027.      * the user's signed in state changes,
  12028.      * @param {boolean} signedIn Is the user currently signed in?
  12029.      */
  12030.     updateSignInState: function(signedIn) {
  12031.       if (signedIn)
  12032.         chrome.send('getForeignSessions');
  12033.       else
  12034.         this.hidden = true;
  12035.     },
  12036.   };
  12037.  
  12038.   /**
  12039.    * Controller for the context menu for device names in the list of sessions.
  12040.    * This class is designed to be used as a singleton.
  12041.    *
  12042.    * @constructor
  12043.    */
  12044.   function DeviceContextMenuController() {
  12045.     this.__proto__ = DeviceContextMenuController.prototype;
  12046.     this.initialize();
  12047.   }
  12048.   cr.addSingletonGetter(DeviceContextMenuController);
  12049.  
  12050.   DeviceContextMenuController.prototype = {
  12051.  
  12052.     initialize: function() {
  12053.       var menu = new cr.ui.Menu;
  12054.       cr.ui.decorate(menu, cr.ui.Menu);
  12055.       menu.classList.add('device-context-menu');
  12056.       menu.classList.add('footer-menu-context-menu');
  12057.       this.menu = menu;
  12058.       this.collapseItem_ = this.appendMenuItem_('collapseSessionMenuItemText');
  12059.       this.collapseItem_.addEventListener('activate',
  12060.                                           this.onCollapseOrExpand_.bind(this));
  12061.       this.expandItem_ = this.appendMenuItem_('expandSessionMenuItemText');
  12062.       this.expandItem_.addEventListener('activate',
  12063.                                         this.onCollapseOrExpand_.bind(this));
  12064.       this.openAllItem_ = this.appendMenuItem_('restoreSessionMenuItemText');
  12065.       this.openAllItem_.addEventListener('activate',
  12066.                                          this.onOpenAll_.bind(this));
  12067.     },
  12068.  
  12069.     /**
  12070.      * Appends a menu item to |this.menu|.
  12071.      * @param {String} textId The ID for the localized string that acts as
  12072.      *     the item's label.
  12073.      */
  12074.     appendMenuItem_: function(textId) {
  12075.       var button = cr.doc.createElement('button');
  12076.       this.menu.appendChild(button);
  12077.       cr.ui.decorate(button, cr.ui.MenuItem);
  12078.       button.textContent = loadTimeData.getString(textId);
  12079.       return button;
  12080.     },
  12081.  
  12082.     /**
  12083.      * Handler for the 'Collapse' and 'Expand' menu items.
  12084.      * @param {Event} e The activation event.
  12085.      * @private
  12086.      */
  12087.     onCollapseOrExpand_: function(e) {
  12088.       this.session_.collapsed = !this.session_.collapsed;
  12089.       this.updateMenuItems_();
  12090.       chrome.send('setForeignSessionCollapsed',
  12091.                   [this.session_.tag, this.session_.collapsed]);
  12092.       chrome.send('getForeignSessions');  // Refresh the list.
  12093.  
  12094.       var eventId = this.session_.collapsed ?
  12095.           HISTOGRAM_EVENT.COLLAPSE_SESSION : HISTOGRAM_EVENT.EXPAND_SESSION;
  12096.       recordUmaEvent_(eventId);
  12097.     },
  12098.  
  12099.     /**
  12100.      * Handler for the 'Open all' menu item.
  12101.      * @param {Event} e The activation event.
  12102.      * @private
  12103.      */
  12104.     onOpenAll_: function(e) {
  12105.       chrome.send('openForeignSession', [this.session_.tag]);
  12106.       recordUmaEvent_(HISTOGRAM_EVENT.OPEN_ALL);
  12107.     },
  12108.  
  12109.     /**
  12110.      * Set the session data for the session the context menu was invoked on.
  12111.      * This should never be called when the menu is visible.
  12112.      * @param {Object} session The model object for the session.
  12113.      */
  12114.     setSession: function(session) {
  12115.       this.session_ = session;
  12116.       this.updateMenuItems_();
  12117.     },
  12118.  
  12119.     /**
  12120.      * Set the visibility of the Expand/Collapse menu items based on the state
  12121.      * of the session that this menu is currently associated with.
  12122.      * @private
  12123.      */
  12124.     updateMenuItems_: function() {
  12125.       this.collapseItem_.hidden = this.session_.collapsed;
  12126.       this.expandItem_.hidden = !this.session_.collapsed;
  12127.     }
  12128.   };
  12129.  
  12130.   return {
  12131.     OtherSessionsMenuButton: OtherSessionsMenuButton,
  12132.   };
  12133. });
  12134. </script>
  12135. </head>
  12136.  
  12137. <body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
  12138.   <div id="notification-container" class="inactive" hidden>
  12139.     <div id="notification">
  12140.       <span></span>
  12141.       <div id="notificationLinks"></div>
  12142.       <button class="close-button custom-appearance" class="custom-appearance">
  12143.       </button>
  12144.     </div>
  12145.   </div>
  12146.  
  12147.   <div id="card-slider-frame">
  12148.     <button id="page-switcher-start" class="page-switcher custom-appearance"
  12149.         tabindex="2" hidden>ΓÇ╣
  12150.     </button>
  12151.     <div id="page-list"></div>
  12152.     <button id="page-switcher-end" class="page-switcher custom-appearance"
  12153.         tabindex="2" hidden>ΓÇ║
  12154.     </button>
  12155.     <div id="attribution">
  12156.       <span i18n-content="attributionintro"></span>
  12157.       <img id="attribution-img">
  12158.     </div>
  12159.   </div>
  12160.  
  12161.   <div id="footer">
  12162.     <div id="footer-border"></div>
  12163.     <div id="footer-content">
  12164.       <div id="logo-img">
  12165.         <img alt="" src="chrome://theme/IDR_PRODUCT_LOGO">
  12166.         <div id="promo-bubble-anchor"></div>
  12167.       </div>
  12168.  
  12169.       <ul id="dot-list">
  12170.       </ul>
  12171.  
  12172.       <div id="footer-menu-container" class="menu-container">
  12173.         <button id="other-sessions-menu-button"
  12174.             class="footer-menu-button custom-appearance" hidden>
  12175.           <span i18n-content="otherSessions"></span>
  12176.           <div class="disclosure-triangle"></div>
  12177.         </button>
  12178.         <button id="recently-closed-menu-button"
  12179.             class="footer-menu-button custom-appearance">
  12180.           <span i18n-content="recentlyclosed"></span>
  12181.           <div class="disclosure-triangle"></div>
  12182.         </button>
  12183.         <div id="vertical-separator"></div>
  12184.       </div>
  12185.  
  12186.       <a id="chrome-web-store-link">
  12187.         <span id="chrome-web-store-title" i18n-content="webStoreTitleShort">
  12188.         </span>
  12189.       </a>
  12190.  
  12191.       <div id="trash" class="trash">
  12192.         <span class="lid"></span>
  12193.         <span class="can"></span>
  12194.         <span class="trash-text" i18n-content="appuninstall"></span>
  12195.       </div>
  12196.     </div>
  12197.   </div>
  12198.  
  12199.   <button id="login-container" class="custom-appearance" hidden>
  12200.     <div id="login-status-header-container" class="login-status-row">
  12201.       <div id="login-status-header"></div>
  12202.     </div>
  12203.     <div id="login-status-sub-header"></div>
  12204.   </button>
  12205. </body>
  12206.  
  12207. <!-- A div to hold all the templates, and in the darkness bind them. -->
  12208. <div hidden>
  12209.  
  12210. <!-- Login status bubble -->
  12211. <div id="login-status-bubble-contents">
  12212.   <div id="login-status-message-container">
  12213.     <span i18n-content="login_status_message"></span>
  12214.     <a id="login-status-learn-more" i18n-content="learn_more"
  12215.         i18n-values="href:login_status_url" target="_blank"></a>
  12216.   </div>
  12217.   <div class="login-status-row">
  12218.     <div id="login-status-advanced-container">
  12219.       <a id="login-status-advanced"
  12220.           i18n-content="login_status_advanced" href="#"></a>
  12221.     </div>
  12222.     <button id="login-status-dismiss" i18n-content="login_status_dismiss">
  12223.     </button>
  12224.   </div>
  12225. </div>
  12226.  
  12227. <!-- App Contents w/ Large Icon -->
  12228. <div id="app-large-icon-template" class="app-contents">
  12229.   <div class="app-img-container" aria-hidden="true">
  12230.     <img class="invisible" alt="">
  12231.   </div>
  12232.   <span class="title"></span>
  12233. </div>
  12234.  
  12235. <!-- App Contents w/ Small Icon -->
  12236. <div id="app-small-icon-template" class="app-contents">
  12237.   <div class="app-icon-div" aria-hidden="true">
  12238.     <div class="app-img-container">
  12239.       <img class="invisible" alt="">
  12240.     </div>
  12241.     <div class="color-stripe"></div>
  12242.   </div>
  12243.   <span class="title"></span>
  12244. </div>
  12245.  
  12246. <!-- Message shown in the other sessions menu when the user is signed in but
  12247.      there is no session data (e.g. they have tab sync turned off). -->
  12248. <div id="other-sessions-promo-template" class="other-sessions-promo-message">
  12249.   <span i18n-content="otherSessionsEmpty"></span>
  12250.   <p>
  12251.     <a i18n-values="href:otherSessionsLearnMoreUrl" i18n-content="learnMore">
  12252.     </a>
  12253.   </p>
  12254. </div>
  12255.  
  12256. </div>
  12257.  
  12258. <!-- This is used to measure text in the current locale. It is not visible. -->
  12259. <div id="fontMeasuringDiv"></div>
  12260.  
  12261. </html>
  12262.