You are on page 1of 98

<!DOCTYPE html> <!

-- saved from url=(0016)chrome://newtab/ --> <html i18n-values=" dir:textdirection; bookmarkbarattached:bookmarkbarattached; hasattribution:hasattribution; anim:anim; syncispresent:syncispresent; customlogo:customlogo" install-animation-enabled="true" dir="ltr" bookmarkbara ttached="false" hasattribution="false" anim="true" syncispresent="true" customlo go="false" enable-section-animations="false"><head><meta http-equiv="Content-Typ e" content="text/html; charset=UTF-8"> <meta charset="utf-8"> <title i18n-content="title">New Tab</title> <script> // Logging info for benchmarking purposes. var log = []; function logEvent(name, shouldLogTime) { if (shouldLogTime) { chrome.send('logEventTime', [name]); } log.push([name, Date.now()]); } logEvent('Tab.NewTabScriptStart', true); var global = this; /** * Registers a callback function so that if the backend calls it too early it * will get delayed until DOMContentLoaded is fired. * @param {string} name The name of the global function that the backend calls. */ function registerCallback(name) { var f = function(var_args) { var args = Array.prototype.slice.call(arguments); // If we still have the temporary function we delay until the dom is ready. if (global[name] == f) { logEvent(name + ' is not yet ready. Waiting for DOMContentLoaded'); document.addEventListener('DOMContentLoaded', function() { logEvent('Calling the new ' + name); global[name].apply(null, args); }); } }; global[name] = f; } chrome.send('getMostVisited'); chrome.send('getRecentlyClosedTabs'); chrome.send('getApps'); registerCallback('mostVisitedPages'); registerCallback('recentlyClosedTabs'); registerCallback('syncMessageChanged'); registerCallback('getAppsCallback'); registerCallback('setShownSections'); </script> <script>var templateData = {"anim":"true","appoptions":"Options","apps":"Apps"," appsettings":"Settings","appuninstall":"Uninstall","attributionintro":"Theme cre

ated by","bookmarkbarattached":"false","close":"Close","closedwindowmultiple":"$ 1 Tabs","closedwindowsingle":"1 Tab","closefirstrunnotification":"Close","custom logo":"false","downloads":"Downloads","firstrunnotification":"Over time, the are a below will show your eight most visited sites.","fontfamily":"Arial","fontsize ":"84%","hasattribution":"false","help":"Help","helpurl":"http://www.google.com/ support/chrome/?hl=en-US","history":"History","mostvisited":"Most visited","page displaytooltip":"Change page layout","pinthumbnailtooltip":"Keep on this page"," recentlyclosed":"Recently closed","removethumbnailtooltip":"Don't show on this p age","restorethumbnails":"Restore all removed thumbnails","showhidelisttooltip": "List view","showhidethumbnailtooltip":"Thumbnail view","shown_sections":1,"sync ispresent":"true","textdirection":"ltr","thumbnailremovednotification":"Thumbnai l removed.","tips":"Tips","title":"New Tab","undothumbnailremove":"Undo","unpint humbnailtooltip":"Don't keep on this page","web_store_title":"Get More Apps","we b_store_url":"https://chrome.google.com/extensions?hl=en-US"};</script> <style>html { /* This is needed because of chrome://theme/css/new_tab.css */ height: 100%; } body { margin: 0; height: 100%; overflow: auto; -webkit-user-select: none; cursor: default; } html[mode=app-launcher] { height: auto; } #main { -webkit-box-sizing: border-box; -webkit-transition: width .15s; margin: 0 auto; min-height: 100%; } body.loading #main { /* We start out hidden to prevent glitchiness as the app and most visited data flows in. */ visibility: hidden; } #main, .section, .maxiview { width: 920px; } html[dir=rtl] #main { background-position-x: 100%; } html[mode=app-launcher] #main { min-height: 50px; } html[anim=false] *, .no-anim, .no-anim *,

.loading * { -webkit-transition: none !important; -webkit-animation: none !important; } :link, :visited, .link { cursor: pointer; text-decoration: underline; color: hsla(213, 90%, 24%, 0.33333); -webkit-appearance: none; border: 0; background: none; } .link-color { color: hsl(213, 90%, 24%); text-decoration: none; } .hide { opacity: 0 !important; visibility: hidden !important; pointer-events: none; } /* Notification */ #notification { position: relative; background-color: hsl(52, 100%, 80%); border: 1px solid rgb(211, 211, 211); border-radius: 6px; padding: 7px 15px; white-space: nowrap; display: table; /* Set the height and margin so that the element does not use any vertical space */ height: 16px; margin: -44px auto 12px auto; font-weight: bold; opacity: 0; pointer-events: none; -webkit-transition: opacity .15s; z-index: 1; color: black; } #notification.first-run { padding: 5px 13px; /* subtract the border witdh */ border: 2px solid hsl(213, 55%, 75%); background-color: hsl(213, 63%, 93%); -webkit-box-shadow: 2px 2px 3px hsla(0, 0%, 0%, .3); font-weight: normal; } #notification > * { display: table-cell; max-width: 500px;

overflow: hidden; text-overflow: ellipsis; } #notification.show { opacity: 1; pointer-events: all; -webkit-transition: opacity 1s; } #notification .link { color: rgba(0, 102, 204, 0.3); -webkit-padding-start: 20px; } #notification .link-color { color: rgb(0, 102, 204); } #notification > span > .blacklist-title { display: inline-block; max-width: 30ex; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .item { background: no-repeat 0% 50%; padding: 2px; padding-left: 18px; background-size: 16px 16px; background-color: hsla(213, 63%, 93%, 0); display: block; line-height: 20px; -webkit-box-sizing: border-box; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 100%; } .item:visited, .item:link { color: hsl(213, 90%, 24%); } html[dir=rtl] .item { background-position-x: 100%; padding-right: 18px; padding-left: 2px; text-align: right; } .window { overflow: visible; /* We use visible so that the menu can be a child and shown on :hover. To get this to work we have to set visibility to visible which unfortunately breaks the ellipsis for t he window items */ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC

AYAAAAf8/9hAAAA10lEQVQ4y82OSwsBURiGv78of0Ehk2vGwsKlTLllFlMzyJgmaqKJ0fgBVrZWFjb2i o2N3WvOiAhNHRuLp7fO6Xn6CAAZ7gLGcoEhw2Xr+Gt4y/78N9fxMRjLG8ylgWMjrJ24GMxtkDax0F2ds d7sHnRmW4TUYyDa1AIpY9OX9ofLCywSdIEyMkGy3nuT7zxf9QlZ74OaqvI1EETDc0mS29wBSW6BynWJO 1Cp10DFaok7wNw/CBSKBe6A6LmUEzPcgVw+C0qlBe5A0nMpIUS5A8yleCyCX7gCQoVZHBMa2iYAAAAAS UVORK5CYII="); } .window-menu { position: absolute; display: none; border: 1px solid #999; -webkit-box-shadow: 2px 2px 3px hsla(0, 0%, 0%, .3); color: black; background-color: white; left: 0; white-space: nowrap; z-index: 2; padding: 2px; cursor: default; border-radius: 4px; } /* Made to look like a tooltip using vista/win7 look and feel. TODO(arv): Replace with -webkit-appearance once issue 17371 is fixed */ #window-tooltip { color: #555; pointer-events: none; border: 1px solid rgb(118, 118, 118); border-radius: 3px; padding: 0 3px; background: -webkit-gradient(linear, left top, left bottom, from(white), to(rgb(228, 229, 240))); width: auto; max-width: 300px; } .hbox { display: -webkit-box; -webkit-box-orient: horizontal; } #sync-status > div { border-radius: 6px; padding: 5px 0; margin: 10px 0 20px; white-space: nowrap; overflow-x: hidden; } #sync-status > div > * { display: inline-block; max-width: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 100%; margin: 0 10px; }

.notification.hidden { opacity: 0; pointer-events: none; } /** * Unfortunately, there seems to be a bug in WebKit where this div doesn't * immediately get layout. It still doesn't have it in 'load', but gains it * sometime after. * * We detect this in the JS by looking for offsetWidth > 0, and when it occurs, * remove the 'nolayout' class. */ #attribution.nolayout { position: static; visibility: hidden; } #attribution { position: fixed; right: 5px; bottom: 5px; text-align: end; } html[dir=rtl] #attribution { left: 5px; right: auto; } #attribution.obscured { visibility: hidden; } html[hasattribution=false] #attribution > div { display: none; } .sync-button { font-size: inherit; padding: 0; margin: 0; -webkit-appearance: none; border: 0; background: none; cursor: pointer; text-decoration: underline; font-family: inherit; } .section { position: fixed; font-size: 92%; } html[anim=true][enable-section-animations=true] .section { -webkit-transition: top .15s; }

.section.disabled, #closed-sections-bar .disabled { display: none !important; } .section > h2 { font-family: Helvetica, Arial, sans-serif; font-size: 133%; font-weight: normal; margin: 0; position: relative; } .section:not([noexpand]) > h2 { cursor: pointer; } .section > h2 > .disclosure { position: absolute; left: -15px; margin-top: 50%; top: -5px; } html[dir=rtl] .section > h2 > .disclosure { left: auto; right: -15px; -webkit-transform: rotate(180deg); } html[anim=true] .section > h2 > .disclosure { -webkit-transition: -webkit-transform .15s; } .section:not(.hidden) > h2 > .disclosure { -webkit-transform: rotate(90deg); } .section > h2 .back { position: absolute; left: 0; top: 0.56em; width: 100%; height: 1.5em; z-index: 1; } .section > h2 span { -webkit-padding-end: 0.30em; position: relative; z-index: 2; } .section-close-button { -webkit-appearance: none; -webkit-transition: opacity .15s; background-color: transparent; background-image: url(chrome://theme/IDR_CLOSE_BAR); background-position: center center; background-repeat no-repeat;

border: 0; height: 21px; margin-top: -10px; position: absolute; right: -21px; top: 50%; width: 21px; opacity: 0; z-index: 3; } html[dir=rtl] .section-close-button { left: -21px; right: auto; } .section > h2:hover .section-close-button, .section-close-button:hover { opacity: 1; } .section-close-button:hover { background-image: url(chrome://theme/IDR_CLOSE_BAR_H); } #closed-sections-bar { position: fixed; bottom: 5px; } #closed-sections-bar > button { -webkit-appearance: none; background: none; border: 0; cursor: pointer; font: inherit; margin: 0; margin-right: 1.5em; padding: 0; /* Note: The font here should end up the same as .section > h2. A different percentage is needed because the parent element here has a different size. */ font-family: Helvetica, Arial, sans-serif; font-size: 122%; font-weight: normal; } #closed-sections-bar > button > img { -webkit-transform: rotate(90deg); position: relative; top: -2px; margin-left: 1px; } .maxiview { padding: 5px 0 30px; position: absolute; -webkit-mask-attachment: fixed; opacity: 1; }

.maxiview.hiding { opacity: 0; } .maxiview.hidden { display: none; } html[anim=true] .maxiview { -webkit-transition: opacity .15s; } .section > .miniview { display: none; margin: 10px 0 30px; white-space: nowrap; overflow-x: hidden; } .section.hidden > * { display: none; } .section.hidden > h2, .section.hidden > .miniview { display: block; } .section.hidden > h2 { margin-right: 0; } .miniview > span { display: inline-block; max-width: 114px; /* Selected so that we can fit 5 items in EN-US */ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 100%; margin: 0 10px; } .miniview > span:first-child { margin-left: 0; } .miniview > span:last-child { margin-right: 0; } /* small */ @media (max-width: 940px) { #main, .section, .maxiview { width: 692px; }

#notification > * { max-width: 300px; } #notification > span > .blacklist-title { max-width: 15ex; } } /* Ensure we have at least 10px horizontal marging. */ @media (max-width: 712px) { #main { margin-left: 10px; margin-right: 10px; } } </style> <style>/* Most Visited */ #most-visited-maxiview { position: relative; height: 366px; -webkit-user-select: none; } .thumbnail-container { position: absolute; color: hsl(213, 90%, 24%); text-decoration: none; -webkit-transition: left .15s, right .15s, top .15s; text-decoration: none; } .thumbnail-container:focus { outline: none; } .thumbnail, .thumbnail-container > .title { width: 207px; /* natural size is 196 */ height: 129px; /* 136 */ -webkit-transition: width .15s, height .15s; } .thumbnail-container > .title { line-height: 16px; height: 16px; margin: 0; margin-top: 4px; font-size: 100%; font-weight: normal; padding: 0 3px; opacity: 1; -webkit-transition: opacity .15s, width .15s; color: black; } .thumbnail-wrapper { display: block;

background-size: 212px 132px; background: no-repeat 4px 4px; background-color: white; border-radius: 5px; -webkit-transition: background-size .15s; position: relative; } .filler * { visibility: hidden; } .filler { pointer-events: none; } .filler .thumbnail-wrapper { visibility: visible; border: 3px solid hsl(213, 60%, 92%); } .filler .thumbnail { visibility: inherit; border: 1px solid white; padding: 0; background-color: hsl(213, 60%, 92%); } .edit-bar { display: -webkit-box; -webkit-box-orient: horizontal; -webkit-box-align: stretch; padding: 3px; padding-bottom: 0; height: 17px; /* 23 - 2 * 3 */ cursor: move; font-size: 100%; line-height: 17px; background: hsl(213, 54%, 95%); border-top-left-radius: 4px; border-top-right-radius: 4px; position: relative; margin-top: 21px; margin-bottom: -21px; -webkit-transition: margin .15s, background .15s; } .edit-bar > * { display: block; position: relative; } .thumbnail-container:focus .edit-bar, .thumbnail-container:hover .edit-bar { margin-top: 0; margin-bottom: 0; -webkit-transition-delay: .5s, 0s; /* We need background-color as well to get the fade out animation correct */ background-color: hsl(213, 66%, 57%);

background-image: -webkit-gradient(linear, left top, left bottom, from(hsl(213, 87%, 67%)), to(hsl(213, 66%, 57%))); } .edit-bar > .spacer { -webkit-box-flex: 1; } .edit-bar > .pin, .edit-bar > .remove { width: 16px; height: 16px; cursor: pointer; background-image: no-repeat 50% 50%; } .edit-bar > .pin { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC AYAAAAf8/9hAAAAi0lEQVQ4y6WTbQqAIAyGR4eRPs5h/to1umYX2J1swhuYlG0lPCLi+zDFERENyqhEh Y1EZEqWJmx6ByNLqUw5ZxcY6TS1B4IiBgHfCUp4hyB4BXVYepIngTwQvFeQDq+CniRYBXeSzXqFGsGjb ljzF8FZNtcSq6CFUdFF8Psr/2mmmdCSi7I62nlFhg6BYt2lXCEqGgAAAABJRU5ErkJggg=="); } .edit-bar > .pin:hover { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC AYAAAAf8/9hAAAAkUlEQVQ4y52TwQnAIAxF/6EDuUkppXdH8Sq6gFN2hNTQVGxRqwa+iPAeiRAAWGLWG BsTOmOFYRYbPxIRRiIiZuH5mBAkNhQEiu76E4SSgOFTBGpUkMPUktQEtVKjI7TqV9CSqF5BSWJ6R8hD8 qlG7npG8LStc0mv4BstHb0EHvPl0zJNwMzskJU8YtzAOjthcAHXovXMr/dmSgAAAABJRU5ErkJggg==" ); } .edit-bar > .pin:active { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC AYAAAAf8/9hAAAAtElEQVQ4y6WTQQrCMBBFZ+GhcgsRcZltj+AFXEl7gdl5gdKFELyYi6zifJlCWtM40 YFHQ8p/mYSEiGgn7IVReBi5agZZOggsdI2wZmn6Idw5587yHUhbWvz03l+SlEHAHwKEY4xPCDBuEuThu bYkRUHaqJKkuoVUqa+CmiTvpCooSUIId9MWchDEoSKMMTPfmgVz2wjnEpNgDcLoaC0Y/r3K78eEiRZ09 SPpkzwJvU5a6DVDL0iIFWtK1DHEAAAAAElFTkSuQmCC"); } .pinned .edit-bar > .pin { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC AYAAAAf8/9hAAAAoUlEQVQ4y6WTQQrEIAxFs5j7lTLM0u1cZFbiXCC7uUPBm3XhyuZDhLaTWm0DH0R4z yiGiOghGSRewo3xyoClUTffnWFl6XsBLgH7f7pz7pOlGrvYCgCnlGYIsO4SrOFSJ5KtIB9URWJfIVfqV FCTGJ3YAksSY5yarrAOQDwqYKyZ+dctKG0DNiR1wT6A0dFecPsr3xmmJ+lIviShY5yDMrQA7LABeK+JN KgAAAAASUVORK5CYII="); } .pinned .edit-bar > .pin:hover { background-image: url(ntp_pin_on_h.png'); } .pinned .edit-bar > .pin:active { background-image: url(ntp_pin_on_p.png'); } .edit-bar > .remove { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC AYAAAAf8/9hAAAAeUlEQVQ4y7WTywmAQAxEH1bjpw53QdiGtkZ7Gi8RRBc1BgO5JMyDfAagA3pgBsrLn E3TAQxW9EYxLRlAkist8k76CigtwCKpNkTVeo+AKmk9QS61O8BZ0AI+Ao6QtTXS74DQCOElhs/ofqTwK

0fMNGKWnIDksHMyDRvjd9VHQSi/pgAAAABJRU5ErkJggg=="); } .edit-bar > .remove:hover { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC AYAAAAf8/9hAAAAdklEQVQ4y72TUQrAIAiG/4fdb4yxty4UdYFO6xRklE+msOB7kfyyUgA4mJOpzHBSN UdycWlwdw3NRUd8dQRPn6tYBURUmMHAILHiEchGMpIv5hFYySL0CmbJUs1vgtQV0o+Y+8ZII6VbOTNMN 3QkH6ZtjHPTHLxiHvw7LRQlVgAAAABJRU5ErkJggg=="); } .edit-bar > .remove:active { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC AYAAAAf8/9hAAAAi0lEQVQ4y2NgYGBgAWJ/IF4LxDuIxJ1QPSC9DIFAPBOI00jEM6F6GdaToTnNwsKiD Ej3MECdRK4BMzEM+P//fwwQY3gJJAaSI8YAkML/yIZgE8NpALoGbJoJGoBmyH9sXqKtARR5geJApDgay UlIPZQmZXBmAgmQgqG2BzNAs2QoEHdDBYnB3VA9DACUmgUVtMbe9gAAAABJRU5ErkJggg=="); } .thumbnail-container > .title > div { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: no-repeat 0 50%; background-size: 16px; padding-left: 20px; /* we cannot use padding start here because even if we set the direction we always want the icon on the same side */ padding-right: 0; } html[dir=rtl] .thumbnail-container > .title > div { background-position-x: 100%; padding-left: 0; padding-right: 20px; text-align: right; } .thumbnail { border: 3px solid hsl(213, 63%, 93%); padding: 1px; border-radius: 5px; display: block; -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, 0); -webkit-transition: width .15s, height .15s, border-color .15s, border-top-left-radius .15s, border-top-right-radius .15s, -webkit-box-shadow .15s; } .edit-mode-border { border-radius: 4px; /* when dragged over we move this */ position: relative; -webkit-transition: top .15s, left .15s; } .thumbnail-container:focus .thumbnail, .thumbnail-container:hover .thumbnail { border-color: hsl(213, 66%, 57%); -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, 0); -webkit-border-top-left-radius: 0;

-webkit-border-top-right-radius: 0; background-image: -webkit-gradient(linear, left top, left bottom, from(hsla(0, 0%, 0%, 0)), color-stop(0.85, hsla(0, 0%, 47%, 0)), to(hsla(0, 0%, 47%, 0.2)) ); /* delay border radius transition as much as the edit bar slide delay */ -webkit-transition-delay: 0, 0, 0, .5s, .5s, 0; } .thumbnail-container:focus > .edit-mode-border, .thumbnail-container:hover > .edit-mode-border { background-color: hsl(213, 66%, 57%); -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, .5); } .dragging, .dragging * { -webkit-transition: none !important; } .dragging > .title { opacity: 0; } @-webkit-keyframes 'fade-in' { 0% { opacity: 0; } 100% { opacity: 1; } } .fade-in { -webkit-animation: 'fade-in' .15s; } @media (max-width: 940px) { #most-visited-maxiview { height: 294px; } .thumbnail, .thumbnail-container > .title { width: 150px; height: 93px; } .thumbnail-container > .title { height: auto; } .thumbnail-wrapper { background-size: 150px 93px; } }

#most-visited-settings { position: absolute; top: 1px; right: 0; border: 0; cursor: pointer; font-size: 70%; margin: 0; padding: 0; text-decoration: underline; visibility: hidden; -webkit-padding-start: 3px; z-index: 3; } html[dir=rtl] #most-visited-settings { left: 0; right: auto; } #most-visited:not(.hidden) #most-visited-settings.has-blacklist { visibility: visible; } </style> <style>/* Apps */ .app, .app[new=installed] { -webkit-box-sizing: border-box; -webkit-perspective: 400; border-radius: 10px; color: black; display: inline-block; margin: 5px 3px; position: relative; height: 136px; width: 124px; /* 920 / 7 - margin * 2 */ } .app a { border-radius: 10px; bottom: 0; left: 0; position: absolute; right: 0; top: 0; } .app a { -webkit-transition: background-color .5s; background: rgba(255, 255, 255, 0) /* transparent white */ no-repeat center 10px; background-size: 96px 96px; font-family: Helvetica, Arial; font-size: 107%; overflow: hidden; padding: 111px 10px 10px; /* 10 + 96 + 5 */ text-align: center; text-decoration: none;

text-overflow: ellipsis; white-space: nowrap; } .app .app-settings { background-color: transparent; background-position: center center; border: 0; height: 14px; padding: 0; position: absolute; right: 5px; top: 5px; width: 14px; } .app > .app-settings { -webkit-transition: opacity .3s; -webkit-transition-delay: 0; background-image: url(chrome://theme/IDR_BALLOON_WRENCH); opacity: 0; } .app > .app-settings:hover { -webkit-transition: none; background-image: url(chrome://theme/IDR_BALLOON_WRENCH_H); } .app:hover > .app-settings, .app > .app-settings:focus { -webkit-transition-delay: .5s; opacity: .9; } @-webkit-keyframes bounce { 0% { -webkit-transform: scale(0, 0); } 60% { -webkit-transform: scale(1.2, 1.2); } 100% { -webkit-transform: scale(1, 1); } } html[install-animation-enabled=true] .app[new=new] { opacity: 0; } html[install-animation-enabled=true] .app[new=installed] { -webkit-animation: bounce .5s ease-in-out; -webkit-transition: opacity .5s; } a[app-id=web-store-entry] { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGIAAABiC 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"); } menu > button.default { font-weight: bold; } </style> <style>menu { display: none; position: fixed; border: 1px solid rgba(0, 0, 0, .50); -webkit-box-shadow: 0px 2px 4px rgba(0, 0, 0, .50); color: black; background: -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#eee));

left: 0; white-space: nowrap; padding: 8px 0; margin: 0; cursor: default; border-radius: 3px; z-index: 3; } menu > * { -webkit-box-sizing: border-box; display: block; margin: 0; width: 100%; text-align: start; } menu > :not(hr) { -webkit-appearance: none; background: transparent; font: inherit; border: 0; line-height: 18px; padding: 0 19px; overflow: hidden; text-overflow: ellipsis; } menu > hr { background: -webkit-gradient(linear, 0 0, 96% 0, from(rgba(0, 0, 0, .10)), to(rgba(0, 0, 0, .02))); border: 0; height: 1px; margin: 8px 0; } menu > [disabled] { color: rgba(0, 0, 0, .3); } menu > [hidden] { display: none; } menu > :not(hr)[selected] { background-color: #dce5fa; } menu > :not(hr)[selected]:active { background-color: #426dc9; color: #fff; } </style> <script> /** * Bitmask for the different UI sections. * This matches the Section enum in ../dom_ui/shown_sections_handler.h * @enum {number} */

var Section = { THUMB: 1 << 0, APPS: 1 << 6 }; // These are used to hide sections and are part of the shownSections bitmask, // but are not sections themselves. var MINIMIZED_THUMB = 1 << (0 + 16); var MINIMIZED_RECENT = 1 << (2 + 16); var MINIMIZED_APPS = 1 << (6 + 16); var shownSections = templateData['shown_sections']; // Until themes can clear the cache, force-reload the theme stylesheet. document.write('<link id="themecss" rel="stylesheet" ' + 'href="chrome://theme/css/newtab.css?' + Date.now() + '">'); function useSmallGrid() { return window.innerWidth <= 940; } function isRtl() { return templateData['textdirection'] == 'rtl'; } // Parse any name value pairs passed through the URL hash. var hashParams = (function() { var result = {}; if (location.hash.length) { location.hash.substr(1).split('&').forEach(function(pair) { pair = pair.split('='); if (pair.length != 2) { throw new Error('Unexpected hash value: ' + location.hash); } result[pair[0]] = pair[1]; }); } return result; })(); // Reflect the mode param as an attribute so we can use CSS attribute selectors // on it. if ('mode' in hashParams) { document.documentElement.setAttribute('mode', hashParams['mode']); } </script><link id="themecss" rel="stylesheet" href="chrome://theme/css/newtab.cs s?1288105177556"> </head> <body class="" i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsiz e" style="font-family: Arial; font-size: 84%; "> <div id="attribution" class="attribution"> <div i18n-content="attributionintro">Theme created by</div> <img id="attribution-img" src="chrome://theme/IDR_PRODUCT_LOGO?1288105177572"> </div> <div id="main">

<div id="notification"> <span>&nbsp;</span> <span class="link"><span class="link-color"></span></span> </div> <div class="maxiview hidden" id="apps-maxiview"></div> <div class="maxiview" id="most-visited-maxiview" style="top: 44px; -webkit-mas k-image: -webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(0.0 774648, transparent), color-stop(0.0950704, rgb(0, 0, 0)), color-stop(0.739437, rgb(0, 0, 0)), color-stop(0.757042, transparent), to(transparent)); "><a class=" thumbnail-container" tabindex="1" id="t0" style="left: 0px; top: 10px; " href="h ttp://timesofindia.indiatimes.com/"><div class="edit-mode-border"><div class="ed it-bar"><div class="pin" title="Keep on this page"></div><div class="spacer"></d iv><div class="remove" title="Don&#39;t show on this page"></div></div><span cla ss="thumbnail-wrapper" style="background-image: url(chrome://thumb/http://timeso findia.indiatimes.com/); "><span class="thumbnail"></span></span></div><div clas s="title"><div style="background-image: url(chrome://favicon/http://timesofindia .indiatimes.com/); " dir="ltr">The Times of India: Latest News India, World &amp ; Business News, Cricket &amp; Sports, Bollywood</div></div></a><a class="thumbn ail-container pinned" tabindex="1" id="t1" style="left: 235px; top: 10px; " href ="http://www.bhejafry.net/"><div class="edit-mode-border"><div class="edit-bar"> <div class="pin" title="Don&#39;t keep on this page"></div><div class="spacer">< /div><div class="remove" title="Don&#39;t show on this page"></div></div><span c lass="thumbnail-wrapper" style="background-image: url(chrome://thumb/http://www. bhejafry.net/); "><span class="thumbnail"></span></span></div><div class="title" ><div style="background-image: url(chrome://favicon/http://www.bhejafry.net/); " dir="ltr" title="">BhejaFry</div></div></a><a class="thumbnail-container" tabin dex="1" id="t2" style="left: 470px; top: 10px; " href="http://www.facebook.com/" ><div class="edit-mode-border"><div class="edit-bar"><div class="pin" title="Kee p on this page"></div><div class="spacer"></div><div class="remove" title="Don&# 39;t show on this page"></div></div><span class="thumbnail-wrapper" style="backg round-image: url(chrome://thumb/http://www.facebook.com/); "><span class="thumbn ail"></span></span></div><div class="title"><div style="background-image: url(ch rome://favicon/http://www.facebook.com/); " dir="ltr">Facebook (1)</div></div></ a><a class="thumbnail-container pinned" tabindex="1" id="t3" style="left: 705px; top: 10px; " href="http://www.youtube.com/"><div class="edit-mode-border"><div class="edit-bar"><div class="pin" title="Don&#39;t keep on this page"></div><div class="spacer"></div><div class="remove" title="Don&#39;t show on this page"></ div></div><span class="thumbnail-wrapper" style="background-image: url(chrome:// thumb/http://www.youtube.com/); "><span class="thumbnail"></span></span></div><d iv class="title"><div style="background-image: url(chrome://favicon/http://www.y outube.com/); " dir="ltr">YouTube - Broadcast Yourself.</div></div></a><a class= "thumbnail-container" tabindex="1" id="t4" style="left: 0px; top: 193px; " href= "http://www.orkut.com/"><div class="edit-mode-border"><div class="edit-bar"><div class="pin" title="Keep on this page"></div><div class="spacer"></div><div clas s="remove" title="Don&#39;t show on this page"></div></div><span class="thumbnai l-wrapper" style="background-image: url(chrome://thumb/http://www.orkut.com/); " ><span class="thumbnail"></span></span></div><div class="title"><div style="back ground-image: url(chrome://favicon/http://www.orkut.com/); " dir="ltr">Redirecti ng</div></div></a><a class="thumbnail-container" tabindex="1" id="t5" style="lef t: 235px; top: 193px; " href="http://www.google.com/"><div class="edit-mode-bord er"><div class="edit-bar"><div class="pin" title="Keep on this page"></div><div class="spacer"></div><div class="remove" title="Don&#39;t show on this page"></d iv></div><span class="thumbnail-wrapper" style="background-image: url(chrome://t humb/http://www.google.com/); "><span class="thumbnail"></span></span></div><div class="title"><div style="background-image: url(chrome://favicon/http://www.goo gle.com/); " dir="ltr" title="">iGoogle</div></div></a><a class="thumbnail-conta iner" tabindex="1" id="t6" style="left: 470px; top: 193px; " href="http://www.gm ail.com/"><div class="edit-mode-border"><div class="edit-bar"><div class="pin" t

itle="Keep on this page"></div><div class="spacer"></div><div class="remove" tit le="Don&#39;t show on this page"></div></div><span class="thumbnail-wrapper" sty le="background-image: url(chrome://thumb/http://www.gmail.com/); "><span class=" thumbnail"></span></span></div><div class="title"><div style="background-image: url(chrome://favicon/http://www.gmail.com/); " dir="ltr">Gmail: Email from Googl e</div></div></a><a class="thumbnail-container" tabindex="1" id="t7" style="left : 705px; top: 193px; " href="http://www.youtube.com/results?search_query=hindi+s ongs&aq=0"><div class="edit-mode-border"><div class="edit-bar"><div class="pin" title="Keep on this page"></div><div class="spacer"></div><div class="remove" ti tle="Don&#39;t show on this page"></div></div><span class="thumbnail-wrapper" st yle="background-image: url(chrome://thumb/http://www.youtube.com/results?search_ query=hindi+songs&amp;aq=0); "><span class="thumbnail"></span></span></div><div class="title"><div style="background-image: url(chrome://favicon/http://www.yout ube.com/results?search_query=hindi+songs&amp;aq=0); " dir="ltr">YouTube - hindi songs</div></div></a></div> <div class="sections"> <!-- Start disabled. We only enable if we have at least one app. --> <div id="apps" class="section hidden disabled" section="APPS" style="top: 25 px; "> <h2> <img class="disclosure" img="" src="data:image/png;base64,iVBORw0KGgoAAA ANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAA d0SU1FB9oIGRQbOY8MjgMAAABVSURBVBjTfc6xDcAwCETRM0rt5nbA+49j70DDAqSLsGXyJQqkVxxwNO eMiEA+waW1VuT/inrvG7wikht8UETy2ygVMjO4O8YYTf6AqrZyUwYlygAAXo+QLmeF4c4uAAAAAElFTk SuQmCC"> <div class="back"></div> <span i18n-content="apps">Apps</span> <button class="section-close-button"></button> </h2> <div class="miniview"></div> </div> <div id="most-visited" class="section" section="THUMB" style="top: 25px; "> <h2> <img class="disclosure" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEU gAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB 9oIGRQbOY8MjgMAAABVSURBVBjTfc6xDcAwCETRM0rt5nbA+49j70DDAqSLsGXyJQqkVxxwNOeMiEA+w aW1VuT/inrvG7wikht8UETy2ygVMjO4O8YYTf6AqrZyUwYlygAAXo+QLmeF4c4uAAAAAElFTkSuQmCC" > <div class="back"></div> <span i18n-content="mostvisited">Most visited</span> <button id="most-visited-settings" i18n-content="restorethumbnails" clas s="has-blacklist">Restore all removed thumbnails</button> <button class="section-close-button"></button> </h2> <div class="miniview"><span><a href="http://timesofindia.indiatimes.com/" style="background-image: url(chrome://favicon/http://timesofindia.indiatimes.com /); " class="item">The Times of India: Latest News India, World &amp; Business N ews, Cricket &amp; Sports, Bollywood</a></span><span><a href="http://www.bhejafr y.net/" style="background-image: url(chrome://favicon/http://www.bhejafry.net/); " class="item">BhejaFry</a></span><span><a href="http://www.facebook.com/" styl e="background-image: url(chrome://favicon/http://www.facebook.com/); " class="it em">Facebook (1)</a></span><span><a href="http://www.youtube.com/" style="backgr ound-image: url(chrome://favicon/http://www.youtube.com/); " class="item">YouTub e - Broadcast Yourself.</a></span><span><a href="http://www.orkut.com/" style="b ackground-image: url(chrome://favicon/http://www.orkut.com/); " class="item">Red irecting</a></span><span><a href="http://www.google.com/" style="background-imag e: url(chrome://favicon/http://www.google.com/); " class="item">iGoogle</a></spa n><span><a href="http://www.gmail.com/" style="background-image: url(chrome://fa

vicon/http://www.gmail.com/); " class="item">Gmail: Email from Google</a></span> <span><a href="http://www.youtube.com/results?search_query=hindi+songs&aq=0" sty le="background-image: url(chrome://favicon/http://www.youtube.com/results?search _query=hindi+songs&amp;aq=0); " class="item">YouTube - hindi songs</a></span></d iv> </div> <!-- Start this section disabled because it might not have data, and looks silly without any. --> <div id="recently-closed" class="section hidden" section="RECENT" noexpand=" true" style="top: 445px; "> <h2> <div class="back"></div> <span i18n-content="recentlyclosed">Recently closed</span> <button class="section-close-button"></button> </h2> <div class="miniview"><span><span class="item link window" tabindex="0">2 Tabs</span></span><span><a class="item" href="https://www.google.com/accounts/Se rviceLogin?service=mail&passive=true&rm=false&continue=https%3A%2F%2Fmail.google .com%2Fmail%2F%3Fui%3Dhtml%26zy%3Dl&bsv=1eic6yu9oa4y3&ss=1&scc=1&ltmpl=default&l tmplcache=2&hl=en" style="background-image: url(chrome://favicon/https://www.goo gle.com/accounts/ServiceLogin?service=mail&amp;passive=true&amp;rm=false&amp;con tinue=https%3A%2F%2Fmail.google.com%2Fmail%2F%3Fui%3Dhtml%26zy%3Dl&amp;bsv=1eic6 yu9oa4y3&amp;ss=1&amp;scc=1&amp;ltmpl=default&amp;ltmplcache=2&amp;hl=en); " dir ="ltr">Gmail: Email from Google</a></span><span><a class="item" href="https://ma il.google.com/mail/?hl=en&shva=1#inbox/12be8a7f43e77145" style="background-image : url(chrome://favicon/https://mail.google.com/mail/?hl=en&amp;shva=1#inbox/12be 8a7f43e77145); " dir="ltr">Gmail - resume - bhosaledipti2@gmail.com</a></span><s pan><a class="item" href="https://docs.google.com/viewer?a=v&pid=gmail&attid=0.1 &thid=12be8a7f43e77145&mt=application/msword&url=https://mail.google.com/mail/?u i%3D2%26ik%3Dfa3f6d8395%26view%3Datt%26th%3D12be8a7f43e77145%26attid%3D0.1%26dis p%3Dattd%26realattid%3Df_gfqslmwy0%26zw&sig=AHIEtbShfif170svnaV61ciiNSAIirkPGA" style="background-image: url(chrome://favicon/https://docs.google.com/viewer?a=v &amp;pid=gmail&amp;attid=0.1&amp;thid=12be8a7f43e77145&amp;mt=application/msword &amp;url=https://mail.google.com/mail/?ui%3D2%26ik%3Dfa3f6d8395%26view%3Datt%26t h%3D12be8a7f43e77145%26attid%3D0.1%26disp%3Dattd%26realattid%3Df_gfqslmwy0%26zw& amp;sig=AHIEtbShfif170svnaV61ciiNSAIirkPGA); " dir="ltr" title="Trupti_Bhosale.d oc - Powered by Google Docs">Trupti_Bhosale.doc - Powered by Google Docs</a></sp an><span><a class="item" href="https://docs.google.com/viewer?a=v&pid=gmail&atti d=0.1&thid=12be8a8b9a052b61&mt=application/msword&url=https://mail.google.com/ma il/?ui%3D2%26ik%3Dfa3f6d8395%26view%3Datt%26th%3D12be8a8b9a052b61%26attid%3D0.1% 26disp%3Dattd%26realattid%3Df_gfqsmvwe0%26zw&sig=AHIEtbTRAbbW-blUUUqduHuZXKmN3sd rIQ" style="background-image: url(chrome://favicon/https://docs.google.com/viewe r?a=v&amp;pid=gmail&amp;attid=0.1&amp;thid=12be8a8b9a052b61&amp;mt=application/m sword&amp;url=https://mail.google.com/mail/?ui%3D2%26ik%3Dfa3f6d8395%26view%3Dat t%26th%3D12be8a8b9a052b61%26attid%3D0.1%26disp%3Dattd%26realattid%3Df_gfqsmvwe0% 26zw&amp;sig=AHIEtbTRAbbW-blUUUqduHuZXKmN3sdrIQ); " dir="ltr" title="Format CV f or MBA IT II (09-11).doc - Powered by Google Docs">Format CV for MBA IT II (09-1 1).doc - Powered by Google Docs</a></span><span><a class="item" href="https://do cs.google.com/viewer?a=v&pid=gmail&attid=0.1&thid=12be8a8b9a052b61&mt=applicatio n/msword&url=https://mail.google.com/mail/?ui%3D2%26ik%3Dfa3f6d8395%26view%3Datt %26th%3D12be8a8b9a052b61%26attid%3D0.1%26disp%3Dattd%26realattid%3Df_gfqsmvwe0%2 6zw&sig=AHIEtbTRAbbW-blUUUqduHuZXKmN3sdrIQ&pli=1" style="background-image: url(c hrome://favicon/https://docs.google.com/viewer?a=v&amp;pid=gmail&amp;attid=0.1&a mp;thid=12be8a8b9a052b61&amp;mt=application/msword&amp;url=https://mail.google.c om/mail/?ui%3D2%26ik%3Dfa3f6d8395%26view%3Datt%26th%3D12be8a8b9a052b61%26attid%3 D0.1%26disp%3Dattd%26realattid%3Df_gfqsmvwe0%26zw&amp;sig=AHIEtbTRAbbW-blUUUqduH uZXKmN3sdrIQ&amp;pli=1); " dir="ltr" title="Format CV for MBA IT II (09-11).doc - Powered by Google Docs">Format CV for MBA IT II (09-11).doc - Powered by Googl e Docs</a></span><span><a class="item" href="http://www.yahoo.com/" style="backg

round-image: url(chrome://favicon/http://www.yahoo.com/); " dir="ltr" title="">Y ahoo!</a></span><span style="display: none; "><a class="item" href="http://www.f acebook.com/login.php?email=bhosalevrushali%40ymail.com" style="background-image : url(chrome://favicon/http://www.facebook.com/login.php?email=bhosalevrushali%4 0ymail.com); " dir="ltr">Login Facebook</a></span><span style="display: none; "><a class="item" href="http://www.facebook.com/index.php?lh=64dd766b3262d480ef0 b034ba3f22f0e&eu=cJRSVVxsMmPq1BD1_uNLJw" style="background-image: url(chrome://f avicon/http://www.facebook.com/index.php?lh=64dd766b3262d480ef0b034ba3f22f0e&amp ;eu=cJRSVVxsMmPq1BD1_uNLJw); " dir="ltr">Welcome to Facebook</a></span></div> </div> <div id="sync-status" class="section disabled"> <div> <h3></h3> <span></span> </div> </div> </div> <div id="closed-sections-bar"> <!-- The default visibility of these buttons needs to be the opposite of the default visibility of the corresponding sections. --> <button id="apps-button" menu="#apps-menu" class="disabled"> <span i18n-content="apps">Apps</span> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQ YQAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oIGRQbOY8MjgMAAABVSU RBVBjTfc6xDcAwCETRM0rt5nbA+49j70DDAqSLsGXyJQqkVxxwNOeMiEA+waW1VuT/inrvG7wikht8UE Ty2ygVMjO4O8YYTf6AqrZyUwYlygAAXo+QLmeF4c4uAAAAAElFTkSuQmCC"> </button> <button id="most-visited-button" class="disabled" menu="#most-visited-menu"> <span i18n-content="mostvisited">Most visited</span> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQ YQAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oIGRQbOY8MjgMAAABVSU RBVBjTfc6xDcAwCETRM0rt5nbA+49j70DDAqSLsGXyJQqkVxxwNOeMiEA+waW1VuT/inrvG7wikht8UE Ty2ygVMjO4O8YYTf6AqrZyUwYlygAAXo+QLmeF4c4uAAAAAElFTkSuQmCC"> </button> <button id="recently-closed-button" menu="#recently-closed-menu" class="disa bled"> <span i18n-content="recentlyclosed">Recently closed</span> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQ YQAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oIGRQbOY8MjgMAAABVSU RBVBjTfc6xDcAwCETRM0rt5nbA+49j70DDAqSLsGXyJQqkVxxwNOeMiEA+waW1VuT/inrvG7wikht8UE Ty2ygVMjO4O8YYTf6AqrZyUwYlygAAXo+QLmeF4c4uAAAAAElFTkSuQmCC"> </button> </div> </div> <!-- main --> <div class="window-menu" id="window-tooltip" style="display: none; "></div> <command mand> <command <command <command > id="clear-all-blacklisted" i18n-values=".label:restorethumbnails"></com id="apps-launch-command"></command> id="apps-options-command" i18n-values=".label:appoptions"></command> id="apps-uninstall-command" i18n-values=".label:appuninstall"></command

<menu id="app-context-menu"> <button class="default" command="#apps-launch-command"></button> <hr> <button command="#apps-options-command">Options</button>

<button command="#apps-uninstall-command">Uninstall</button> </menu> <!-- These are populated dynamically --> <menu id="apps-menu"><hr><span><a href="" class="item">Apps</a></span></menu> <menu id="most-visited-menu"><span><a href="http://timesofindia.indiatimes.com/" style="background-image: url(chrome://favicon/http://timesofindia.indiatimes.co m/); " class=" item menuitem">The Times of India: Latest News India, World &amp; Business News, Cricket &amp; Sports, Bollywood</a></span><span><a href="http:// www.bhejafry.net/" style="background-image: url(chrome://favicon/http://www.bhej afry.net/); " class=" item menuitem">BhejaFry</a></span><span><a href="http://ww w.facebook.com/" style="background-image: url(chrome://favicon/http://www.facebo ok.com/); " class=" item menuitem">Facebook (1)</a></span><span><a href="http:// www.youtube.com/" style="background-image: url(chrome://favicon/http://www.youtu be.com/); " class=" item menuitem">YouTube - Broadcast Yourself.</a></span><span ><a href="http://www.orkut.com/" style="background-image: url(chrome://favicon/h ttp://www.orkut.com/); " class=" item menuitem">Redirecting</a></span><span><a h ref="http://www.google.com/" style="background-image: url(chrome://favicon/http: //www.google.com/); " class=" item menuitem">iGoogle</a></span><span><a href="ht tp://www.gmail.com/" style="background-image: url(chrome://favicon/http://www.gm ail.com/); " class=" item menuitem">Gmail: Email from Google</a></span><span><a href="http://www.youtube.com/results?search_query=hindi+songs&aq=0" style="backg round-image: url(chrome://favicon/http://www.youtube.com/results?search_query=hi ndi+songs&amp;aq=0); " class=" item menuitem">YouTube - hindi songs</a></span><h r><span><a href="" class="item">Most visited</a></span></menu> <menu id="recently-closed-menu"><span><a class="window item menuitem" href="">2 Tabs</a></span><span><a href="https://www.google.com/accounts/ServiceLogin?servi ce=mail&passive=true&rm=false&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F%3 Fui%3Dhtml%26zy%3Dl&bsv=1eic6yu9oa4y3&ss=1&scc=1&ltmpl=default&ltmplcache=2&hl=e n" style="background-image: url(chrome://favicon/https://www.google.com/accounts /ServiceLogin?service=mail&amp;passive=true&amp;rm=false&amp;continue=https%3A%2 F%2Fmail.google.com%2Fmail%2F%3Fui%3Dhtml%26zy%3Dl&amp;bsv=1eic6yu9oa4y3&amp;ss= 1&amp;scc=1&amp;ltmpl=default&amp;ltmplcache=2&amp;hl=en); " class=" item menuit em">Gmail: Email from Google</a></span><span><a href="https://mail.google.com/ma il/?hl=en&shva=1#inbox/12be8a7f43e77145" style="background-image: url(chrome://f avicon/https://mail.google.com/mail/?hl=en&amp;shva=1#inbox/12be8a7f43e77145); " class=" item menuitem">Gmail - resume - bhosaledipti2@gmail.com</a></span><span ><a href="https://docs.google.com/viewer?a=v&pid=gmail&attid=0.1&thid=12be8a7f43 e77145&mt=application/msword&url=https://mail.google.com/mail/?ui%3D2%26ik%3Dfa3 f6d8395%26view%3Datt%26th%3D12be8a7f43e77145%26attid%3D0.1%26disp%3Dattd%26reala ttid%3Df_gfqslmwy0%26zw&sig=AHIEtbShfif170svnaV61ciiNSAIirkPGA" style="backgroun d-image: url(chrome://favicon/https://docs.google.com/viewer?a=v&amp;pid=gmail&a mp;attid=0.1&amp;thid=12be8a7f43e77145&amp;mt=application/msword&amp;url=https:/ /mail.google.com/mail/?ui%3D2%26ik%3Dfa3f6d8395%26view%3Datt%26th%3D12be8a7f43e7 7145%26attid%3D0.1%26disp%3Dattd%26realattid%3Df_gfqslmwy0%26zw&amp;sig=AHIEtbSh fif170svnaV61ciiNSAIirkPGA); " class=" item menuitem">Trupti_Bhosale.doc - Power ed by Google Docs</a></span><span><a href="https://docs.google.com/viewer?a=v&pi d=gmail&attid=0.1&thid=12be8a8b9a052b61&mt=application/msword&url=https://mail.g oogle.com/mail/?ui%3D2%26ik%3Dfa3f6d8395%26view%3Datt%26th%3D12be8a8b9a052b61%26 attid%3D0.1%26disp%3Dattd%26realattid%3Df_gfqsmvwe0%26zw&sig=AHIEtbTRAbbW-blUUUq duHuZXKmN3sdrIQ" style="background-image: url(chrome://favicon/https://docs.goog le.com/viewer?a=v&amp;pid=gmail&amp;attid=0.1&amp;thid=12be8a8b9a052b61&amp;mt=a pplication/msword&amp;url=https://mail.google.com/mail/?ui%3D2%26ik%3Dfa3f6d8395 %26view%3Datt%26th%3D12be8a8b9a052b61%26attid%3D0.1%26disp%3Dattd%26realattid%3D f_gfqsmvwe0%26zw&amp;sig=AHIEtbTRAbbW-blUUUqduHuZXKmN3sdrIQ); " class=" item men uitem">Format CV for MBA IT II (09-11).doc - Powered by Google Docs</a></span><s pan><a href="https://docs.google.com/viewer?a=v&pid=gmail&attid=0.1&thid=12be8a8 b9a052b61&mt=application/msword&url=https://mail.google.com/mail/?ui%3D2%26ik%3D fa3f6d8395%26view%3Datt%26th%3D12be8a8b9a052b61%26attid%3D0.1%26disp%3Dattd%26re alattid%3Df_gfqsmvwe0%26zw&sig=AHIEtbTRAbbW-blUUUqduHuZXKmN3sdrIQ&pli=1" style="

background-image: url(chrome://favicon/https://docs.google.com/viewer?a=v&amp;pi d=gmail&amp;attid=0.1&amp;thid=12be8a8b9a052b61&amp;mt=application/msword&amp;ur l=https://mail.google.com/mail/?ui%3D2%26ik%3Dfa3f6d8395%26view%3Datt%26th%3D12b e8a8b9a052b61%26attid%3D0.1%26disp%3Dattd%26realattid%3Df_gfqsmvwe0%26zw&amp;sig =AHIEtbTRAbbW-blUUUqduHuZXKmN3sdrIQ&amp;pli=1); " class=" item menuitem">Format CV for MBA IT II (09-11).doc - Powered by Google Docs</a></span><span><a href="h ttp://www.yahoo.com/" style="background-image: url(chrome://favicon/http://www.y ahoo.com/); " class=" item menuitem">Yahoo!</a></span><span><a href="http://www. facebook.com/login.php?email=bhosalevrushali%40ymail.com" style="background-imag e: url(chrome://favicon/http://www.facebook.com/login.php?email=bhosalevrushali% 40ymail.com); " class=" item menuitem">Login Facebook</a></span><span><a href= "http://www.facebook.com/index.php?lh=64dd766b3262d480ef0b034ba3f22f0e&eu=cJRSVV xsMmPq1BD1_uNLJw" style="background-image: url(chrome://favicon/http://www.faceb ook.com/index.php?lh=64dd766b3262d480ef0b034ba3f22f0e&amp;eu=cJRSVVxsMmPq1BD1_uN LJw); " class=" item menuitem">Welcome to Facebook</a></span><hr><span><a href=" " class="item">Recently closed</a></span></menu>

<script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview This is a simple template engine inspired by JsTemplates * optimized for i18n. * * It currently supports two handlers: * * * i18n-content which sets the textContent of the element * * <span i18n-content="myContent"></span> * i18nTemplate.process(element, {'myContent': 'Content'}); * * * i18n-values is a list of attribute-value or property-value pairs. * Properties are prefixed with a '.' and can contain nested properties. * * <span i18n-values="title:myTitle;.style.fontSize:fontSize"></span> * i18nTemplate.process(element, { * 'myTitle': 'Title', * 'fontSize': '13px' * }); */ var i18nTemplate = (function() { /** * This provides the handlers for the templating engine. The key is used as * the attribute name and the value is the function that gets called for every * single node that has this attribute. * @type {Object} */ var handlers = { /** * This handler sets the textContent of the element. */ 'i18n-content': function(element, attributeValue, obj) { element.textContent = obj[attributeValue]; }, /**

* This is used to set HTML attributes and DOM properties,. The syntax is: * attributename:key; * .domProperty:key; * .nested.dom.property:key */ 'i18n-values': function(element, attributeValue, obj) { var parts = attributeValue.replace(/\s/g, '').split(/;/); for (var j = 0; j < parts.length; j++) { var a = parts[j].match(/^([^:]+):(.+)$/); if (a) { var propName = a[1]; var propExpr = a[2]; // Ignore missing properties if (propExpr in obj) { var value = obj[propExpr]; if (propName.charAt(0) == '.') { var path = propName.slice(1).split('.'); var object = element; while (object && path.length > 1) { object = object[path.shift()]; } if (object) { object[path] = value; // In case we set innerHTML (ignoring others) we need to // recursively check the content if (path == 'innerHTML') { process(element, obj); } } } else { element.setAttribute(propName, value); } } else { console.warn('i18n-values: Missing value for "' + propExpr + '"'); } } } } }; var attributeNames = []; for (var key in handlers) { attributeNames.push(key); } var selector = '[' + attributeNames.join('],[') + ']'; /** * Processes a DOM tree with the {@code obj} map. */ function process(node, obj) { var elements = node.querySelectorAll(selector); for (var element, i = 0; element = elements[i]; i++) { for (var j = 0; j < attributeNames.length; j++) { var name = attributeNames[j]; var att = element.getAttribute(name); if (att != null) { handlers[name](element, att, obj); } }

} } return { process: process }; })(); </script> <script> i18nTemplate.process(document, templateData); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(arv): Namespace /** * The local strings get injected into the page usig a variable named * {@code templateData}. This class provides a simpler interface to access those * strings. * @constructor */ function LocalStrings() { } // Start of anonymous namespace. (function() { /** * Returns a formatted string where $1 to $9 are replaced by the second to the * tenth argument. * @param {string} s The format string. * @param {...string} The extra values to include in the formatted output. * @return {string} The string after format substitution. */ function replaceArgs(s, args) { return s.replace(/\$[$1-9]/g, function(m) { return (m == '$$') ? '$' : args[m[1]]; }); } /** * Returns a string after removing Windows-style accelerators. * @param {string} s The input string that may contain accelerators. * @return {string} The resulting string with accelerators removed. */ function trimAccelerators(s) { return s.replace(/&{1,2}/g, function(m) { return (m == '&&') ? '&' : ''; }); } LocalStrings.prototype = { /** * The template data object. * @type {Object} */ templateData: null,

/** * Gets a localized string by its id. * @param {string} s The ID of the string we want. * @return {string} The localized string. */ getString: function(id) { // TODO(arv): We should not rely on a global variable here. return (this.templateData window.templateData)[id] ''; }, /** * Returns a formatted localized string where $1 to $9 are replaced by the * second to the tenth argument. * @param {string} id The ID of the string we want. * @param {...string} The extra values to include in the formatted output. * @return {string} The formatted string. */ getStringF: function(id, var_args) { return replaceArgs(this.getString(id), arguments); }, /** * Gets a localized string (stripped of Windows-style accelerators) by its id. * @param {string} s The ID of the string we want. * @return {string} The localized string. */ getStringWithoutAccelerator: function(id) { return trimAccelerators(this.getString(id)); }, /** * Returns a formatted localized string (stripped of Windows-style * accelerators) where $1 to $9 are replaced by the second to the tenth * argument. * @param {string} id The ID of the string we want. * @param {...string} The extra values to include in the formatted output. * @return {string} The formatted string. */ getStringWithoutAcceleratorF: function(id, var_args) { return replaceArgs(this.getStringWithoutAccelerator(id), arguments); } }; // End of anonymous namespace. })(); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview This implements the HTML5 HTMLElement classList property. */ // TODO(arv): Remove this when classList is available in WebKit. // https://bugs.webkit.org/show_bug.cgi?id=20709 if (typeof document.createElement('div').classList == 'undefined') { function DOMTokenList(el) {

this.el_ = el; } (function() { var re = /\s+/; function split(el) { var cn = el.className.replace(/^\s+ \s$/g, ''); if (cn == '') return []; return cn.split(re); } function DOMException_(code) { this.code = code; } DOMException_.prototype = DOMException.prototype; function assertValidToken(s) { if (s == '') throw new DOMException_(DOMException.SYNTAX_ERR); if (re.test(s)) throw new DOMException_(DOMException.INVALID_CHARACTER_ERR); } DOMTokenList.prototype = { contains: function(token) { assertValidToken(token); return split(this.el_).indexOf(token) >= 0; }, add: function(token) { assertValidToken(token); if (this.contains(token)) return; var cn = this.el_.className; this.el_.className += (cn == '' re.test(cn.slice(-1)) ? '' : ' ') + token; }, remove: function(token) { assertValidToken(token); var names = split(this.el_); var s = []; for (var i = 0; i < names.length; i++) { if (names[i] != token) s.push(names[i]) } this.el_.className = s.join(' '); }, toggle: function(token) { assertValidToken(token); if (this.contains(token)) { this.remove(token); return false; } else { this.add(token); return true; } }, get length() {

return split(this.el_).length; }, item: function(index) { return split(this.el_)[index]; } }; })(); HTMLElement.prototype.__defineGetter__('classList', function() { var tl = new DOMTokenList(this); // Override so that we reuse the same DOMTokenList and so that // el.classList == el.classList this.__defineGetter__('classList', function() { return tl; }); return tl; }); } // if </script> <script>/** * Whitelist of tag names allowed in parseHtmlSubset. * @type {[string]} */ var allowedTags = ['A', 'B', 'STRONG']; /** * Parse a very small subset of HTML. * @param {string} s The string to parse. * @throws {Error} In case of non supported markup. * @return {DocumentFragment} A document fragment containing the DOM tree. */ var allowedAttributes = { 'href': function(node, value) { // Only allow a[href] starting with http:// and https:// return node.tagName == 'A' && (value.indexOf('http://') == 0 value.indexOf('https://') == 0); }, 'target': function(node, value) { // Allow a[target] but reset the value to "". if (node.tagName != 'A') return false; node.setAttribute('target', ''); return true; } } /** * Parse a very small subset of HTML. This ensures that insecure HTML / * javascript cannot be injected into the new tab page. * @param {string} s The string to parse. * @throws {Error} In case of non supported markup. * @return {DocumentFragment} A document fragment containing the DOM tree. */ function parseHtmlSubset(s) { function walk(n, f) { f(n); for (var i = 0; i < n.childNodes.length; i++) { walk(n.childNodes[i], f);

} } function assertElement(node) { if (allowedTags.indexOf(node.tagName) == -1) throw Error(node.tagName + ' is not supported'); } function assertAttribute(attrNode, node) { var n = attrNode.nodeName; var v = attrNode.nodeValue; if (!allowedAttributes.hasOwnProperty(n) !allowedAttributes[n](node, v)) throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported'); } var r = document.createRange(); r.selectNode(document.body); // This does not execute any scripts. var df = r.createContextualFragment(s); walk(df, function(node) { switch (node.nodeType) { case Node.ELEMENT_NODE: assertElement(node); var attrs = node.attributes; for (var i = 0; i < attrs.length; i++) { assertAttribute(attrs[i], node); } break; case Node.COMMENT_NODE: case Node.DOCUMENT_FRAGMENT_NODE: case Node.TEXT_NODE: break; default: throw Error('Node type ' + node.nodeType + ' is not supported'); } }); return df; } </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. const cr = (function() { /** * Whether we are using a Mac or not. * @type {boolean} */ const isMac = /Mac/.test(navigator.platform); /** * Whether this is on the Windows platform or not. * @type {boolean} */ const isWindows = /Win/.test(navigator.platform);

/** * Whether this is on chromeOS or not. * @type {boolean} */ const isChromeOS = /CrOS/.test(navigator.userAgent); /** * Builds an object structure for the provided namespace path, * ensuring that names that already exist are not overwritten. For * example: * "a.b.c" -> a = {};a.b={};a.b.c={}; * @param {string} name Name of the object that this file defines. * @param {*=} opt_object The object to expose at the end of the path. * @param {Object=} opt_objectToExportTo The object to add the path to; * default is {@code window}. * @private */ function exportPath(name, opt_object, opt_objectToExportTo) { var parts = name.split('.'); var cur = opt_objectToExportTo window /* global */; for (var part; parts.length && (part = parts.shift());) { if (!parts.length && opt_object !== undefined) { // last part and we have an object; use it cur[part] = opt_object; } else if (part in cur) { cur = cur[part]; } else { cur = cur[part] = {}; } } return cur; }; // cr.Event is called CrEvent in here to prevent naming conflicts. We also // store the original Event in case someone does a global alias of cr.Event. const DomEvent = Event; /** * Creates a new event to be used with cr.EventTarget or DOM EventTarget * objects. * @param {string} type The name of the event. * @param {boolean=} opt_bubbles Whether the event bubbles. Default is false. * @param {boolean=} opt_preventable Whether the default action of the event * can be prevented. * @constructor * @extends {DomEvent} */ function CrEvent(type, opt_bubbles, opt_preventable) { var e = cr.doc.createEvent('Event'); e.initEvent(type, !!opt_bubbles, !!opt_preventable); e.__proto__ = CrEvent.prototype; return e; } CrEvent.prototype = { __proto__: DomEvent.prototype }; /**

* Fires a property change event on the target. * @param {EventTarget} target The target to dispatch the event on. * @param {string} propertyName The name of the property that changed. * @param {*} newValue The new value for the property. * @param {*} oldValue The old value for the property. */ function dispatchPropertyChange(target, propertyName, newValue, oldValue) { var e = new CrEvent(propertyName + 'Change'); e.propertyName = propertyName; e.newValue = newValue; e.oldValue = oldValue; target.dispatchEvent(e); } /** * The kind of property to define in {@code defineProperty}. * @enum {number} */ const PropertyKind = { /** * Plain old JS property where the backing data is stored as a "private" * field on the object. */ JS: 'js', /** * The property backing data is stored as an attribute on an element. */ ATTR: 'attr', /** * The property backing data is stored as an attribute on an element. If the * element has the attribute then the value is true. */ BOOL_ATTR: 'boolAttr' }; /** * Helper function for defineProperty that returns the getter to use for the * property. * @param {string} name * @param {cr.PropertyKind} kind * @param {*} defaultValue The default value. This is only used for the ATTR * kind. * @return {function():*} The getter for the property. */ function getGetter(name, kind, defaultValue) { switch (kind) { case PropertyKind.JS: var privateName = name + '_'; return function() { return this[privateName]; }; case PropertyKind.ATTR: // For attr with default value we return the default value if the // element is missing the attribute. if (defaultValue == undefined) { return function() { return this.getAttribute(name); };

} else { return function() { // WebKit uses null for non existant attributes. var value = this.getAttribute(name); return value !== null ? value : defaultValue; }; } case PropertyKind.BOOL_ATTR: // Boolean attributes don't support default values. return function() { return this.hasAttribute(name); }; } } /** * Helper function for defineProperty that returns the setter of the right * kind. * @param {string} name The name of the property we are defining the setter * for. * @param {cr.PropertyKind} kind The kind of property we are getting the * setter for. * @return {function(*):void} The function to use as a setter. */ function getSetter(name, kind) { switch (kind) { case PropertyKind.JS: var privateName = name + '_'; return function(value) { var oldValue = this[privateName]; if (value !== oldValue) { this[privateName] = value; dispatchPropertyChange(this, name, value, oldValue); } }; case PropertyKind.ATTR: return function(value) { var oldValue = this[name]; if (value !== oldValue) { this.setAttribute(name, value); dispatchPropertyChange(this, name, value, oldValue); } }; case PropertyKind.BOOL_ATTR: return function(value) { var oldValue = this[name]; if (value !== oldValue) { if (value) this.setAttribute(name, name); else this.removeAttribute(name); dispatchPropertyChange(this, name, value, oldValue); } }; } } /**

* Defines a property on an object. When the setter changes the value a * property change event with the type {@code name + 'Change'} is fired. * @param {!Object} The object to define the property for. * @param {string} The name of the property. * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use. * @param {*} opt_defaultValue The default value. */ function defineProperty(obj, name, opt_kind, opt_default) { if (typeof obj == 'function') obj = obj.prototype; var kind = opt_kind PropertyKind.JS;

if (!obj.__lookupGetter__(name)) { // For js properties we set the default value on the prototype. if (kind == PropertyKind.JS && arguments.length > 3) { var privateName = name + '_'; obj[privateName] = opt_default; } obj.__defineGetter__(name, getGetter(name, kind, opt_default)); } if (!obj.__lookupSetter__(name)) { obj.__defineSetter__(name, getSetter(name, kind)); } } /** * Counter for use with createUid */ var uidCounter = 1; /** * @return {number} A new unique ID. */ function createUid() { return uidCounter++; } /** * Returns a unique ID for the item. This mutates the item so it needs to be * an object * @param {!Object} item The item to get the unique ID for. * @return {number} The unique ID for the item. */ function getUid(item) { if (item.hasOwnProperty('uid')) return item.uid; return item.uid = createUid(); } /** * Partially applies this function to a particular 'this object' and zero or * more arguments. The result is a new function with some arguments of the * first function pre-filled and the value of this 'pre-specified'. * * Remaining arguments specified at call-time are appended to the pre* specified ones. * * Usage:

* <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2'); * barMethBound('arg3', 'arg4');</pre> * * @param {Function} fn A function to partially apply. * @param {Object undefined} selfObj Specifies the object which this should * point to when the function is run. If the value is null or undefined, * it will default to the global object. * @param {...*} var_args Additional arguments that are partially * applied to the function. * * @return {!Function} A partially-applied form of the function bind() was * invoked as a method of. */ function bind(fn, selfObj, var_args) { var boundArgs = Array.prototype.slice.call(arguments, 2); return function() { var args = Array.prototype.slice.call(arguments); args.unshift.apply(args, boundArgs); return fn.apply(selfObj, args); } } /** * Dispatches a simple event on an event target. * @param {!EventTarget} target The event target to dispatch the event on. * @param {string} type The type of the event. * @param {boolean=} opt_bubbles Whether the event bubbles or not. * @param {boolean=} opt_cancelable Whether the default action of the event * can be prevented. * @return {boolean} If any of the listeners called {@code preventDefault} * during the dispatch this will return false. */ function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { var e = new cr.Event(type, opt_bubbles, opt_cancelable); return target.dispatchEvent(e); } /** * @param {string} name * @param {!Function} fun */ function define(name, fun) { var obj = exportPath(name); var exports = fun(); for (var propertyName in exports) { // Maybe we should check the prototype chain here? The current usage // pattern is always using an object literal so we only care about own // properties. var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, propertyName); if (propertyDescriptor) Object.defineProperty(obj, propertyName, propertyDescriptor); } } /** * Document used for various document related operations. * @type {!Document} */ var doc = document;

/** * Allows you to run func in the context of a different document. * @param {!Document} document The document to use. * @param {function():*} func The function to call. */ function withDoc(document, func) { var oldDoc = doc; doc = document; try { func(); } finally { doc = oldDoc; } } /** * Adds a {@code getInstance} static method that always return the same * instance object. * @param {!Function} ctor The constructor for the class to add the static * method to. */ function addSingletonGetter(ctor) { ctor.getInstance = function() { return ctor.instance_ (ctor.instance_ = new ctor()); }; } return { addSingletonGetter: addSingletonGetter, isChromeOS: isChromeOS, isMac: isMac, isWindows: isWindows, define: define, defineProperty: defineProperty, PropertyKind: PropertyKind, createUid: createUid, getUid: getUid, bind: bind, dispatchSimpleEvent: dispatchSimpleEvent, dispatchPropertyChange: dispatchPropertyChange, /** * The document that we are currently using. * @type {!Document} */ get doc() { return doc; }, withDoc: withDoc, Event: CrEvent }; })(); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('cr.ui', function() {

/** * Decorates elements as an instance of a class. * @param {string !Element} source The way to find the element(s) to decorate. * If this is a string then {@code querySeletorAll} is used to find the * elements to decorate. * @param {!Function} constr The constructor to decorate with. The constr * needs to have a {@code decorate} function. */ function decorate(source, constr) { var elements; if (typeof source == 'string') elements = cr.doc.querySelectorAll(source); else elements = [source]; for (var i = 0, el; el = elements[i]; i++) { if (!(el instanceof constr)) constr.decorate(el); } } /** * Helper function for creating new element for define. */ function createElementHelper(tagName, opt_bag) { // Allow passing in ownerDocument to create in a different document. var doc; if (opt_bag && opt_bag.ownerDocument) doc = opt_bag.ownerDocument; else doc = cr.doc; return doc.createElement(tagName); } /** * Creates the constructor for a UI element class. * * Usage: * <pre> * var List = cr.ui.define('list'); * List.prototype = { * __proto__: HTMLUListElement.prototype, * decorate: function() { * ... * }, * ... * }; * </pre> * * @param {string Function} tagNameOrFunction The tagName or * function to use for newly created elements. If this is a function it * needs to return a new element when called. * @return {function(Object=):Element} The constructor function which takes * an optional property bag. The function also has a static * {@code decorate} method added to it. */ function define(tagNameOrFunction) { var createFunction, tagName; if (typeof tagNameOrFunction == 'function') {

createFunction = tagNameOrFunction; tagName = ''; } else { createFunction = createElementHelper; tagName = tagNameOrFunction; } /** * Creates a new UI element constructor. * @param {Object=} opt_propertyBag Optional bag of properties to set on the * object after created. The property {@code ownerDocument} is special * cased and it allows you to create the element in a different * document than the default. * @constructor */ function f(opt_propertyBag) { var el = createFunction(tagName, opt_propertyBag); f.decorate(el); for (var propertyName in opt_propertyBag) { el[propertyName] = opt_propertyBag[propertyName]; } return el; } /** * Decorates an element as a UI element class. * @param {!Element} el The element to decorate. */ f.decorate = function(el) { el.__proto__ = f.prototype; el.decorate(); }; return f; } /** * Input elements do not grow and shrink with their content. This is a simple * (and not very efficient) way of handling shrinking to content with support * for min width and limited by the width of the parent element. * @param {HTMLElement} el The element to limit the width for. * @param {number} parentEl The parent element that should limit the size. * @param {number} min The minimum width. */ function limitInputWidth(el, parentEl, min) { // Needs a size larger than borders el.style.width = '10px'; var doc = el.ownerDocument; var win = doc.defaultView; var computedStyle = win.getComputedStyle(el); var parentComputedStyle = win.getComputedStyle(parentEl); var rtl = computedStyle.direction == 'rtl'; // To get the max width we get the width of the treeItem minus the position // of the input. var inputRect = el.getBoundingClientRect(); // box-sizing var parentRect = parentEl.getBoundingClientRect(); var startPos = rtl ? parentRect.right - inputRect.right : inputRect.left - parentRect.left;

// Add up border and padding of the input. var inner = parseInt(computedStyle.borderLeftWidth, 10) + parseInt(computedStyle.paddingLeft, 10) + parseInt(computedStyle.paddingRight, 10) + parseInt(computedStyle.borderRightWidth, 10); // We also need to subtract the padding of parent to prevent it to overflow. var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : parseInt(parentComputedStyle.paddingRight, 10); // The magic number 14 comes from trial and error :'( It consists of: // border + padding + treeItem.paddingEnd + treeItem.borderEnd + // tree.paddingEnd var max = parentEl.clientWidth - startPos - inner - parentPadding; var pcs = getComputedStyle(parentEl); console.log('pcs', 'borderLeft', pcs.borderLeftWidth, 'paddingLeft', pcs.paddingLeft, 'paddingRight', pcs.paddingRight, 'borderRight', pcs.borderRightWidth, 'width', pcs.width, 'clientWidth', parentEl.clientWidth, 'offsetWidth', parentEl.offsetWidth); function limit() { if (el.scrollWidth > max) { el.style.width = max + 'px'; } else { el.style.width = 0; var sw = el.scrollWidth; if (sw < min) { el.style.width = min + 'px'; } else { el.style.width = sw + 'px'; } } } el.addEventListener('input', limit); limit(); } return { decorate: decorate, define: define, limitInputWidth: limitInputWidth }; }); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview A command is an abstraction of an action a user can do in the * UI. * * When the focus changes in the document for each command a canExecute event * is dispatched on the active element. By listening to this event you can * enable and disable the command by setting the event.canExecute property.

* * When a command is executed a command event is dispatched on the active * element. Note that you should stop the propagation after you have handled the * command if there might be other command listeners higher up in the DOM tree. */ cr.define('cr.ui', function() { /** * This is used to identify keyboard shortcuts. * @param {string} shortcut The text used to describe the keys for this * keyboard shortcut. * @constructor */ function KeyboardShortcut(shortcut) { var mods = {}; var ident = ''; shortcut.split('-').forEach(function(part) { var partLc = part.toLowerCase(); switch (partLc) { case 'alt': case 'ctrl': case 'meta': case 'shift': mods[partLc + 'Key'] = true; break; default: if (ident) throw Error('Invalid shortcut'); ident = part; } }); this.ident_ = ident; this.mods_ = mods; } KeyboardShortcut.prototype = { /** * Wether the keyboard shortcut object mathes a keyboard event. * @param {!Event} e The keyboard event object. * @return {boolean} Whether we found a match or not. */ matchesEvent: function(e) { if (e.keyIdentifier == this.ident_) { // All keyboard modifiers needs to match. var mods = this.mods_; return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { return e[k] == !!mods[k]; }); } return false; } }; /** * Creates a new command element. * @constructor * @extends {HTMLElement} */

var Command = cr.ui.define('command'); Command.prototype = { __proto__: HTMLElement.prototype, /** * Initializes the command. */ decorate: function() { CommandManager.init(this.ownerDocument); }, /** * Executes the command. This dispatches a command event on the active * element. If the command is {@code disabled} this does nothing. */ execute: function() { if (this.disabled) return; var doc = this.ownerDocument; if (doc.activeElement) { var e = new cr.Event('command', true, false); e.command = this; doc.activeElement.dispatchEvent(e); } }, /** * Call this when there have been changes that might change whether the * command can be executed or not. */ canExecuteChange: function() { dispatchCanExecuteEvent(this, this.ownerDocument.activeElement); }, /** * The keyboard shortcut that triggers the command. This is a string * consisting of a keyIdentifier (as reported by WebKit in keydown) as * well as optional key modifiers joinded with a '-'. * * Multiple keyboard shortcuts can be provided by separating them by * whitespace. * * For example: * "F1" * "U+0008-Meta" for Apple command backspace. * "U+0041-Ctrl" for Control A * "U+007F U+0008-Meta" for Delete and Command Backspace * * @type {string} */ shortcut_: '', get shortcut() { return this.shortcut_; }, set shortcut(shortcut) { var oldShortcut = this.shortcut_; if (shortcut !== oldShortcut) { this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { return new KeyboardShortcut(shortcut);

}); // Set this after the keyboardShortcuts_ since that might throw. this.shortcut_ = shortcut; cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, oldShortcut); } }, /** * Whether the event object matches the shortcut for this command. * @param {!Event} e The key event object. * @return {boolean} Whether it matched or not. */ matchesEvent: function(e) { if (!this.keyboardShortcuts_) return false; return this.keyboardShortcuts_.some(function(keyboardShortcut) { return keyboardShortcut.matchesEvent(e); }); } }; /** * The label of the command. * @type {string} */ cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); /** * Whether the command is disabled or not. * @type {boolean} */ cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); /** * Whether the command is hidden or not. * @type {boolean} */ cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); /** * Dispatches a canExecute event on the target. * @param {cr.ui.Command} command The command that we are testing for. * @param {Element} target The target element to dispatch the event on. */ function dispatchCanExecuteEvent(command, target) { var e = new CanExecuteEvent(command, true) target.dispatchEvent(e); command.disabled = !e.canExecute; } /** * The command managers for different documents. */ var commandManagers = {}; /** * Keeps track of the focused element and updates the commands when the focus

* changes. * @param {!Document} doc The document that we are managing the commands for. * @constructor */ function CommandManager(doc) { doc.addEventListener('focus', cr.bind(this.handleFocus_, this), true); // Make sure we add the listener to the bubbling phase so that elements can // prevent the command. doc.addEventListener('keydown', cr.bind(this.handleKeyDown_, this), false); } /** * Initializes a command manager for the document as needed. * @param {!Document} doc The document to manage the commands for. */ CommandManager.init = function(doc) { var uid = cr.getUid(doc); if (!(uid in commandManagers)) { commandManagers[uid] = new CommandManager(doc); } }, CommandManager.prototype = { /** * Handles focus changes on the document. * @param {Event} e The focus event object. * @private */ handleFocus_: function(e) { var target = e.target; var commands = Array.prototype.slice.call( target.ownerDocument.querySelectorAll('command')); commands.forEach(function(command) { dispatchCanExecuteEvent(command, target); }); }, /** * Handles the keydown event and routes it to the right command. * @param {!Event} e The keydown event. */ handleKeyDown_: function(e) { var target = e.target; var commands = Array.prototype.slice.call( target.ownerDocument.querySelectorAll('command')); for (var i = 0, command; command = commands[i]; i++) { if (!command.disabled && command.matchesEvent(e)) { e.preventDefault(); // We do not want any other element to handle this. e.stopPropagation(); command.execute(); return; } } } };

/** * The event type used for canExecute events. * @param {!cr.ui.Command} command The command that we are evaluating. * @extends {Event} */ function CanExecuteEvent(command) { var e = command.ownerDocument.createEvent('Event'); e.initEvent('canExecute', true, false); e.__proto__ = CanExecuteEvent.prototype; e.command = command; return e; } CanExecuteEvent.prototype = { __proto__: Event.prototype, /** * The current command * @type {cr.ui.Command} */ command: null, /** * Whether the target can execute the command. Setting this also stops the * propagation. * @type {boolean} */ canExecute_: false, get canExecute() { return this.canExecute_; }, set canExecute(canExecute) { this.canExecute_ = !!canExecute; this.stopPropagation(); } }; // Export return { Command: Command, CanExecuteEvent: CanExecuteEvent }; }); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('cr.ui', function() { const Command = cr.ui.Command; /** * Creates a new menu item element. * @param {Object=} opt_propertyBag Optional properties. * @constructor * @extends {HTMLDivElement} */ var MenuItem = cr.ui.define('div');

/** * Creates a new menu separator element. * @return {cr.ui.MenuItem} */ MenuItem.createSeparator = function() { var el = cr.doc.createElement('hr'); MenuItem.decorate(el); return el; }; MenuItem.prototype = { __proto__: HTMLButtonElement.prototype, /** * Initializes the menu item. */ decorate: function() { var commandId; if ((commandId = this.getAttribute('command'))) this.command = commandId; this.addEventListener('mouseup', this.handleMouseUp_); }, /** * The command associated with this menu item. If this is set to a string * of the form "#element-id" then the element is looked up in the document * of the command. * @type {cr.ui.Command} */ command_: null, get command() { return this.command_; }, set command(command) { if (this.command_) { this.command_.removeEventListener('labelChange', this); this.command_.removeEventListener('disabledChange', this); this.command_.removeEventListener('hiddenChange', this); } if (typeof command == 'string' && command[0] == '#') { command = this.ownerDocument.getElementById(command.slice(1)); cr.ui.decorate(command, Command); } this.command_ = command; if (command) { if (command.id) this.setAttribute('command', '#' + command.id); this.label = command.label; this.disabled = command.disabled; this.hidden = command.hidden; this.command_.addEventListener('labelChange', this); this.command_.addEventListener('disabledChange', this); this.command_.addEventListener('hiddenChange', this); } },

/** * The text label. * @type {string} */ get label() { return this.textContent; }, set label(label) { this.textContent = label; }, /** * @return {boolean} Whether the menu item is a separator. */ isSeparator: function() { return this.tagName == 'HR'; }, /** * Handles mouseup events. This dispatches an active event and if there * is an assiciated command then that is executed. * @param {Event} The mouseup event object. * @private */ handleMouseUp_: function(e) { if (!this.disabled && !this.isSeparator()) { // Dispatch command event followed by executing the command object. if (cr.dispatchSimpleEvent(this, 'activate', true, true)) { var command = this.command; if (command) command.execute(); } } }, /** * Handles changes to the associated command. * @param {Event} e The event object. */ handleEvent: function(e) { switch (e.type) { case 'disabledChange': this.disabled = this.command.disabled; break; case 'hiddenChange': this.hidden = this.command.hidden; break; case 'labelChange': this.label = this.command.label; break; } } }; /** * Whether the menu item is disabled or not. * @type {boolean} */ cr.defineProperty(MenuItem, 'disabled', cr.PropertyKind.BOOL_ATTR);

/** * Whether the menu item is hidden or not. * @type {boolean} */ cr.defineProperty(MenuItem, 'hidden', cr.PropertyKind.BOOL_ATTR); /** * Whether the menu item is selected or not. * @type {boolean} */ cr.defineProperty(MenuItem, 'selected', cr.PropertyKind.BOOL_ATTR); // Export return { MenuItem: MenuItem }; }); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('cr.ui', function() { const MenuItem = cr.ui.MenuItem; /** * Creates a new menu element. * @param {Object=} opt_propertyBag Optional properties. * @constructor * @extends {HTMLMenuElement} */ var Menu = cr.ui.define('menu'); Menu.prototype = { __proto__: HTMLMenuElement.prototype, /** * Initializes the menu element. */ decorate: function() { this.addEventListener('mouseover', this.handleMouseOver_); this.addEventListener('mouseout', this.handleMouseOut_); // Decorate the children as menu items. var children = this.children; for (var i = 0, child; child = children[i]; i++) { cr.ui.decorate(child, MenuItem); } }, /** * Walks up the ancestors until a menu item belonging to this menu is found. * @param {Element} el * @return {cr.ui.MenuItem} The found menu item or null. * @private */ findMenuItem_: function(el) { while (el && el.parentNode != this) {

el = el.parentNode; } return el; }, /** * Handles mouseover events and selects the hovered item. * @param {Event} e The mouseover event. * @private */ handleMouseOver_: function(e) { var overItem = this.findMenuItem_(e.target); this.selectedItem = overItem; }, /** * Handles mouseout events and deselects any selected item. * @param {Event} e The mouseout event. * @private */ handleMouseOut_: function(e) { this.selectedItem = null; }, /** * The index of the selected item. * @type {boolean} */ // getter and default value is defined using cr.defineProperty. set selectedIndex(selectedIndex) { if (this.selectedIndex_ != selectedIndex) { var oldSelectedItem = this.selectedItem; this.selectedIndex_ = selectedIndex; if (oldSelectedItem) oldSelectedItem.selected = false; var item = this.selectedItem; if (item) item.selected = true; cr.dispatchSimpleEvent(this, 'change'); } }, /** * The selected menu item or null if none. * @type {cr.ui.MenuItem} */ get selectedItem() { return this.children[this.selectedIndex]; }, set selectedItem(item) { var index = Array.prototype.indexOf.call(this.children, item); this.selectedIndex = index; }, /** * This is the function that handles keyboard navigation. This is usually * called by the element responsible for managing the menu. * @param {Event} e The keydown event object. * @return {boolean} Whether the event was handled be the menu.

*/ handleKeyDown: function(e) { var item = this.selectedItem; var self = this; function selectNextVisible(m) { var children = self.children; var len = children.length; var i = self.selectedIndex; if (i == -1 && m == -1) { // Edge case when we need to go the last item fisrt. i = 0; } while (true) { i = (i + m + len) % len; item = children[i]; if (item && !item.isSeparator() && !item.hidden) break; } if (item) self.selectedIndex = i; } switch (e.keyIdentifier) { case 'Down': selectNextVisible(1); return true; case 'Up': selectNextVisible(-1); return true; case 'Enter': case 'U+0020': // Space if (item) { if (cr.dispatchSimpleEvent(item, 'activate', true, true)) { if (item.command) item.command.execute(); } } return true; } return false; } }; /** * The selected menu item. * @type {number} */ cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS, -1); // Export return { Menu: Menu }; }); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file.

/** * @fileoverview This file provides utility functions for position popups. */ cr.define('cr.ui', function() { /** * Type def for rects as returned by getBoundingClientRect. * @typedef { {left: number, top: number, width: number, height: number, * right: number, bottom: number}} */ var Rect; /** * Enum for defining how to anchor a popup to an anchor element. * @enum {number} */ const AnchorType = { /** * The popup's right edge is aligned with the left edge of the anchor. * The popup's top edge is aligned with the top edge of the anchor's top * edge. */ BEFORE: 1, // p: right, a: left, p: top, a: top /** * The popop's left edge is aligned with the right edge of the anchor. * The popup's top edge is aligned with the top edge of the anchor's top * edge. */ AFTER: 2, // p: left a: right, p: top, a: top /** * The popop's bottom edge is aligned with the top edge of the anchor. * The popup's left edge is aligned with the left edge of the anchor's top * edge. */ ABOVE: 3, // p: bottom, a: top, p: left, a: left /** * The popop's top edge is aligned with the bottom edge of the anchor. * The popup's left edge is aligned with the left edge of the anchor's top * edge. */ BELOW: 4 // p: top, a: bottom, p: left, a: left }; /** * Helper function for positionPopupAroundElement and positionPopupAroundRect. * @param {!Rect} anchorRect The rect for the anchor. * @param {!HTMLElement} popupElement The element used for the popup. * @param {AnchorType} type The type of anchoring to do. */ function positionPopupAroundRect(anchorRect, popupElement, type) { var popupRect = popupElement.getBoundingClientRect(); var popupContainer; var cs = popupElement.ownerDocument.defaultView. getComputedStyle(popupElement); if (cs.position == 'fixed')

popupContainer = popupElement.ownerDocument.body; else popupContainer = popupElement.offsetParent; var availRect = popupContainer.getBoundingClientRect(); var rtl = cs.direction == 'rtl'; // Flip BEFORE, AFTER based on RTL. if (rtl) { if (type == AnchorType.BEFORE) type = AnchorType.AFTER; else if (type == AnchorType.AFTER) type = AnchorType.BEFORE; } // Flip type based on available size switch (type) { case AnchorType.BELOW: if (anchorRect.bottom + popupRect.height > availRect.height && popupRect.height <= anchorRect.top) { type = AnchorType.ABOVE; } break; case AnchorType.ABOVE: if (popupRect.height > anchorRect.top && anchorRect.bottom + popupRect.height <= availRect.height) { type = AnchorType.BELOW; } break; case AnchorType.AFTER: if (anchorRect.right + popupRect.width > availRect.width && popupRect.width <= anchorRect.left) { type = AnchorType.BEFORE; } break; case AnchorType.BEFORE: if (popupRect.width > anchorRect.left && anchorRect.right + popupRect.width <= availRect.width) { type = AnchorType.AFTER; } break; } // flipping done var style = popupElement.style; // Reset all directions. style.left = style.right = style.top = style.bottom = 'auto' // Primary direction switch (type) { case AnchorType.BELOW: if (anchorRect.bottom + popupRect.height <= availRect.height) style.top = anchorRect.bottom + 'px'; else style.bottom = '0'; break; case AnchorType.ABOVE: if (availRect.height - anchorRect.top >= 0) style.bottom = availRect.height - anchorRect.top + 'px'; else style.top = '0';

break; case AnchorType.AFTER: if (anchorRect.right + popupRect.width <= availRect.width) style.left = anchorRect.right + 'px'; else style.right = '0'; break; case AnchorType.BEFORE: if (availRect.width - anchorRect.left >= 0) style.right = availRect.width - anchorRect.left + 'px'; else style.left = '0'; break; } // Secondary direction switch (type) { case AnchorType.BELOW: case AnchorType.ABOVE: if (rtl) { // align right edges if (anchorRect.right - popupRect.width >= 0) { style.right = availRect.width - anchorRect.right + 'px'; // align left edges } else if (anchorRect.left + popupRect.width <= availRect.width) { style.left = anchorRect.left + 'px'; // not enough room on either side } else { style.right = '0'; } } else { // align left edges if (anchorRect.left + popupRect.width <= availRect.width) { style.left = anchorRect.left + 'px'; // align right edges } else if (anchorRect.right - popupRect.width >= 0) { style.right = availRect.width - anchorRect.right + 'px'; // not enough room on either side } else { style.left = '0'; } } break; case case // if AnchorType.AFTER: AnchorType.BEFORE: align top edges (anchorRect.top + popupRect.height <= availRect.height) { style.top = anchorRect.top + 'px';

// align bottom edges } else if (anchorRect.bottom - popupRect.height >= 0) { style.bottom = availRect.height - anchorRect.bottom + 'px'; // not enough room on either side } else {

style.top = '0'; } break; } } /** * Positions a popup element relative to an anchor element. The popup element * should have position set to absolute and it should be a child of the body * element. * @param {!HTMLElement} anchorElement The element that the popup is anchored * to. * @param {!HTMLElement} popupElement The popup element we are positioning. * @param {AnchorType} type The type of anchoring we want. */ function positionPopupAroundElement(anchorElement, popupElement, type) { var anchorRect = anchorElement.getBoundingClientRect(); positionPopupAroundRect(anchorRect, popupElement, type); } /** * Positions a popup around a point. * @param {number} x The client x position. * @param {number} y The client y position. * @param {!HTMLElement} popupElement The popup element we are positioning. */ function positionPopupAtPoint(x, y, popupElement) { var rect = { left: x, top: y, width: 0, height: 0, right: x, bottom: y }; positionPopupAroundRect(rect, popupElement, AnchorType.BELOW); } // Export return { AnchorType: AnchorType, positionPopupAroundElement: positionPopupAroundElement, positionPopupAtPoint: positionPopupAtPoint }; }); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('cr.ui', function() { const Menu = cr.ui.Menu; const positionPopupAroundElement = cr.ui.positionPopupAroundElement; /** * Creates a new menu button element. * @param {Object=} opt_propertyBag Optional properties. * @constructor * @extends {HTMLButtonElement} */

var MenuButton = cr.ui.define('button'); MenuButton.prototype = { __proto__: HTMLButtonElement.prototype, /** * Initializes the menu button. */ decorate: function() { this.addEventListener('mousedown', this); this.addEventListener('keydown', this); var menu; if ((menu = this.getAttribute('menu'))) this.menu = menu; }, /** * The menu associated with the menu button. * @type {cr.ui.Menu} */ get menu() { return this.menu_; }, set menu(menu) { if (typeof menu == 'string' && menu[0] == '#') { menu = this.ownerDocument.getElementById(menu.slice(1)); cr.ui.decorate(menu, Menu); } this.menu_ = menu; if (menu) { if (menu.id) this.setAttribute('menu', '#' + menu.id); } }, /** * Handles event callbacks. * @param {Event} e The event object. */ handleEvent: function(e) { if (!this.menu) return; switch (e.type) { case 'mousedown': if (e.currentTarget == this.ownerDocument) { if (!this.contains(e.target) && !this.menu.contains(e.target)) this.hideMenu(); else e.preventDefault(); } else { if (this.isMenuShown()) { this.hideMenu(); } else if (e.button == 0) { // Only show the menu when using left // mouse button. this.showMenu(); // Prevent the button from stealing focus on mousedown. e.preventDefault();

} } break; case 'keydown': this.handleKeyDown(e); // If the menu is visible we let it handle all the keyboard events. if (this.isMenuShown() && e.currentTarget == this.ownerDocument) { this.menu.handleKeyDown(e); e.preventDefault(); e.stopPropagation(); } break; case 'activate': case 'blur': case 'resize': this.hideMenu(); break; } }, /** * Shows the menu. */ showMenu: function() { this.menu.style.display = 'block'; this.setAttribute('menu-shown', ''); // when the menu is shown we steal all keyboard events. var doc = this.ownerDocument; var win = doc.defaultView; doc.addEventListener('keydown', this, true); doc.addEventListener('mousedown', this, true); doc.addEventListener('blur', this, true); win.addEventListener('resize', this); this.menu.addEventListener('activate', this); this.positionMenu_(); }, /** * Hides the menu. */ hideMenu: function() { this.removeAttribute('menu-shown'); this.menu.style.display = 'none'; var doc = this.ownerDocument; var win = doc.defaultView; doc.removeEventListener('keydown', this, true); doc.removeEventListener('mousedown', this, true); doc.removeEventListener('blur', this, true); win.removeEventListener('resize', this); this.menu.removeEventListener('activate', this); this.menu.selectedIndex = -1; }, /** * Whether the menu is shown. */ isMenuShown: function() { return window.getComputedStyle(this.menu).display != 'none';

}, /** * Positions the menu below the menu button. At this point we do not use any * advanced positioning logic to ensure the menu fits in the viewport. * @private */ positionMenu_: function() { positionPopupAroundElement(this, this.menu, cr.ui.AnchorType.BELOW); }, /** * Handles the keydown event for the menu button. */ handleKeyDown: function(e) { switch (e.keyIdentifier) { case 'Down': case 'Up': case 'Enter': case 'U+0020': // Space if (!this.isMenuShown()) this.showMenu(); e.preventDefault(); break; case 'Esc': case 'U+001B': // Maybe this is remote desktop playing a prank? this.hideMenu(); break; } } }; // Export return { MenuButton: MenuButton }; }); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview This implements a special button that is useful for showing a * context menu. */ cr.define('cr.ui', function() { const MenuButton = cr.ui.MenuButton; /** * Helper function for ContextMenuButton to find the first ancestor of the * button that has a context menu. * @param {!MenuButton} el The button to start the search from. * @return {HTMLElement} The found element or null if not found. */ function getContextMenuTarget(el) { do { el = el.parentNode; } while (el && !('contextMenu' in el));

return el; } /** * Creates a new menu button which is used to show the context menu for an * ancestor that has a {@code contextMenu} property. * @param {Object=} opt_propertyBag Optional properties. * @constructor * @extends {MenuButton} */ var ContextMenuButton = cr.ui.define('button'); ContextMenuButton.prototype = { __proto__: MenuButton.prototype, /** * Override to return the contextMenu for the ancestor. * @override * @type {cr.ui.Menu} */ get menu() { var target = getContextMenuTarget(this); return target && target.contextMenu; }, /** @inheritDoc */ decorate: function() { this.tabIndex = -1; this.addEventListener('mouseup', this); MenuButton.prototype.decorate.call(this); }, /** @inheritDoc */ handleEvent: function(e) { switch (e.type) { case 'mousedown': // Menu buttons prevent focus changes. var target = getContextMenuTarget(this); if (target) target.focus(); break; case 'mouseup': // Stop mouseup to prevent selection changes. e.stopPropagation(); break; } MenuButton.prototype.handleEvent.call(this, e); } }; // Export return { ContextMenuButton: ContextMenuButton }; }); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file.

cr.define('cr.ui', function() { const positionPopupAtPoint = cr.ui.positionPopupAtPoint; const Menu = cr.ui.Menu; /** * Handles context menus. * @constructor */ function ContextMenuHandler() {} ContextMenuHandler.prototype = { /** * The menu that we are currently showing. * @type {cr.ui.Menu} */ menu_: null, get menu() { return this.menu_; }, /** * Shows a menu as a context menu. * @param {!Event} e The event triggering the show (usally a contextmenu * event). * @param {!cr.ui.Menu} menu The menu to show. */ showMenu: function(e, menu) { this.menu_ = menu; menu.style.display = 'block'; // when the menu is shown we steal all keyboard events. var doc = menu.ownerDocument; doc.addEventListener('keydown', this, true); doc.addEventListener('mousedown', this, true); doc.addEventListener('blur', this, true); doc.defaultView.addEventListener('resize', this); menu.addEventListener('contextmenu', this); menu.addEventListener('activate', this); this.positionMenu_(e, menu); }, /** * Hide the currently shown menu. */ hideMenu: function() { var menu = this.menu; if (!menu) return; menu.style.display = 'none'; var doc = menu.ownerDocument; doc.removeEventListener('keydown', this, true); doc.removeEventListener('mousedown', this, true); doc.removeEventListener('blur', this, true); doc.defaultView.removeEventListener('resize', this); menu.removeEventListener('contextmenu', this); menu.removeEventListener('activate', this); menu.selectedIndex = -1;

this.menu_ = null; // On windows we might hide the menu in a right mouse button up and if // that is the case we wait some short period before we allow the menu // to be shown again. this.hideTimestamp_ = cr.isWindows ? Date.now() : 0; }, /** * Positions the menu * @param {!Event} e The event object triggering the showing. * @param {!cr.ui.Menu} menu The menu to position. * @private */ positionMenu_: function(e, menu) { // TODO(arv): Handle scrolled documents when needed. var element = e.currentTarget; var x, y; // When the user presses the context menu key (on the keyboard) we need // to detect this. if (this.keyIsDown_) { var rect = element.getRectForContextMenu ? element.getRectForContextMenu() : element.getBoundingClientRect(); var offset = Math.min(rect.width, rect.height) / 2; x = rect.left + offset; y = rect.top + offset; } else { x = e.clientX; y = e.clientY; } positionPopupAtPoint(x, y, menu); }, /** * Handles event callbacks. * @param {!Event} e The event object. */ handleEvent: function(e) { // Keep track of keydown state so that we can use that to determine the // reason for the contextmenu event. switch (e.type) { case 'keydown': this.keyIsDown_ = !e.ctrlKey && !e.altKey && // context menu key or Shift-F10 (e.keyCode == 93 && !e.shiftKey e.keyIdentifier == 'F10' && e.shiftKey); break; case 'keyup': this.keyIsDown_ = false; break; } // Context menu is handled even when we have no menu. if (e.type != 'contextmenu' && !this.menu) return;

switch (e.type) { case 'mousedown': if (!this.menu.contains(e.target)) this.hideMenu(); else e.preventDefault(); break; case 'keydown': // keyIdentifier does not report 'Esc' correctly if (e.keyCode == 27 /* Esc */) { this.hideMenu(); // If the menu is visible we let it handle all the keyboard events. } else if (this.menu) { this.menu.handleKeyDown(e); e.preventDefault(); e.stopPropagation(); } break; case 'activate': case 'blur': case 'resize': this.hideMenu(); break; case 'contextmenu': if ((!this.menu !this.menu.contains(e.target)) && (!this.hideTimestamp_ Date.now() - this.hideTimestamp_ > 50)) this.showMenu(e, e.currentTarget.contextMenu); e.preventDefault(); // Don't allow elements further up in the DOM to show their menus. e.stopPropagation(); break; } }, /** * Adds a contextMenu property to an element or element class. * @param {!Element !Function} element The element or class to add the * contextMenu property to. */ addContextMenuProperty: function(element) { if (typeof element == 'function') element = element.prototype; element.__defineGetter__('contextMenu', function() { return this.contextMenu_; }); element.__defineSetter__('contextMenu', function(menu) { var oldContextMenu = this.contextMenu; if (typeof menu == 'string' && menu[0] == '#') { menu = this.ownerDocument.getElementById(menu.slice(1)); cr.ui.decorate(menu, Menu); } if (menu === oldContextMenu) return;

if (oldContextMenu && !menu) { this.removeEventListener('contextmenu', contextMenuHandler); this.removeEventListener('keydown', contextMenuHandler); this.removeEventListener('keyup', contextMenuHandler); } if (menu && !oldContextMenu) { this.addEventListener('contextmenu', contextMenuHandler); this.addEventListener('keydown', contextMenuHandler); this.addEventListener('keyup', contextMenuHandler); } this.contextMenu_ = menu; if (menu && menu.id) this.setAttribute('contextmenu', '#' + menu.id); cr.dispatchPropertyChange(this, 'contextMenu', menu, oldContextMenu); }); if (!element.getRectForContextMenu) { /** * @return {!ClientRect} The rect to use for positioning the context * menu when the context menu is not opened using a mouse position. */ element.getRectForContextMenu = function() { return this.getBoundingClientRect(); }; } } }; /** * The singleton context menu handler. * @type {!ContextMenuHandler} */ var contextMenuHandler = new ContextMenuHandler; // Export return { contextMenuHandler: contextMenuHandler }; }); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(arv): Move to shared/js once namespaced and tested. var global = this; const IS_MAC = /^Mac/.test(navigator.platform); function $(id) { return document.getElementById(id); } function bind(fn, selfObj, var_args) { var boundArgs = Array.prototype.slice.call(arguments, 2); return function() {

var args = Array.prototype.slice.call(arguments); args.unshift.apply(args, boundArgs); return fn.apply(selfObj, args); } } function url(s) { // http://www.w3.org/TR/css3-values/#uris // Parentheses, commas, whitespace characters, single quotes (') and double // quotes (") appearing in a URI must be escaped with a backslash var s2 = s.replace(/(\( \) \, \s \' \" \\)/g, '\\$1'); // WebKit has a bug when it comes to URLs that end with \ // https://bugs.webkit.org/show_bug.cgi?id=28885 if (/\\\\$/.test(s2)) { // Add a space to work around the WebKit bug. s2 += ' '; } return 'url("' + s2 + '")'; } function findAncestorByClass(el, className) { return findAncestor(el, function(el) { if (el.classList) return el.classList.contains(className); return null; }); } /** * Return the first ancestor for which the {@code predicate} returns true. * @param {Node} node The node to check. * @param {function(Node) : boolean} predicate The function that tests the * nodes. * @return {Node} The found ancestor or null if not found. */ function findAncestor(node, predicate) { var last = false; while (node != null && !(last = predicate(node))) { node = node.parentNode; } return last ? node : null; } function swapDomNodes(a, b) { var afterA = a.nextSibling; if (afterA == b) { swapDomNodes(b, a); return; } var aParent = a.parentNode; b.parentNode.replaceChild(a, b); aParent.insertBefore(b, afterA); } /** * Calls chrome.send with a callback and restores the original afterwards. */ function chromeSend(name, params, callbackName, callback) { var old = global[callbackName]; global[callbackName] = function() {

// restore global[callbackName] = old; var args = Array.prototype.slice.call(arguments); return callback.apply(global, args); }; chrome.send(name, params); } </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // // // // // // // // // // Dependencies that we should remove/formalize: ../shared/js/class_list.js util.js afterTransition chrome.send hideNotification isRtl localStrings logEvent showNotification

var MostVisited = (function() { function addPinnedUrl(item, index) { chrome.send('addPinnedURL', [item.url, item.title, item.faviconUrl '', item.thumbnailUrl '', String(index)]); } function getItem(el) { return findAncestorByClass(el, 'thumbnail-container'); } function updatePinnedDom(el, pinned) { el.querySelector('.pin').title = localStrings.getString(pinned ? 'unpinthumbnailtooltip' : 'pinthumbnailtooltip'); if (pinned) { el.classList.add('pinned'); } else { el.classList.remove('pinned'); } } function getThumbnailIndex(el) { var nodes = el.parentNode.querySelectorAll('.thumbnail-container'); return Array.prototype.indexOf.call(nodes, el); } function MostVisited(el, miniview, menu, useSmallGrid, visible) { this.element = el; this.miniview = miniview; this.menu = menu; this.useSmallGrid_ = useSmallGrid; this.visible_ = visible;

this.createThumbnails_(); this.applyMostVisitedRects_(); el.addEventListener('click', bind(this.handleClick_, this)); el.addEventListener('keydown', bind(this.handleKeyDown_, this)); document.addEventListener('DOMContentLoaded', bind(this.ensureSmallGridCorrect, this)); // Commands document.addEventListener('command', bind(this.handleCommand_, this)); document.addEventListener('canExecute', bind(this.handleCanExecute_, this)); // DND el.addEventListener('dragstart', bind(this.handleDragStart_, this)); el.addEventListener('dragenter', bind(this.handleDragEnter_, this)); el.addEventListener('dragover', bind(this.handleDragOver_, this)); el.addEventListener('dragleave', bind(this.handleDragLeave_, this)); el.addEventListener('drop', bind(this.handleDrop_, this)); el.addEventListener('dragend', bind(this.handleDragEnd_, this)); el.addEventListener('drag', bind(this.handleDrag_, this)); el.addEventListener('mousedown', bind(this.handleMouseDown_, this)); } MostVisited.prototype = { togglePinned_: function(el) { var index = getThumbnailIndex(el); var item = this.data[index]; item.pinned = !item.pinned; if (item.pinned) { addPinnedUrl(item, index); } else { chrome.send('removePinnedURL', [item.url]); } updatePinnedDom(el, item.pinned); }, swapPosition_: function(source, destination) { var nodes = source.parentNode.querySelectorAll('.thumbnail-container'); var sourceIndex = getThumbnailIndex(source); var destinationIndex = getThumbnailIndex(destination); swapDomNodes(source, destination); var sourceData = this.data[sourceIndex]; addPinnedUrl(sourceData, destinationIndex); sourceData.pinned = true; updatePinnedDom(source, true); var destinationData = this.data[destinationIndex]; // Only update the destination if it was pinned before. if (destinationData.pinned) { addPinnedUrl(destinationData, sourceIndex); } this.data[destinationIndex] = sourceData; this.data[sourceIndex] = destinationData; }, updateSettingsLink: function(hasBlacklistedUrls) { if (hasBlacklistedUrls)

$('most-visited-settings').classList.add('has-blacklist'); else $('most-visited-settings').classList.remove('has-blacklist'); }, blacklist: function(el) { var self = this; var url = el.href; chrome.send('blacklistURLFromMostVisited', [url]); el.classList.add('hide'); // Find the old item. var oldUrls = {}; var oldIndex = -1; var oldItem; var data = this.data; for (var i = 0; i < data.length; i++) { if (data[i].url == url) { oldItem = data[i]; oldIndex = i; } oldUrls[data[i].url] = true; } // Send 'getMostVisitedPages' with a callback since we want to find the // new page and add that in the place of the removed page. chromeSend('getMostVisited', [], 'mostVisitedPages', function(data, firstRun, hasBlacklistedUrls) { // Update settings link. self.updateSettingsLink(hasBlacklistedUrls); // Find new item. var newItem; for (var i = 0; i < data.length; i++) { if (!(data[i].url in oldUrls)) { newItem = data[i]; break; } } if (!newItem) { // If no other page is available to replace the blacklisted item, // we need to reorder items s.t. all filler items are in the rightmost // indices. self.data = data; // Replace old item with new item in the most visited data array. } else if (oldIndex != -1) { var oldData = self.data.concat(); oldData.splice(oldIndex, 1, newItem); self.data = oldData; el.classList.add('fade-in'); } // We wrap the title in a <span class=blacklisted-title>. We pass an // empty string to the notifier function and use DOM to insert the real // string. var actionText = localStrings.getString('undothumbnailremove');

// Show notification and add undo callback function. var wasPinned = oldItem.pinned; showNotification('', actionText, function() { self.removeFromBlackList(url); if (wasPinned) { addPinnedUrl(oldItem, oldIndex); } chrome.send('getMostVisited'); }); // Now change the DOM. var removeText = localStrings.getString('thumbnailremovednotification'); var notifySpan = document.querySelector('#notification > span'); notifySpan.textContent = removeText; // Focus the undo link. var undoLink = document.querySelector( '#notification > .link > [tabindex]'); undoLink.focus(); }); }, removeFromBlackList: function(url) { chrome.send('removeURLsFromMostVisitedBlacklist', [url]); }, clearAllBlacklisted: function() { chrome.send('clearMostVisitedURLsBlacklist', []); hideNotification(); }, dirty_: false, invalidate_: function() { this.dirty_ = true; }, visible_: true, get visible() { return this.visible_; }, set visible(visible) { if (this.visible_ != visible) { this.visible_ = visible; this.invalidate_(); } }, useSmallGrid_: false, get useSmallGrid() { return this.useSmallGrid_; }, set useSmallGrid(b) { if (this.useSmallGrid_ != b) { this.useSmallGrid_ = b; this.invalidate_(); } }, layout: function() { if (!this.dirty_)

return; var d0 = Date.now(); this.applyMostVisitedRects_(); this.dirty_ = false; logEvent('mostVisited.layout: ' + (Date.now() - d0)); }, createThumbnails_: function() { var singleHtml = '<a class="thumbnail-container filler" tabindex="1">' + '<div class="edit-mode-border">' + '<div class="edit-bar">' + '<div class="pin"></div>' + '<div class="spacer"></div>' + '<div class="remove"></div>' + '</div>' + '<span class="thumbnail-wrapper">' + '<span class="thumbnail"></span>' + '</span>' + '</div>' + '<div class="title">' + '<div></div>' + '</div>' + '</a>'; this.element.innerHTML = Array(8 + 1).join(singleHtml); var children = this.element.children; for (var i = 0; i < 8; i++) { children[i].id = 't' + i; } }, getMostVisitedLayoutRects_: function() { var small = this.useSmallGrid; var var var var var var var var var var var cols = 4; rows = 2; marginWidth = 10; marginHeight = 7; borderWidth = 4; thumbWidth = small ? 150 : 207; thumbHeight = small ? 93 : 129; w = thumbWidth + 2 * borderWidth + 2 * marginWidth; h = thumbHeight + 40 + 2 * marginHeight; sumWidth = cols * w - 2 * marginWidth; topSpacing = 10;

var rtl = isRtl(); var rects = []; if (this.visible) { for (var i = 0; i < rows * cols; i++) { var row = Math.floor(i / cols); var col = i % cols; var left = rtl ? sumWidth - col * w - thumbWidth - 2 * borderWidth : col * w; var top = row * h + topSpacing; rects[i] = {left: left, top: top}; }

} return rects; }, applyMostVisitedRects_: function() { if (this.visible) { var rects = this.getMostVisitedLayoutRects_(); var children = this.element.children; for (var i = 0; i < 8; i++) { var t = children[i]; t.style.left = rects[i].left + 'px'; t.style.top = rects[i].top + 'px'; t.style.right = ''; var innerStyle = t.firstElementChild.style; innerStyle.left = innerStyle.top = ''; } } }, // Work around for http://crbug.com/25329 ensureSmallGridCorrect: function(expected) { if (expected != this.useSmallGrid) this.applyMostVisitedRects_(); }, getRectByIndex_: function(index) { return this.getMostVisitedLayoutRects_()[index]; }, // Commands handleCommand_: function(e) { var commandId = e.command.id; switch (commandId) { case 'clear-all-blacklisted': this.clearAllBlacklisted(); chrome.send('getMostVisited'); break; } }, handleCanExecute_: function(e) { if (e.command.id == 'clear-all-blacklisted') e.canExecute = true; }, // DND currentOverItem_: null, get currentOverItem() { return this.currentOverItem_; }, set currentOverItem(item) { var style; if (item != this.currentOverItem_) { if (this.currentOverItem_) { style = this.currentOverItem_.firstElementChild.style; style.left = style.top = ''; } this.currentOverItem_ = item;

if (item) { // Make the drag over item move 15px towards the source. The movement // is done by only moving the edit-mode-border (as in the mocks) and // it is done with relative positioning so that the movement does not // change the drop target. var dragIndex = getThumbnailIndex(this.dragItem_); var overIndex = getThumbnailIndex(item); if (dragIndex == -1 overIndex == -1) { return; } var dragRect = this.getRectByIndex_(dragIndex); var overRect = this.getRectByIndex_(overIndex); var var var var var var x = dragRect.left - overRect.left; y = dragRect.top - overRect.top; z = Math.sqrt(x * x + y * y); z2 = 15; x2 = x * z2 / z; y2 = y * z2 / z;

style = this.currentOverItem_.firstElementChild.style; style.left = x2 + 'px'; style.top = y2 + 'px'; } } }, dragItem_: null, startX_: 0, startY_: 0, startScreenX_: 0, startScreenY_: 0, dragEndTimer_: null, isDragging: function() { return !!this.dragItem_; }, handleDragStart_: function(e) { var thumbnail = getItem(e.target); if (thumbnail) { // Don't set data since HTML5 does not allow setting the name for // url-list. Instead, we just rely on the dragging of link behavior. this.dragItem_ = thumbnail; this.dragItem_.classList.add('dragging'); this.dragItem_.style.zIndex = 2; e.dataTransfer.effectAllowed = 'copyLinkMove'; } }, handleDragEnter_: function(e) { if (this.canDropOnElement_(this.currentOverItem)) { e.preventDefault(); } }, handleDragOver_: function(e) { var item = getItem(e.target); this.currentOverItem = item;

if (this.canDropOnElement_(item)) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } }, handleDragLeave_: function(e) { var item = getItem(e.target); if (item) { e.preventDefault(); } this.currentOverItem = null; }, handleDrop_: function(e) { var dropTarget = getItem(e.target); if (this.canDropOnElement_(dropTarget)) { dropTarget.style.zIndex = 1; this.swapPosition_(this.dragItem_, dropTarget); // The timeout below is to allow WebKit to see that we turned off // pointer-event before moving the thumbnails so that we can get out of // hover mode. window.setTimeout(bind(function() { this.invalidate_(); this.layout(); }, this), 10); e.preventDefault(); if (this.dragEndTimer_) { window.clearTimeout(this.dragEndTimer_); this.dragEndTimer_ = null; } afterTransition(function() { dropTarget.style.zIndex = ''; }); } }, handleDragEnd_: function(e) { var dragItem = this.dragItem_; if (dragItem) { dragItem.style.pointerEvents = ''; dragItem.classList.remove('dragging'); afterTransition(function() { // Delay resetting zIndex to let the animation finish. dragItem.style.zIndex = ''; // Same for overflow. dragItem.parentNode.style.overflow = ''; }); this.invalidate_(); this.layout(); this.dragItem_ = null; } }, handleDrag_: function(e) { // Moves the drag item making sure that it is not displayed outside the // browser viewport.

var item = getItem(e.target); var rect = this.element.getBoundingClientRect(); item.style.pointerEvents = 'none'; var x = this.startX_ + e.screenX - this.startScreenX_; var y = this.startY_ + e.screenY - this.startScreenY_; // The position of the item is relative to #most-visited so we need to // subtract that when calculating the allowed position. x = Math.max(x, -rect.left); x = Math.min(x, document.body.clientWidth - rect.left - item.offsetWidth 2); // The shadow is 2px y = Math.max(-rect.top, y); y = Math.min(y, document.body.clientHeight - rect.top item.offsetHeight - 2); // Override right in case of RTL. item.style.right = 'auto'; item.style.left = x + 'px'; item.style.top = y + 'px'; item.style.zIndex = 2; }, // We listen to mousedown to get the relative position of the cursor for // dnd. handleMouseDown_: function(e) { var item = getItem(e.target); if (item) { this.startX_ = item.offsetLeft; this.startY_ = item.offsetTop; this.startScreenX_ = e.screenX; this.startScreenY_ = e.screenY; // // // // if } }, canDropOnElement_: function(el) { return this.dragItem_ && el && el.classList.contains('thumbnail-container') && !el.classList.contains('filler'); }, /// data data_: null, get data() { return this.data_; }, set data(data) { // We append the class name with the "filler" so that we can style fillers // differently. var maxItems = 8; We don't want to focus the item on mousedown. However, to prevent focus one has to call preventDefault but this also prevents the drag and drop (sigh) so we only prevent it when the user is not doing a left mouse button drag. (e.button != 0) // LEFT e.preventDefault();

data.length = Math.min(maxItems, data.length); var len = data.length; for (var i = len; i < maxItems; i++) { data[i] = {filler: true}; } // On setting we need to update the items this.data_ = data; this.updateMostVisited_(); this.updateMiniview_(); this.updateMenu_(); }, updateMostVisited_: function() { function getThumbnailClassName(item) { return 'thumbnail-container' + (item.pinned ? ' pinned' : '') + (item.filler ? ' filler' : ''); } var data = this.data; var children = this.element.children; for (var i = 0; i < data.length; i++) { var d = data[i]; var t = children[i]; // If we have a filler continue var oldClassName = t.className; var newClassName = getThumbnailClassName(d); if (oldClassName != newClassName) { t.className = newClassName; } // No need to continue if this is a filler. if (newClassName == 'thumbnail-container filler') { // Make sure the user cannot tab to the filler. t.tabIndex = -1; t.querySelector('.thumbnail-wrapper').style.backgroundImage = ''; continue; } // Allow focus. t.tabIndex = 1; t.href = d.url; t.querySelector('.pin').title = localStrings.getString(d.pinned ? 'unpinthumbnailtooltip' : 'pinthumbnailtooltip'); t.querySelector('.remove').title = localStrings.getString('removethumbnailtooltip'); // There was some concern that a malformed malicious URL could cause an // XSS attack but setting style.backgroundImage = 'url(javascript:...)' // does not execute the JavaScript in WebKit. var thumbnailUrl = d.thumbnailUrl 'chrome://thumb/' + d.url; t.querySelector('.thumbnail-wrapper').style.backgroundImage = url(thumbnailUrl); var titleDiv = t.querySelector('.title > div'); titleDiv.xtitle = titleDiv.textContent = d.title; var faviconUrl = d.faviconUrl 'chrome://favicon/' + d.url;

titleDiv.style.backgroundImage = url(faviconUrl); titleDiv.dir = d.direction; } }, updateMiniview_: function() { this.miniview.textContent = ''; var data = this.data.slice(0, MAX_MINIVIEW_ITEMS); for (var i = 0, item; item = data[i]; i++) { if (item.filler) { continue; } var span = document.createElement('span'); var a = span.appendChild(document.createElement('a')); a.href = item.url; a.textContent = item.title; a.style.backgroundImage = url('chrome://favicon/' + item.url); a.className = 'item'; this.miniview.appendChild(span); } updateMiniviewClipping(this.miniview); }, updateMenu_: function() { clearClosedMenu(this.menu); var data = this.data.slice(0, MAX_MINIVIEW_ITEMS); for (var i = 0, item; item = data[i]; i++) { if (!item.filler) { addClosedMenuEntry( this.menu, item.url, item.title, 'chrome://favicon/' + item.url); } } addClosedMenuFooter( this.menu, 'most-visited', MINIMIZED_THUMB, Section.THUMB); }, handleClick_: function(e) { var target = e.target; if (target.classList.contains('pin')) { this.togglePinned_(getItem(target)); e.preventDefault(); } else if (target.classList.contains('remove')) { this.blacklist(getItem(target)); e.preventDefault(); } else { var item = getItem(target); if (item) { var index = Array.prototype.indexOf.call(item.parentNode.children, item); if (index != -1) chrome.send('metrics', ['NTP_MostVisited' + index]); } } }, /** * Allow blacklisting most visited site using the keyboard. */ handleKeyDown_: function(e) {

if (!IS_MAC && e.keyCode == 46 // Del IS_MAC && e.metaKey && e.keyCode == 8) { // Cmd + Backspace this.blacklist(e.target); } } }; return MostVisited; })(); </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // To avoid creating tons of unnecessary nodes. We assume we cannot fit more // than this many items in the miniview. var MAX_MINIVIEW_ITEMS = 15; // Extra spacing at the top of the layout. var LAYOUT_SPACING_TOP = 25; function getSectionCloseButton(sectionId) { return document.querySelector('#' + sectionId + ' .section-close-button'); } function getSectionMenuButton(sectionId) { return $(sectionId + '-button'); } function getSectionMenuButtonTextId(sectionId) { return sectionId.replace(/-/g, ''); } function setSectionVisible(sectionId, section, visible, hideMask) { if (visible && !(shownSections & hideMask) !visible && (shownSections & hideMask)) return; if (visible) { // Because sections are collapsed when they are minimized, it is not // necessary to restore the maxiview here. It will happen if the section // header is clicked. var el = $(sectionId); el.classList.remove('disabled'); el = getSectionMenuButton(sectionId); el.classList.add('disabled'); shownSections &= ~hideMask; } else { if (section) { hideSection(section); // To hide the maxiview. } var el = $(sectionId); el.classList.add('disabled'); el = getSectionMenuButton(sectionId); el.classList.remove('disabled'); shownSections = hideMask; } layoutSections(); }

function clearClosedMenu(menu) { menu.innerHTML = ''; } function addClosedMenuEntryWithLink(menu, a) { var span = document.createElement('span'); a.className += ' item menuitem'; span.appendChild(a); menu.appendChild(span); } function addClosedMenuEntry(menu, url, title, imageUrl) { var a = document.createElement('a'); a.href = url; a.textContent = title; a.style.backgroundImage = 'url(' + imageUrl + ')'; addClosedMenuEntryWithLink(menu, a); } function addClosedMenuFooter(menu, sectionId, mask, opt_section) { menu.appendChild(document.createElement('hr')); var span = document.createElement('span'); var a = span.appendChild(document.createElement('a')); a.href = ''; a.textContent = localStrings.getString(getSectionMenuButtonTextId(sectionId)); a.className = 'item'; a.addEventListener( 'click', function(e) { getSectionMenuButton(sectionId).hideMenu(); e.preventDefault(); setSectionVisible(sectionId, opt_section, true, mask); shownSections &= ~mask; saveShownSections(); }); menu.appendChild(span); } function initializeSection(sectionId, mask, opt_section) { var button = getSectionCloseButton(sectionId); button.addEventListener( 'click', function() { setSectionVisible(sectionId, opt_section, false, mask); saveShownSections(); }); } function updateSimpleSection(id, section) { var elm = $(id); var maxiview = getSectionMaxiview(elm); if (shownSections & section) { $(id).classList.remove('hidden'); if (maxiview) maxiview.classList.remove('hidden'); } else { $(id).classList.add('hidden'); if (maxiview)

maxiview.classList.add('hidden'); } } function recentlyClosedTabs(data) { logEvent('received recently closed tabs'); // We need to store the recent items so we can update the layout on a resize. recentItems = data; renderRecentlyClosed(); layoutSections(); } var recentItems = []; function renderRecentlyClosed() { // Remove all existing items and create new items. var recentElement = $('recently-closed'); var parentEl = recentElement.lastElementChild; parentEl.textContent = ''; var recentMenu = $('recently-closed-menu'); clearClosedMenu(recentMenu); recentItems.forEach(function(item) { parentEl.appendChild(createRecentItem(item)); addRecentMenuItem(recentMenu, item); }); addClosedMenuFooter(recentMenu, 'recently-closed', MINIMIZED_RECENT); layoutRecentlyClosed(); } function createRecentItem(data) { var isWindow = data.type == 'window'; var el; if (isWindow) { el = document.createElement('span'); el.className = 'item link window'; el.tabItems = data.tabs; el.tabIndex = 0; el.textContent = formatTabsText(data.tabs.length); } else { el = document.createElement('a'); el.className = 'item'; el.href = data.url; el.style.backgroundImage = url('chrome://favicon/' + data.url); el.dir = data.direction; el.textContent = data.title; } el.sessionId = data.sessionId; el.xtitle = data.title; var wrapperEl = document.createElement('span'); wrapperEl.appendChild(el); return wrapperEl; } function addRecentMenuItem(menu, data) { var isWindow = data.type == 'window'; var a = document.createElement('a'); if (isWindow) { a.textContent = formatTabsText(data.tabs.length);

a.className = 'window'; // To get the icon from the CSS .window rule. a.href = ''; // To make underline show up. } else { a.href = data.url; a.style.backgroundImage = 'url(chrome://favicon/' + data.url + ')'; a.textContent = data.title; } function clickHandler(e) { chrome.send('reopenTab', [String(data.sessionId)]); e.preventDefault(); } a.addEventListener('click', clickHandler); addClosedMenuEntryWithLink(menu, a); } function saveShownSections() { chrome.send('setShownSections', [String(shownSections)]); } var LayoutMode = { SMALL: 1, NORMAL: 2 }; var layoutMode = useSmallGrid() ? LayoutMode.SMALL : LayoutMode.NORMAL; function handleWindowResize() { if (window.innerWidth < 10) { // We're probably a background tab, so don't do anything. return; } var oldLayoutMode = layoutMode; var b = useSmallGrid(); layoutMode = b ? LayoutMode.SMALL : LayoutMode.NORMAL if (layoutMode != oldLayoutMode){ mostVisited.useSmallGrid = b; mostVisited.layout(); renderRecentlyClosed(); updateAllMiniviewClippings(); } layoutSections(); } // Stores some information about each section necessary to layout. A new // instance is constructed for each section on each layout. function SectionLayoutInfo(section) { this.section = section; this.header = section.getElementsByTagName('h2')[0]; this.miniview = section.getElementsByClassName('miniview')[0]; this.maxiview = getSectionMaxiview(section); this.expanded = this.maxiview && !section.classList.contains('hidden'); this.fixedHeight = this.section.offsetHeight; this.scrollingHeight = 0; if (this.expanded) this.scrollingHeight = this.maxiview.offsetHeight; }

// Get all sections to be layed out. SectionLayoutInfo.getAll = function() { var sections = document.querySelectorAll('.section:not(.disabled)'); var result = []; for (var i = 0, section; section = sections[i]; i++) { result.push(new SectionLayoutInfo(section)); } return result; }; // Ensure the miniview sections don't have any clipped items. function updateMiniviewClipping(miniview) { var clipped = false; for (var j = 0, item; item = miniview.children[j]; j++) { item.style.display = ''; if (clipped (item.offsetLeft + item.offsetWidth) > miniview.offsetWidth) { item.style.display = 'none'; clipped = true; } else { item.style.display = ''; } } } // Ensure none of the miniviews have any clipped items. function updateAllMiniviewClippings() { var miniviews = document.querySelectorAll('.section.hidden .miniview'); for (var i = 0, miniview; miniview = miniviews[i]; i++) { updateMiniviewClipping(miniview); } } // // // // // // // // // // // // // // // // // // // // // // // // // // Layout the sections in a modified accordian. The header and miniview, if visible are fixed within the viewport. If there is an expanded section, its it scrolls. ============================= collapsed section <- Any collapsed sections are fixed position. and miniview --------------------------expanded section <- There can be one expanded section and it and maxiview is absolutely positioned so that it can scroll "underneath" the fixed elements. --------------------------another collapsed section --------------------------We want the main frame scrollbar to be the one that scrolls the expanded region. To get this effect, we make the fixed elements position:fixed and the scrollable element position:absolute. We also artificially increase the height of the document so that it is possible to scroll down enough to display the end of the document, even with any fixed elements at the bottom of the viewport. There is a final twist: If the intrinsic height of the expanded section is less than the available height (because the window is tall), any collapsed

// sections sinch up and sit below the expanded section. This is so that we // don't have a bunch of dead whitespace in the case of expanded sections that // aren't very tall. function layoutSections() { var sections = SectionLayoutInfo.getAll(); var expandedSection = null; var headerHeight = LAYOUT_SPACING_TOP; var footerHeight = 0; // Calculate the height of the fixed elements above the expanded section. Also // take note of the expanded section, if there is one. var i; var section; for (i = 0; section = sections[i]; i++) { headerHeight += section.fixedHeight; if (section.expanded) { expandedSection = section; i++; break; } } // Calculate the height of the fixed elements below the expanded section, if // any. for (; section = sections[i]; i++) { footerHeight += section.fixedHeight; } // Leave room for bottom bar if it's visible. footerHeight += $('closed-sections-bar').offsetHeight; // Determine the height to use for the expanded section. If there isn't enough // space to show the expanded section completely, this will be the available // height. Otherwise, we use the intrinsic height of the expanded section. var expandedSectionHeight; if (expandedSection) { var flexHeight = window.innerHeight - headerHeight - footerHeight; if (flexHeight < expandedSection.scrollingHeight) { expandedSectionHeight = flexHeight; // Also, artificially expand the height of the document so that we can see // the entire expanded section. // // TODO(aa): Where does this come from? It is the difference between what // we set document.body.style.height to and what // document.body.scrollHeight measures afterward. I expect them to be the // same if document.body has no margins. var fudge = 44; document.body.style.height = headerHeight + expandedSection.scrollingHeight + footerHeight + fudge + 'px'; } else { expandedSectionHeight = expandedSection.scrollingHeight; document.body.style.height = ''; } }

// Now position all the elements. var y = LAYOUT_SPACING_TOP; for (i = 0, section; section = sections[i]; i++) { section.section.style.top = y + 'px'; y += section.fixedHeight; if (section.maxiview && section == expandedSection) { section.maxiview.style.top = y + 'px'; updateMask(section.maxiview, expandedSectionHeight); } if (section == expandedSection) y += expandedSectionHeight; } updateAttributionDisplay(y); } function updateMask(maxiview, visibleHeightPx) { // We want to end up with 10px gradients at the top and bottom of // visibleHeight, but webkit-mask only supports expression in terms of // percentages. // We might not have enough room to do 10px gradients on each side. To get the // right effect, we don't want to make the gradients smaller, but make them // appear to mush into each other. var gradientHeightPx = Math.min(10, Math.floor(visibleHeightPx / 2)); var gradientDestination = 'rgba(0,0,0,' + (gradientHeightPx / 10) + ')'; var var var var var bottomSpacing = 15; first = parseFloat(maxiview.style.top) / window.innerHeight; second = first + gradientHeightPx / window.innerHeight; fourth = first + (visibleHeightPx - bottomSpacing) / window.innerHeight; third = fourth - gradientHeightPx / window.innerHeight;

var gradientArguments = [ 'linear', '0 0', '0 100%', 'from(transparent)', getColorStopString(first, 'transparent'), getColorStopString(second, gradientDestination), getColorStopString(third, gradientDestination), getColorStopString(fourth, 'transparent'), 'to(transparent)' ]; var gradient = '-webkit-gradient(' + gradientArguments.join(', ') + ')'; maxiview.style.WebkitMaskImage = gradient; } function getColorStopString(height, color) { return 'color-stop(' + height + ', ' + color + ')'; } window.addEventListener('resize', handleWindowResize); var sectionToElementMap; function getSectionElement(section) { if (!sectionToElementMap) {

sectionToElementMap = {}; for (var key in Section) { sectionToElementMap[Section[key]] = document.querySelector('.section[section=' + key + ']'); } } return sectionToElementMap[section]; } function getSectionMaxiview(section) { return $(section.id + '-maxiview'); } // You usually want to call showOnlySection() instead of this. function showSection(section) { if (!(section & shownSections)) { shownSections = section; var el = getSectionElement(section); if (el) { el.classList.remove('hidden'); var maxiview = getSectionMaxiview(el); if (maxiview) { maxiview.classList.remove('hiding'); maxiview.classList.remove('hidden'); } } switch (section) { case Section.THUMB: mostVisited.visible = true; mostVisited.layout(); break; } } } // Show this section and hide all other sections - at most one section can // be open at one time. function showOnlySection(section) { for (var p in Section) { if (p == section) showSection(Section[p]); else hideSection(Section[p]); } } function hideSection(section) { if (section & shownSections) { shownSections &= ~section; switch (section) { case Section.THUMB: mostVisited.visible = false; mostVisited.layout(); break; } var el = getSectionElement(section);

if (el) { el.classList.add('hidden'); var maxiview = getSectionMaxiview(el); if (maxiview) maxiview.classList.add('hiding'); var miniview = el.getElementsByClassName('miniview')[0]; if (miniview) updateMiniviewClipping(miniview); } } } window.addEventListener('webkitTransitionEnd', function(e) { if (e.target.classList.contains('hiding')) { e.target.classList.add('hidden'); e.target.classList.remove('hiding'); } document.documentElement.setAttribute('enable-section-animations', 'false'); }); /** * Callback when the shown sections changes in another NTP. * @param {number} newShownSections Bitmask of the shown sections. */ function setShownSections(newShownSections) { for (var key in Section) { if (newShownSections & Section[key]) showSection(Section[key]); else hideSection(Section[key]); } setSectionVisible( 'apps', Section.APPS, !(newShownSections & MINIMIZED_APPS), MINIMIZED_APPS); setSectionVisible( 'most-visited', Section.THUMB, !(newShownSections & MINIMIZED_THUMB), MINIMIZED_THUMB); setSectionVisible( 'recently-closed', undefined, !(newShownSections & MINIMIZED_RECENT), MINIMIZED_RECENT); layoutSections(); } // Recently closed function layoutRecentlyClosed() { var recentElement = $('recently-closed'); var miniview = recentElement.getElementsByClassName('miniview')[0]; updateMiniviewClipping(miniview); if (miniview.hasChildNodes()) { if (!(shownSections & MINIMIZED_RECENT)) { recentElement.classList.remove('disabled'); } } else { recentElement.classList.add('disabled');

} } /** * This function is called by the backend whenever the sync status section * needs to be updated to reflect recent sync state changes. The backend passes * the new status information in the newMessage parameter. The state includes * the following: * * syncsectionisvisible: true if the sync section needs to show up on the new * tab page and false otherwise. * title: the header for the sync status section. * msg: the actual message (e.g. "Synced to foo@gmail.com"). * linkisvisible: true if the link element should be visible within the sync * section and false otherwise. * linktext: the text to display as the link in the sync status (only used if * linkisvisible is true). * linkurlisset: true if an URL should be set as the href for the link and false * otherwise. If this field is false, then clicking on the link * will result in sending a message to the backend (see * 'SyncLinkClicked'). * linkurl: the URL to use as the element's href (only used if linkurlisset is * true). */ function syncMessageChanged(newMessage) { var syncStatusElement = $('sync-status'); // Hide the section if the message is emtpy. if (!newMessage['syncsectionisvisible']) { syncStatusElement.classList.add('disabled'); return; } syncStatusElement.classList.remove('disabled'); var content = syncStatusElement.children[0]; // Set the sync section background color based on the state. if (newMessage.msgtype == 'error') { content.style.backgroundColor = 'tomato'; } else { content.style.backgroundColor = ''; } // Set the text for the header and sync message. var titleElement = content.firstElementChild; titleElement.textContent = newMessage.title; var messageElement = titleElement.nextElementSibling; messageElement.textContent = newMessage.msg; // Remove what comes after the message while (messageElement.nextSibling) { content.removeChild(messageElement.nextSibling); } if (newMessage.linkisvisible) { var el; if (newMessage.linkurlisset) { // Use a link el = document.createElement('a');

el.href = newMessage.linkurl; } else { el = document.createElement('button'); el.className = 'link'; el.addEventListener('click', syncSectionLinkClicked); } el.textContent = newMessage.linktext; content.appendChild(el); fixLinkUnderline(el); } layoutSections(); } /** * Invoked when the link in the sync status section is clicked. */ function syncSectionLinkClicked(e) { chrome.send('SyncLinkClicked'); e.preventDefault(); } /** * Invoked when link to start sync in the promo message is clicked, and Chrome * has already been synced to an account. */ function syncAlreadyEnabled(message) { showNotification(message.syncEnabledMessage, localStrings.getString('close')); } /** * Returns the text used for a recently closed window. * @param {number} numTabs Number of tabs in the window. * @return {string} The text to use. */ function formatTabsText(numTabs) { if (numTabs == 1) return localStrings.getString('closedwindowsingle'); return localStrings.getStringF('closedwindowmultiple', numTabs); } // Theme related function themeChanged(hasAttribution) { document.documentElement.setAttribute('hasattribution', hasAttribution); $('themecss').href = 'chrome://theme/css/newtab.css?' + Date.now(); updateAttribution(); } function updateAttribution() { // Default value for standard NTP with no theme attribution or custom logo. logEvent('updateAttribution called'); var imageId = 'IDR_PRODUCT_LOGO'; // Theme attribution always overrides custom logos. if (document.documentElement.getAttribute('hasattribution') == 'true') { logEvent('updateAttribution called with THEME ATTR'); imageId = 'IDR_THEME_NTP_ATTRIBUTION'; } else if (document.documentElement.getAttribute('customlogo') == 'true') { logEvent('updateAttribution with CUSTOMLOGO');

imageId = 'IDR_CUSTOM_PRODUCT_LOGO'; } $('attribution-img').src = 'chrome://theme/' + imageId + '?' + Date.now(); } // If the content overlaps with the attribution, we bump its opacity down. function updateAttributionDisplay(contentBottom) { var attribution = $('attribution'); var main = $('main'); var rtl = document.documentElement.dir == 'rtl'; var contentRect = main.getBoundingClientRect(); var attributionRect = attribution.getBoundingClientRect(); // Hack. See comments for '.haslayout' in new_new_tab.css. if (attributionRect.width == 0) return; else attribution.classList.remove('nolayout'); if (contentBottom > attribution.offsetTop) { if ((!rtl && contentRect.right > attributionRect.left) (rtl && attributionRect.right > contentRect.left)) { attribution.classList.add('obscured'); return; } } attribution.classList.remove('obscured'); } function bookmarkBarAttached() { document.documentElement.setAttribute('bookmarkbarattached', 'true'); } function bookmarkBarDetached() { document.documentElement.setAttribute('bookmarkbarattached', 'false'); } function viewLog() { var lines = []; var start = log[0][1]; for (var i = 0; i < log.length; i++) { lines.push((log[i][1] - start) + ': ' + log[i][0]); } console.log(lines.join('\n')); } // We apply the size class here so that we don't trigger layout animations // onload. handleWindowResize(); var localStrings = new LocalStrings(); /////////////////////////////////////////////////////////////////////////////// // Things we know are not needed at startup go below here

function afterTransition(f) { if (!isDoneLoading()) { // Make sure we do not use a timer during load since it slows down the UI. f(); } else { // The duration of all transitions are .15s window.setTimeout(f, 150); } } // Notification var notificationTimeout; function showNotification(text, actionText, opt_f, opt_delay) { var notificationElement = $('notification'); var f = opt_f function() {}; var delay = opt_delay 10000; function show() { window.clearTimeout(notificationTimeout); notificationElement.classList.add('show'); document.body.classList.add('notification-shown'); } function delayedHide() { notificationTimeout = window.setTimeout(hideNotification, delay); } function doAction() { f(); hideNotification(); } // Remove any possible first-run trails. notification.classList.remove('first-run'); var actionLink = notificationElement.querySelector('.link-color'); notificationElement.firstElementChild.textContent = text; actionLink.textContent = actionText; actionLink.onclick = doAction; actionLink.onkeydown = handleIfEnterKey(doAction); notificationElement.onmouseover = show; notificationElement.onmouseout = delayedHide; actionLink.onfocus = show; actionLink.onblur = delayedHide; // Enable tabbing to the link now that it is shown. actionLink.tabIndex = 0; show(); delayedHide(); } /** * Hides the notifier. */ function hideNotification() { var notificationElement = $('notification');

notificationElement.classList.remove('show'); document.body.classList.remove('notification-shown'); var actionLink = notificationElement.querySelector('.link-color'); // Prevent tabbing to the hidden link. actionLink.tabIndex = -1; // Setting tabIndex to -1 only prevents future tabbing to it. If, however, the // user switches window or a tab and then moves back to this tab the element // may gain focus. We therefore make sure that we blur the element so that the // element focus is not restored when coming back to this window. actionLink.blur(); } function showFirstRunNotification() { showNotification(localStrings.getString('firstrunnotification'), localStrings.getString('closefirstrunnotification'), null, 30000); var notificationElement = $('notification'); notification.classList.add('first-run'); } $('main').addEventListener('click', function(e) { var p = e.target; while (p && p.tagName != 'H2') { // In case the user clicks on a button we do not want to expand/collapse a // section. if (p.tagName == 'BUTTON') return; p = p.parentNode; } if (!p) return; p = p.parentNode; if (!getSectionMaxiview(p)) return; toggleSectionVisibilityAndAnimate(p.getAttribute('section')); }); $('most-visited-settings').addEventListener('click', function() { $('clear-all-blacklisted').execute(); }); function toggleSectionVisibilityAndAnimate(section) { if (!section) return; // It looks better to return the scroll to the top when toggling sections. document.body.scrollTop = 0; // We set it back in webkitTransitionEnd. document.documentElement.setAttribute('enable-section-animations', 'true'); if (shownSections & Section[section]) { hideSection(Section[section]); } else { showOnlySection(section); } layoutSections(); saveShownSections();

} function handleIfEnterKey(f) { return function(e) { if (e.keyIdentifier == 'Enter') f(e); }; } function maybeReopenTab(e) { var el = findAncestor(e.target, function(el) { return el.sessionId !== undefined; }); if (el) { chrome.send('reopenTab', [String(el.sessionId)]); e.preventDefault(); // HACK(arv): After the window onblur event happens we get a mouseover event // on the next item and we want to make sure that we do not show a tooltip // for that. window.setTimeout(function() { windowTooltip.hide(); }, 2 * WindowTooltip.DELAY); } } function maybeShowWindowTooltip(e) { var f = function(el) { return el.tabItems !== undefined; }; var el = findAncestor(e.target, f); var relatedEl = findAncestor(e.relatedTarget, f); if (el && el != relatedEl) { windowTooltip.handleMouseOver(e, el, el.tabItems); } } var recentlyClosedElement = $('recently-closed'); recentlyClosedElement.addEventListener('click', maybeReopenTab); recentlyClosedElement.addEventListener('keydown', handleIfEnterKey(maybeReopenTab)); recentlyClosedElement.addEventListener('mouseover', maybeShowWindowTooltip); recentlyClosedElement.addEventListener('focus', maybeShowWindowTooltip, true); /** * This object represents a tooltip representing a closed window. It is * shown when hovering over a closed window item or when the item is focused. It * gets hidden when blurred or when mousing out of the menu or the item. * @param {Element} tooltipEl The element to use as the tooltip. * @constructor */ function WindowTooltip(tooltipEl) { this.tooltipEl = tooltipEl; this.boundHide_ = bind(this.hide, this); this.boundHandleMouseOut_ = bind(this.handleMouseOut, this); }

WindowTooltip.trackMouseMove_ = function(e) { WindowTooltip.clientX = e.clientX; WindowTooltip.clientY = e.clientY; }; /** * Time in ms to delay before the tooltip is shown. * @type {number} */ WindowTooltip.DELAY = 300; WindowTooltip.prototype = { timer: 0, handleMouseOver: function(e, linkEl, tabs) { this.linkEl_ = linkEl; if (e.type == 'mouseover') { this.linkEl_.addEventListener('mousemove', WindowTooltip.trackMouseMove_); this.linkEl_.addEventListener('mouseout', this.boundHandleMouseOut_); } else { // focus this.linkEl_.addEventListener('blur', this.boundHide_); } this.timer = window.setTimeout(bind(this.show, this, e.type, linkEl, tabs), WindowTooltip.DELAY); }, show: function(type, linkEl, tabs) { window.addEventListener('blur', this.boundHide_); this.linkEl_.removeEventListener('mousemove', WindowTooltip.trackMouseMove_); window.clearTimeout(this.timer); this.renderItems(tabs); var rect = linkEl.getBoundingClientRect(); var bodyRect = document.body.getBoundingClientRect(); var rtl = document.documentElement.dir == 'rtl'; this.tooltipEl.style.display = 'block'; var tooltipRect = this.tooltipEl.getBoundingClientRect(); var x, y; // When focused show below, like a drop down menu. if (type == 'focus') { x = rtl ? rect.left + bodyRect.left + rect.width - this.tooltipEl.offsetWidth : rect.left + bodyRect.left; y = rect.top + bodyRect.top + rect.height; } else { x = bodyRect.left + (rtl ? WindowTooltip.clientX - this.tooltipEl.offsetWidth : WindowTooltip.clientX); // Offset like a tooltip y = 20 + WindowTooltip.clientY + bodyRect.top; } // We need to ensure that the tooltip is inside the window viewport. x = Math.min(x, bodyRect.width - tooltipRect.width); x = Math.max(x, 0); y = Math.min(y, bodyRect.height - tooltipRect.height); y = Math.max(y, 0); this.tooltipEl.style.left = x + 'px';

this.tooltipEl.style.top = y + 'px'; }, handleMouseOut: function(e) { // Don't hide when move to another item in the link. var f = function(el) { return el.tabItems !== undefined; }; var el = findAncestor(e.target, f); var relatedEl = findAncestor(e.relatedTarget, f); if (el && el != relatedEl) { this.hide(); } }, hide: function() { window.clearTimeout(this.timer); window.removeEventListener('blur', this.boundHide_); this.linkEl_.removeEventListener('mousemove', WindowTooltip.trackMouseMove_); this.linkEl_.removeEventListener('mouseout', this.boundHandleMouseOut_); this.linkEl_.removeEventListener('blur', this.boundHide_); this.linkEl_ = null; this.tooltipEl.style.display = 'none'; }, renderItems: function(tabs) { var tooltip = this.tooltipEl; tooltip.textContent = ''; tabs.forEach(function(tab) { var span = document.createElement('span'); span.className = 'item'; span.style.backgroundImage = url('chrome://favicon/' + tab.url); span.dir = tab.direction; span.textContent = tab.title; tooltip.appendChild(span); }); } }; var windowTooltip = new WindowTooltip($('window-tooltip')); window.addEventListener('load', bind(logEvent, global, 'Tab.NewTabOnload', true)); window.addEventListener('resize', handleWindowResize); document.addEventListener('DOMContentLoaded', bind(logEvent, global, 'Tab.NewTabDOMContentLoaded', true)); // Whether or not we should send the initial 'GetSyncMessage' to the backend // depends on the value of the attribue 'syncispresent' which the backend sets // to indicate if there is code in the backend which is capable of processing // this message. This attribute is loaded by the JSTemplate and therefore we // must make sure we check the attribute after the DOM is loaded. document.addEventListener('DOMContentLoaded', callGetSyncMessageIfSyncIsPresent); /** * The sync code is not yet built by default on all platforms so we have to * make sure we don't send the initial sync message to the backend unless the * backend told us that the sync code is present.

*/ function callGetSyncMessageIfSyncIsPresent() { if (document.documentElement.getAttribute('syncispresent') == 'true') { chrome.send('GetSyncMessage'); } } // Tooltip for elements that have text that overflows. document.addEventListener('mouseover', function(e) { // We don't want to do this while we are dragging because it makes things very // janky if (mostVisited.isDragging()) { return; } var el = findAncestor(e.target, function(el) { return el.xtitle; }); if (el && el.xtitle != el.title) { if (el.scrollWidth > el.clientWidth) { el.title = el.xtitle; } else { el.title = ''; } } }); /** * Makes links and buttons support a different underline color. * @param {Node} node The node to search for links and buttons in. */ function fixLinkUnderlines(node) { var elements = node.querySelectorAll('a,button'); Array.prototype.forEach.call(elements, fixLinkUnderline); } /** * Wraps the content of an element in a a link-color span. * @param {Element} el The element to wrap. */ function fixLinkUnderline(el) { var span = document.createElement('span'); span.className = 'link-color'; while (el.hasChildNodes()) { span.appendChild(el.firstChild); } el.appendChild(span); } updateAttribution(); var mostVisited = new MostVisited( $('most-visited-maxiview'), document.querySelector('#most-visited .miniview'), $('most-visited-menu'), useSmallGrid(), shownSections & Section.THUMB); function mostVisitedPages(data, firstRun, hasBlacklistedUrls) { logEvent('received most visited pages');

mostVisited.updateSettingsLink(hasBlacklistedUrls); mostVisited.data = data; mostVisited.layout(); layoutSections(); // Remove class name in a timeout so that changes done in this JS thread are // not animated. window.setTimeout(function() { mostVisited.ensureSmallGridCorrect(); maybeDoneLoading(); }, 1); // Only show the first run notification if first run. if (firstRun) { showFirstRunNotification(); } } function maybeDoneLoading() { if (mostVisited.data && apps.loaded) document.body.classList.remove('loading'); } function isDoneLoading() { return !document.body.classList.contains('loading'); } </script> <script>// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. function getAppsCallback(data) { logEvent('received apps'); var appsSection = $('apps'); var appsSectionContent = $('apps-maxiview'); var appsMiniview = appsSection.getElementsByClassName('miniview')[0]; appsSectionContent.textContent = ''; appsMiniview.textContent = ''; clearClosedMenu(apps.menu); if (data.apps.length == 0) { appsSection.classList.add('disabled'); layoutSections(); } else { data.apps.forEach(function(app) { appsSectionContent.appendChild(apps.createElement(app)); }); appsSectionContent.appendChild(apps.createWebStoreElement()); data.apps.slice(0, MAX_MINIVIEW_ITEMS).forEach(function(app) { appsMiniview.appendChild(apps.createMiniviewElement(app)); addClosedMenuEntryWithLink(apps.menu, apps.createClosedMenuElement(app)); }); if (!(shownSections & MINIMIZED_APPS)) { appsSection.classList.remove('disabled'); } }

addClosedMenuFooter(apps.menu, 'apps', MINIMIZED_APPS, Section.APPS); apps.loaded = true; maybeDoneLoading(); if (data.apps.length > 0 && isDoneLoading()) { updateMiniviewClipping(appsMiniview); layoutSections(); } } var apps = (function() { function createElement(app) { var div = document.createElement('div'); div.className = 'app'; var a = div.appendChild(document.createElement('a')); a.setAttribute('app-id', app['id']); a.xtitle = a.textContent = app['name']; a.href = app['launch_url']; return div; } function createContextMenu(app) { var menu = new cr.ui.Menu; var button = document.createElement(button); } function launchApp(appId) { var appsSection = $('apps'); var expanded = !appsSection.classList.contains('hidden'); var element = document.querySelector( (expanded ? '.maxiview' : '.miniview') + ' a[app-id=' + appId + ']'); // TODO(arv): Handle zoom? var rect = element.getBoundingClientRect(); var cs = getComputedStyle(element); var size = cs.backgroundSize.split(/\s+/); // background-size has the // format '123px 456px'. var width = parseInt(size[0], 10); var height = parseInt(size[1], 10); var top, left; if (expanded) { // We are using background-position-x 50%. top = rect.top + parseInt(cs.backgroundPositionY, 10); left = rect.left + ((rect.width - width) >> 1); // Integer divide by 2. } else { // We are using background-position-y 50%. top = rect.top + ((rect.height - width) >> 1); // Integer divide by 2. if (getComputedStyle(element).direction == 'rtl') left = rect.left + rect.width - width; else left = rect.left; }

chrome.send('launchApp', [appId, String(left), String(top), String(width), String(height)]); } /** * @this {!HTMLAnchorElement} */ function handleClick(e) { var appId = e.currentTarget.getAttribute('app-id'); launchApp(appId); return false; } var currentApp; function addContextMenu(el, app) { el.addEventListener('contextmenu', cr.ui.contextMenuHandler); el.addEventListener('keydown', cr.ui.contextMenuHandler); el.addEventListener('keyup', cr.ui.contextMenuHandler); Object.defineProperty(el, 'contextMenu', { get: function() { currentApp = app; $('apps-launch-command').label = app['name']; $('apps-options-command').canExecuteChange(); return $('app-context-menu'); } }); } document.addEventListener('command', function(e) { if (!currentApp) return; switch (e.command.id) { case 'apps-options-command': window.location = currentApp['options_url']; break; case 'apps-launch-command': launchApp(currentApp['id']); break; case 'apps-uninstall-command': chrome.send('uninstallApp', [currentApp['id']]); break; } }); document.addEventListener('canExecute', function(e) { switch (e.command.id) { case 'apps-options-command': e.canExecute = currentApp && currentApp['options_url']; break; case 'apps-launch-command': case 'apps-uninstall-command': e.canExecute = true; break; }

}); return { loaded: false, menu: $('apps-menu'), createElement: function(app) { var div = createElement(app); var a = div.firstChild; a.onclick = handleClick; a.style.backgroundImage = url(app['icon_big']); if (hashParams['app-id'] == app['id']) { div.setAttribute('new', 'new'); // Delay changing the attribute a bit to let the page settle down a bit. setTimeout(function() { // This will trigger the 'bounce' animation defined in apps.css. div.setAttribute('new', 'installed'); }, 500); div.addEventListener('webkitAnimationEnd', function(e) { div.removeAttribute('new'); // If we get new data (eg because something installs in another tab, // or because we uninstall something here), don't run the install // animation again. document.documentElement.setAttribute("install-animation-enabled", "false"); }); // Make sure apps is de-minimized... setSectionVisible('apps', Section.APPS, true, MINIMIZED_APPS); // ...and expanded. if ($('apps').classList.contains('hidden')) toggleSectionVisibilityAndAnimate('APPS'); } var settingsButton = div.appendChild(new cr.ui.ContextMenuButton); settingsButton.className = 'app-settings'; settingsButton.title = localStrings.getString('appsettings'); addContextMenu(div, app); return div; }, createMiniviewElement: function(app) { var span = document.createElement('span'); var a = span.appendChild(document.createElement('a')); a.setAttribute('app-id', app['id']); a.textContent = app['name']; a.href = app['launch_url']; a.onclick = handleClick; a.style.backgroundImage = url(app['icon_small']); a.className = 'item'; span.appendChild(a); addContextMenu(span, app);

return span; }, createClosedMenuElement: function(app) { var a = document.createElement('a'); a.setAttribute('app-id', app['id']); a.textContent = app['name']; a.href = app['launch_url']; a.onclick = handleClick; a.style.backgroundImage = url(app['icon_small']); a.className = 'item'; return a; }, createWebStoreElement: function() { return createElement({ 'id': 'web-store-entry', 'name': localStrings.getString('web_store_title'), 'launch_url': localStrings.getString('web_store_url') }); } }; })(); </script> <script> cr.ui.decorate('menu', cr.ui.Menu); cr.ui.decorate('command', cr.ui.Command); cr.ui.decorate('button[menu]', cr.ui.MenuButton); </script> <script> initializeSection('apps', MINIMIZED_APPS, Section.APPS); initializeSection('most-visited', MINIMIZED_THUMB, Section.THUMB); initializeSection('recently-closed', MINIMIZED_RECENT); updateSimpleSection('apps', Section.APPS); updateSimpleSection('most-visited', Section.THUMB); var appsInitiallyVisible = !(shownSections & MINIMIZED_APPS); var mostVisitedInitiallyVisible = !(shownSections & MINIMIZED_THUMB); var recentlyClosedInitiallyVisible = !(shownSections & MINIMIZED_RECENT); // Apps and recently closed start as hidden in the HTML, most visited is // initially visible. Adapt to the change received from the prefs by forcing // all three sections to update. shownSections &= ~MINIMIZED_THUMB; shownSections = MINIMIZED_APPS MINIMIZED_RECENT; setSectionVisible('apps', Section.APPS, appsInitiallyVisible, MINIMIZED_APPS); setSectionVisible( 'most-visited', Section.THUMB, mostVisitedInitiallyVisible, MINIMIZED_THUMB); setSectionVisible( 'recently-closed', undefined, recentlyClosedInitiallyVisible, MINIMIZED_RECENT); layoutSections(); </script> </body></html>

You might also like