Index: chrome/browser/resources/ntp_android/ntp_android.js |
diff --git a/chrome/browser/resources/ntp_android/ntp_android.js b/chrome/browser/resources/ntp_android/ntp_android.js |
deleted file mode 100644 |
index c44298cfb3ed78b2807b9f3a6d3c7fbf7aef3f07..0000000000000000000000000000000000000000 |
--- a/chrome/browser/resources/ntp_android/ntp_android.js |
+++ /dev/null |
@@ -1,2736 +0,0 @@ |
-// Copyright (c) 2011 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. |
- |
-// File Description: |
-// Contains all the necessary functions for rendering the NTP on mobile |
-// devices. |
- |
-/** |
- * The event type used to determine when a touch starts. |
- * @type {string} |
- */ |
-var PRESS_START_EVT = 'touchstart'; |
- |
-/** |
- * The event type used to determine when a touch finishes. |
- * @type {string} |
- */ |
-var PRESS_STOP_EVT = 'touchend'; |
- |
-/** |
- * The event type used to determine when a touch moves. |
- * @type {string} |
- */ |
-var PRESS_MOVE_EVT = 'touchmove'; |
- |
-cr.define('ntp', function() { |
- /** |
- * Constant for the localStorage key used to specify the default bookmark |
- * folder to be selected when navigating to the bookmark tab for the first |
- * time of a new NTP instance. |
- * @type {string} |
- */ |
- var DEFAULT_BOOKMARK_FOLDER_KEY = 'defaultBookmarkFolder'; |
- |
- /** |
- * Constant for the localStorage key used to store whether or not sync was |
- * enabled on the last call to syncEnabled(). |
- * @type {string} |
- */ |
- var SYNC_ENABLED_KEY = 'syncEnabled'; |
- |
- /** |
- * The time before and item gets marked as active (in milliseconds). This |
- * prevents an item from being marked as active when the user is scrolling |
- * the page. |
- * @type {number} |
- */ |
- var ACTIVE_ITEM_DELAY_MS = 100; |
- |
- /** |
- * The CSS class identifier for grid layouts. |
- * @type {string} |
- */ |
- var GRID_CSS_CLASS = 'icon-grid'; |
- |
- /** |
- * The element to center when centering a GRID_CSS_CLASS. |
- */ |
- var GRID_CENTER_CSS_CLASS = 'center-icon-grid'; |
- |
- /** |
- * Attribute used to specify the number of columns to use in a grid. If |
- * left unspecified, the grid will fill the container. |
- */ |
- var GRID_COLUMNS = 'grid-columns'; |
- |
- /** |
- * Attribute used to specify whether the top margin should be set to match |
- * the left margin of the grid. |
- */ |
- var GRID_SET_TOP_MARGIN_CLASS = 'grid-set-top-margin'; |
- |
- /** |
- * Attribute used to specify whether the margins of individual items within |
- * the grid should be adjusted to better fill the space. |
- */ |
- var GRID_SET_ITEM_MARGINS = 'grid-set-item-margins'; |
- |
- /** |
- * The CSS class identifier for centered empty section containers. |
- */ |
- var CENTER_EMPTY_CONTAINER_CSS_CLASS = 'center-empty-container'; |
- |
- /** |
- * The CSS class identifier for marking list items as active. |
- * @type {string} |
- */ |
- var ACTIVE_LIST_ITEM_CSS_CLASS = 'list-item-active'; |
- |
- /** |
- * Attributes set on elements representing data in a section, specifying |
- * which section that element belongs to. Used for context menus. |
- * @type {string} |
- */ |
- var SECTION_KEY = 'sectionType'; |
- |
- /** |
- * Attribute set on an element that has a context menu. Specifies the URL for |
- * which the context menu action should apply. |
- * @type {string} |
- */ |
- var CONTEXT_MENU_URL_KEY = 'url'; |
- |
- /** |
- * The list of main section panes added. |
- * @type {Array.<Element>} |
- */ |
- var panes = []; |
- |
- /** |
- * The list of section prefixes, which are used to append to the hash of the |
- * page to allow the native toolbar to see url changes when the pane is |
- * switched. |
- */ |
- var sectionPrefixes = []; |
- |
- /** |
- * The next available index for new favicons. Users must increment this |
- * value once assigning this index to a favicon. |
- * @type {number} |
- */ |
- var faviconIndex = 0; |
- |
- /** |
- * The currently selected pane DOM element. |
- * @type {Element} |
- */ |
- var currentPane = null; |
- |
- /** |
- * The index of the currently selected top level pane. The index corresponds |
- * to the elements defined in {@see #panes}. |
- * @type {number} |
- */ |
- var currentPaneIndex; |
- |
- /** |
- * The ID of the bookmark folder currently selected. |
- * @type {string|number} |
- */ |
- var bookmarkFolderId = null; |
- |
- /** |
- * The current element active item. |
- * @type {?Element} |
- */ |
- var activeItem; |
- |
- /** |
- * The element to be marked as active if no actions cancel it. |
- * @type {?Element} |
- */ |
- var pendingActiveItem; |
- |
- /** |
- * The timer ID to mark an element as active. |
- * @type {number} |
- */ |
- var activeItemDelayTimerId; |
- |
- /** |
- * Enum for the different load states based on the initialization of the NTP. |
- * @enum {number} |
- */ |
- var LoadStatusType = { |
- LOAD_NOT_DONE: 0, |
- LOAD_IMAGES_COMPLETE: 1, |
- LOAD_BOOKMARKS_FINISHED: 2, |
- LOAD_COMPLETE: 3 // An OR'd combination of all necessary states. |
- }; |
- |
- /** |
- * The current loading status for the NTP. |
- * @type {LoadStatusType} |
- */ |
- var loadStatus_ = LoadStatusType.LOAD_NOT_DONE; |
- |
- /** |
- * Whether the loading complete notification has been sent. |
- * @type {boolean} |
- */ |
- var finishedLoadingNotificationSent_ = false; |
- |
- /** |
- * Whether the page title has been loaded. |
- * @type {boolean} |
- */ |
- var titleLoadedStatus_ = false; |
- |
- /** |
- * Whether the NTP is in incognito mode or not. |
- * @type {boolean} |
- */ |
- var isIncognito = false; |
- |
- /** |
- * Whether incognito mode is enabled. (It can be blocked e.g. with a policy.) |
- * @type {boolean} |
- */ |
- var isIncognitoEnabled = true; |
- |
- /** |
- * Whether the initial history state has been replaced. The state will be |
- * replaced once the bookmark data has loaded to ensure the proper folder |
- * id is persisted. |
- * @type {boolean} |
- */ |
- var replacedInitialState = false; |
- |
- /** |
- * Stores number of most visited pages. |
- * @type {number} |
- */ |
- var numberOfMostVisitedPages = 0; |
- |
- /** |
- * Whether there are any recently closed tabs. |
- * @type {boolean} |
- */ |
- var hasRecentlyClosedTabs = false; |
- |
- /** |
- * Whether promo is not allowed or not (external to NTP). |
- * @type {boolean} |
- */ |
- var promoIsAllowed = false; |
- |
- /** |
- * Whether promo should be shown on Most Visited page (externally set). |
- * @type {boolean} |
- */ |
- var promoIsAllowedOnMostVisited = false; |
- |
- /** |
- * Whether promo should be shown on Open Tabs page (externally set). |
- * @type {boolean} |
- */ |
- var promoIsAllowedOnOpenTabs = false; |
- |
- /** |
- * Whether promo should show a virtual computer on Open Tabs (externally set). |
- * @type {boolean} |
- */ |
- var promoIsAllowedAsVirtualComputer = false; |
- |
- /** |
- * Promo-injected title of a virtual computer on an open tabs pane. |
- * @type {string} |
- */ |
- var promoInjectedComputerTitleText = ''; |
- |
- /** |
- * Promo-injected last synced text of a virtual computer on an open tabs pane. |
- * @type {string} |
- */ |
- var promoInjectedComputerLastSyncedText = ''; |
- |
- /** |
- * The different sections that are displayed. |
- * @enum {number} |
- */ |
- var SectionType = { |
- BOOKMARKS: 'bookmarks', |
- FOREIGN_SESSION: 'foreign_session', |
- FOREIGN_SESSION_HEADER: 'foreign_session_header', |
- MOST_VISITED: 'most_visited', |
- PROMO_VC_SESSION_HEADER: 'promo_vc_session_header', |
- RECENTLY_CLOSED: 'recently_closed', |
- SNAPSHOTS: 'snapshots', |
- UNKNOWN: 'unknown', |
- }; |
- |
- /** |
- * The different ids used of our custom context menu. Sent to the ChromeView |
- * and sent back when a menu is selected. |
- * @enum {number} |
- */ |
- var ContextMenuItemIds = { |
- BOOKMARK_EDIT: 0, |
- BOOKMARK_DELETE: 1, |
- BOOKMARK_OPEN_IN_NEW_TAB: 2, |
- BOOKMARK_OPEN_IN_INCOGNITO_TAB: 3, |
- BOOKMARK_SHORTCUT: 4, |
- |
- MOST_VISITED_OPEN_IN_NEW_TAB: 10, |
- MOST_VISITED_OPEN_IN_INCOGNITO_TAB: 11, |
- MOST_VISITED_REMOVE: 12, |
- |
- RECENTLY_CLOSED_OPEN_IN_NEW_TAB: 20, |
- RECENTLY_CLOSED_OPEN_IN_INCOGNITO_TAB: 21, |
- RECENTLY_CLOSED_REMOVE: 22, |
- |
- FOREIGN_SESSIONS_REMOVE: 30, |
- |
- PROMO_VC_SESSION_REMOVE: 40, |
- }; |
- |
- /** |
- * The URL of the element for the context menu. |
- * @type {string} |
- */ |
- var contextMenuUrl = null; |
- |
- var contextMenuItem = null; |
- |
- var currentSnapshots = null; |
- |
- var currentSessions = null; |
- |
- /** |
- * The possible states of the sync section |
- * @enum {number} |
- */ |
- var SyncState = { |
- INITIAL: 0, |
- WAITING_FOR_DATA: 1, |
- DISPLAYING_LOADING: 2, |
- DISPLAYED_LOADING: 3, |
- LOADED: 4, |
- }; |
- |
- /** |
- * The current state of the sync section. |
- */ |
- var syncState = SyncState.INITIAL; |
- |
- /** |
- * Whether or not sync is enabled. It will be undefined until |
- * setSyncEnabled() is called. |
- * @type {?boolean} |
- */ |
- var syncEnabled = undefined; |
- |
- /** |
- * The current most visited data being displayed. |
- * @type {Array.<Object>} |
- */ |
- var mostVisitedData_ = []; |
- |
- /** |
- * The current bookmark data being displayed. Keep a reference to this data |
- * in case the sync enabled state changes. In this case, the bookmark data |
- * will need to be refiltered. |
- * @type {?Object} |
- */ |
- var bookmarkData; |
- |
- /** |
- * Keep track of any outstanding timers related to updating the sync section. |
- */ |
- var syncTimerId = -1; |
- |
- /** |
- * The minimum amount of time that 'Loading...' can be displayed. This is to |
- * prevent flashing. |
- */ |
- var SYNC_LOADING_TIMEOUT = 1000; |
- |
- /** |
- * How long to wait for sync data to load before displaying the 'Loading...' |
- * text to the user. |
- */ |
- var SYNC_INITIAL_LOAD_TIMEOUT = 1000; |
- |
- /** |
- * An array of images that are currently in loading state. Once an image |
- * loads it is removed from this array. |
- */ |
- var imagesBeingLoaded = new Array(); |
- |
- /** |
- * Flag indicating if we are on bookmark shortcut mode. |
- * In this mode, only the bookmark section is available and selecting |
- * a non-folder bookmark adds it to the home screen. |
- * Context menu is disabled. |
- */ |
- var bookmarkShortcutMode = false; |
- |
- function setIncognitoMode(incognito) { |
- isIncognito = incognito; |
- if (!isIncognito) { |
- chrome.send('getMostVisited'); |
- chrome.send('getRecentlyClosedTabs'); |
- chrome.send('getForeignSessions'); |
- chrome.send('getPromotions'); |
- chrome.send('getIncognitoDisabled'); |
- } |
- } |
- |
- function setIncognitoEnabled(item) { |
- isIncognitoEnabled = item.incognitoEnabled; |
- } |
- |
- /** |
- * Flag set to true when the page is loading its initial set of images. This |
- * is set to false after all the initial images have loaded. |
- */ |
- function onInitialImageLoaded(event) { |
- var url = event.target.src; |
- for (var i = 0; i < imagesBeingLoaded.length; ++i) { |
- if (imagesBeingLoaded[i].src == url) { |
- imagesBeingLoaded.splice(i, 1); |
- if (imagesBeingLoaded.length == 0) { |
- // To send out the NTP loading complete notification. |
- loadStatus_ |= LoadStatusType.LOAD_IMAGES_COMPLETE; |
- sendNTPNotification(); |
- } |
- } |
- } |
- } |
- |
- /** |
- * Marks the given image as currently being loaded. Once all such images load |
- * we inform the browser via a hash change. |
- */ |
- function trackImageLoad(url) { |
- if (finishedLoadingNotificationSent_) |
- return; |
- |
- for (var i = 0; i < imagesBeingLoaded.length; ++i) { |
- if (imagesBeingLoaded[i].src == url) |
- return; |
- } |
- |
- loadStatus_ &= (~LoadStatusType.LOAD_IMAGES_COMPLETE); |
- |
- var image = new Image(); |
- image.onload = onInitialImageLoaded; |
- image.onerror = onInitialImageLoaded; |
- image.src = url; |
- imagesBeingLoaded.push(image); |
- } |
- |
- /** |
- * Initializes all the UI once the page has loaded. |
- */ |
- function init() { |
- // Special case to handle NTP caching. |
- if (window.location.hash == '#cached_ntp') |
- document.location.hash = '#most_visited'; |
- // Special case to show a specific bookmarks folder. |
- // Used to show the mobile bookmarks folder after importing. |
- var bookmarkIdMatch = window.location.hash.match(/#bookmarks:(\d+)/); |
- if (bookmarkIdMatch && bookmarkIdMatch.length == 2) { |
- localStorage.setItem(DEFAULT_BOOKMARK_FOLDER_KEY, bookmarkIdMatch[1]); |
- document.location.hash = '#bookmarks'; |
- } |
- // Special case to choose a bookmark for adding a shortcut. |
- // See the doc of bookmarkShortcutMode for details. |
- if (window.location.hash == '#bookmark_shortcut') |
- bookmarkShortcutMode = true; |
- // Make sure a valid section is always displayed. Both normal and |
- // incognito NTPs have a bookmarks section. |
- if (getPaneIndexFromHash() < 0) |
- document.location.hash = '#bookmarks'; |
- |
- // Initialize common widgets. |
- var titleScrollers = |
- document.getElementsByClassName('section-title-wrapper'); |
- for (var i = 0, len = titleScrollers.length; i < len; i++) |
- initializeTitleScroller(titleScrollers[i]); |
- |
- // Initialize virtual computers for the sync promo. |
- createPromoVirtualComputers(); |
- |
- setCurrentBookmarkFolderData( |
- localStorage.getItem(DEFAULT_BOOKMARK_FOLDER_KEY)); |
- |
- addMainSection('incognito'); |
- addMainSection('most_visited'); |
- addMainSection('bookmarks'); |
- addMainSection('open_tabs'); |
- |
- computeDynamicLayout(); |
- |
- scrollToPane(getPaneIndexFromHash()); |
- updateSyncEmptyState(); |
- |
- window.onpopstate = onPopStateHandler; |
- window.addEventListener('hashchange', updatePaneOnHash); |
- window.addEventListener('resize', windowResizeHandler); |
- |
- if (!bookmarkShortcutMode) |
- window.addEventListener('contextmenu', contextMenuHandler); |
- } |
- |
- function sendNTPTitleLoadedNotification() { |
- if (!titleLoadedStatus_) { |
- titleLoadedStatus_ = true; |
- chrome.send('notifyNTPTitleLoaded'); |
- } |
- } |
- |
- /** |
- * Notifies the chrome process of the status of the NTP. |
- */ |
- function sendNTPNotification() { |
- if (loadStatus_ != LoadStatusType.LOAD_COMPLETE) |
- return; |
- |
- if (!finishedLoadingNotificationSent_) { |
- finishedLoadingNotificationSent_ = true; |
- chrome.send('notifyNTPReady'); |
- } else { |
- // Navigating after the loading complete notification has been sent |
- // might break tests. |
- chrome.send('NTPUnexpectedNavigation'); |
- } |
- } |
- |
- /** |
- * The default click handler for created item shortcuts. |
- * |
- * @param {Object} item The item specification. |
- * @param {function} evt The browser click event triggered. |
- */ |
- function itemShortcutClickHandler(item, evt) { |
- // Handle the touch callback |
- if (item['folder']) { |
- browseToBookmarkFolder(item.id); |
- } else { |
- if (bookmarkShortcutMode) { |
- chrome.send('createHomeScreenBookmarkShortcut', [item.id]); |
- } else if (!!item.url) { |
- window.location = item.url; |
- } |
- } |
- } |
- |
- /** |
- * Opens a recently closed tab. |
- * |
- * @param {Object} item An object containing the necessary information to |
- * reopen a tab. |
- */ |
- function openRecentlyClosedTab(item, evt) { |
- chrome.send('openedRecentlyClosed'); |
- chrome.send('reopenTab', [item.sessionId]); |
- } |
- |
- /** |
- * Creates a 'div' DOM element. |
- * |
- * @param {string} className The CSS class name for the DIV. |
- * @param {string=} opt_backgroundUrl The background URL to be applied to the |
- * DIV if required. |
- * @return {Element} The newly created DIV element. |
- */ |
- function createDiv(className, opt_backgroundUrl) { |
- var div = document.createElement('div'); |
- div.className = className; |
- if (opt_backgroundUrl) |
- div.style.backgroundImage = 'url(' + opt_backgroundUrl + ')'; |
- return div; |
- } |
- |
- /** |
- * Helper for creating new DOM elements. |
- * |
- * @param {string} type The type of Element to be created (i.e. 'div', |
- * 'span'). |
- * @param {Object} params A mapping of element attribute key and values that |
- * should be applied to the new element. |
- * @return {Element} The newly created DOM element. |
- */ |
- function createElement(type, params) { |
- var el = document.createElement(type); |
- if (typeof params === 'string') { |
- el.className = params; |
- } else { |
- for (attr in params) { |
- el[attr] = params[attr]; |
- } |
- } |
- return el; |
- } |
- |
- /** |
- * Adds a click listener to a specified element with the ability to override |
- * the default value of itemShortcutClickHandler. |
- * |
- * @param {Element} el The element the click listener should be added to. |
- * @param {Object} item The item data represented by the element. |
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The |
- * click callback to be triggered upon selection. |
- */ |
- function wrapClickHandler(el, item, opt_clickCallback) { |
- el.addEventListener('click', function(evt) { |
- var clickCallback = |
- opt_clickCallback ? opt_clickCallback : itemShortcutClickHandler; |
- clickCallback(item, evt); |
- }); |
- } |
- |
- /** |
- * Create a DOM element to contain a recently closed item for a tablet |
- * device. |
- * |
- * @param {Object} item The data of the item used to generate the shortcut. |
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The |
- * click callback to be triggered upon selection (if not provided it will |
- * use the default -- itemShortcutClickHandler). |
- * @return {Element} The shortcut element created. |
- */ |
- function makeRecentlyClosedTabletItem(item, opt_clickCallback) { |
- var cell = createDiv('cell'); |
- |
- cell.setAttribute(CONTEXT_MENU_URL_KEY, item.url); |
- |
- var iconUrl = item.icon; |
- if (!iconUrl) { |
- iconUrl = 'chrome://touch-icon/size/16@' + window.devicePixelRatio + |
- 'x/' + item.url; |
- } |
- var icon = createDiv('icon', iconUrl); |
- trackImageLoad(iconUrl); |
- cell.appendChild(icon); |
- |
- var title = createDiv('title'); |
- title.textContent = item.title; |
- cell.appendChild(title); |
- |
- wrapClickHandler(cell, item, opt_clickCallback); |
- |
- return cell; |
- } |
- |
- /** |
- * Creates a shortcut DOM element based on the item specified item |
- * configuration using the thumbnail layout used for most visited. Other |
- * data types should not use this as they won't have a thumbnail. |
- * |
- * @param {Object} item The data of the item used to generate the shortcut. |
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The |
- * click callback to be triggered upon selection (if not provided it will |
- * use the default -- itemShortcutClickHandler). |
- * @return {Element} The shortcut element created. |
- */ |
- function makeMostVisitedItem(item, opt_clickCallback) { |
- // thumbnail-cell -- main outer container |
- // thumbnail-container -- container for the thumbnail |
- // thumbnail -- the actual thumbnail image; outer border |
- // inner-border -- inner border |
- // title -- container for the title |
- // img -- hack align title text baseline with bottom |
- // title text -- the actual text of the title |
- var thumbnailCell = createDiv('thumbnail-cell'); |
- var thumbnailContainer = createDiv('thumbnail-container'); |
- var backgroundUrl = item.thumbnailUrl || 'chrome://thumb/' + item.url; |
- if (backgroundUrl == 'chrome://thumb/chrome://welcome/') { |
- // Ideally, it would be nice to use the URL as is. However, as of now |
- // theme support has been removed from Chrome. Instead, load the image |
- // URL from a style and use it. Don't just use the style because |
- // trackImageLoad(...) must be called with the background URL. |
- var welcomeStyle = findCssRule('.welcome-to-chrome').style; |
- var backgroundImage = welcomeStyle.backgroundImage; |
- // trim the "url(" prefix and ")" suffix |
- backgroundUrl = backgroundImage.substring(4, backgroundImage.length - 1); |
- } |
- trackImageLoad(backgroundUrl); |
- var thumbnail = createDiv('thumbnail'); |
- // Use an Image object to ensure the thumbnail image actually exists. If |
- // not, this will allow the default to show instead. |
- var thumbnailImg = new Image(); |
- thumbnailImg.onload = function() { |
- thumbnail.style.backgroundImage = 'url(' + backgroundUrl + ')'; |
- }; |
- thumbnailImg.src = backgroundUrl; |
- |
- thumbnailContainer.appendChild(thumbnail); |
- var innerBorder = createDiv('inner-border'); |
- thumbnailContainer.appendChild(innerBorder); |
- thumbnailCell.appendChild(thumbnailContainer); |
- var title = createDiv('title'); |
- title.textContent = item.title; |
- var spacerImg = createElement('img', 'title-spacer'); |
- spacerImg.alt = ''; |
- title.insertBefore(spacerImg, title.firstChild); |
- thumbnailCell.appendChild(title); |
- |
- var shade = createDiv('thumbnail-cell-shade'); |
- thumbnailContainer.appendChild(shade); |
- addActiveTouchListener(shade, 'thumbnail-cell-shade-active'); |
- |
- wrapClickHandler(thumbnailCell, item, opt_clickCallback); |
- |
- thumbnailCell.setAttribute(CONTEXT_MENU_URL_KEY, item.url); |
- thumbnailCell.contextMenuItem = item; |
- return thumbnailCell; |
- } |
- |
- /** |
- * Creates a shortcut DOM element based on the item specified item |
- * configuration using the favicon layout used for bookmarks. |
- * |
- * @param {Object} item The data of the item used to generate the shortcut. |
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The |
- * click callback to be triggered upon selection (if not provided it will |
- * use the default -- itemShortcutClickHandler). |
- * @return {Element} The shortcut element created. |
- */ |
- function makeBookmarkItem(item, opt_clickCallback) { |
- var holder = createDiv('favicon-cell'); |
- addActiveTouchListener(holder, 'favicon-cell-active'); |
- |
- holder.setAttribute(CONTEXT_MENU_URL_KEY, item.url); |
- holder.contextMenuItem = item; |
- var faviconBox = createDiv('favicon-box'); |
- if (item.folder) { |
- faviconBox.classList.add('folder'); |
- } else { |
- var iconUrl = item.icon || 'chrome://touch-icon/largest/' + item.url; |
- var faviconIcon = createDiv('favicon-icon'); |
- faviconIcon.style.backgroundImage = 'url(' + iconUrl + ')'; |
- trackImageLoad(iconUrl); |
- |
- var image = new Image(); |
- image.src = iconUrl; |
- image.onload = function() { |
- var w = image.width; |
- var h = image.height; |
- if (Math.floor(w) <= 16 || Math.floor(h) <= 16) { |
- // it's a standard favicon (or at least it's small). |
- faviconBox.classList.add('document'); |
- |
- faviconBox.appendChild( |
- createDiv('color-strip colorstrip-' + faviconIndex)); |
- faviconBox.appendChild(createDiv('bookmark-border')); |
- var foldDiv = createDiv('fold'); |
- foldDiv.id = 'fold_' + faviconIndex; |
- foldDiv.style['background'] = |
- '-webkit-canvas(fold_' + faviconIndex + ')'; |
- |
- // Use a container so that the fold it self can be zoomed without |
- // changing the positioning of the fold. |
- var foldContainer = createDiv('fold-container'); |
- foldContainer.appendChild(foldDiv); |
- faviconBox.appendChild(foldContainer); |
- |
- // FaviconWebUIHandler::HandleGetFaviconDominantColor expects |
- // an URL that starts with chrome://favicon/size/. |
- // The handler always loads 16x16 1x favicon and assumes that |
- // the dominant color for all scale factors is the same. |
- chrome.send('getFaviconDominantColor', |
- [('chrome://favicon/size/16@1x/' + item.url), '' + faviconIndex]); |
- faviconIndex++; |
- } else if ((w == 57 && h == 57) || (w == 114 && h == 114)) { |
- // it's a touch icon for 1x or 2x. |
- faviconIcon.classList.add('touch-icon'); |
- } else { |
- // It's an html5 icon (or at least it's larger). |
- // Rescale it to be no bigger than 64x64 dip. |
- var max = 64; |
- if (w > max || h > max) { |
- var scale = (w > h) ? (max / w) : (max / h); |
- w *= scale; |
- h *= scale; |
- } |
- faviconIcon.style.backgroundSize = w + 'px ' + h + 'px'; |
- } |
- }; |
- faviconBox.appendChild(faviconIcon); |
- } |
- holder.appendChild(faviconBox); |
- |
- var title = createDiv('title'); |
- title.textContent = item.title; |
- holder.appendChild(title); |
- |
- wrapClickHandler(holder, item, opt_clickCallback); |
- |
- return holder; |
- } |
- |
- /** |
- * Adds touch listeners to the specified element to apply a class when it is |
- * selected (removing the class when no longer pressed). |
- * |
- * @param {Element} el The element to apply the class to when touched. |
- * @param {string} activeClass The CSS class name to be applied when active. |
- */ |
- function addActiveTouchListener(el, activeClass) { |
- if (!window.touchCancelListener) { |
- window.touchCancelListener = function(evt) { |
- if (activeItemDelayTimerId) { |
- clearTimeout(activeItemDelayTimerId); |
- activeItemDelayTimerId = undefined; |
- } |
- if (!activeItem) { |
- return; |
- } |
- activeItem.classList.remove(activeItem.dataset.activeClass); |
- activeItem = null; |
- }; |
- document.addEventListener('touchcancel', window.touchCancelListener); |
- } |
- el.dataset.activeClass = activeClass; |
- el.addEventListener(PRESS_START_EVT, function(evt) { |
- if (activeItemDelayTimerId) { |
- clearTimeout(activeItemDelayTimerId); |
- activeItemDelayTimerId = undefined; |
- } |
- activeItemDelayTimerId = setTimeout(function() { |
- el.classList.add(activeClass); |
- activeItem = el; |
- }, ACTIVE_ITEM_DELAY_MS); |
- }); |
- el.addEventListener(PRESS_STOP_EVT, function(evt) { |
- if (activeItemDelayTimerId) { |
- clearTimeout(activeItemDelayTimerId); |
- activeItemDelayTimerId = undefined; |
- } |
- // Add the active class to ensure the pressed state is visible when |
- // quickly tapping, which can happen if the start and stop events are |
- // received before the active item delay timer has been executed. |
- el.classList.add(activeClass); |
- el.classList.add('no-active-delay'); |
- setTimeout(function() { |
- el.classList.remove(activeClass); |
- el.classList.remove('no-active-delay'); |
- }, 0); |
- activeItem = null; |
- }); |
- } |
- |
- /** |
- * Creates a shortcut DOM element based on the item specified in the list |
- * format. |
- * |
- * @param {Object} item The data of the item used to generate the shortcut. |
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The |
- * click callback to be triggered upon selection (if not provided it will |
- * use the default -- itemShortcutClickHandler). |
- * @return {Element} The shortcut element created. |
- */ |
- function makeListEntryItem(item, opt_clickCallback) { |
- var listItem = createDiv('list-item'); |
- addActiveTouchListener(listItem, ACTIVE_LIST_ITEM_CSS_CLASS); |
- listItem.setAttribute(CONTEXT_MENU_URL_KEY, item.url); |
- var iconSize = item.iconSize || 64; |
- var iconUrl = item.icon || |
- 'chrome://touch-icon/size/' + iconSize + '@1x/' + item.url; |
- listItem.appendChild(createDiv('icon', iconUrl)); |
- trackImageLoad(iconUrl); |
- var title = createElement('div', { |
- textContent: item.title, |
- className: 'title session_title' |
- }); |
- listItem.appendChild(title); |
- |
- listItem.addEventListener('click', function(evt) { |
- var clickCallback = |
- opt_clickCallback ? opt_clickCallback : itemShortcutClickHandler; |
- clickCallback(item, evt); |
- }); |
- if (item.divider == 'section') { |
- // Add a child div because the section divider has a gradient and |
- // webkit doesn't seem to currently support borders with gradients. |
- listItem.appendChild(createDiv('section-divider')); |
- } else { |
- listItem.classList.add('standard-divider'); |
- } |
- return listItem; |
- } |
- |
- /** |
- * Creates a DOM list entry for a remote session or tab. |
- * |
- * @param {Object} item The data of the item used to generate the shortcut. |
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The |
- * click callback to be triggered upon selection (if not provided it will |
- * use the default -- itemShortcutClickHandler). |
- * @return {Element} The shortcut element created. |
- */ |
- function makeForeignSessionListEntry(item, opt_clickCallback) { |
- // Session item |
- var sessionOuterDiv = createDiv('list-item standard-divider'); |
- addActiveTouchListener(sessionOuterDiv, ACTIVE_LIST_ITEM_CSS_CLASS); |
- sessionOuterDiv.contextMenuItem = item; |
- |
- var icon = createDiv('session-icon ' + item.iconStyle); |
- sessionOuterDiv.appendChild(icon); |
- |
- var titleContainer = createElement('div', 'title'); |
- sessionOuterDiv.appendChild(titleContainer); |
- |
- // Extra container to allow title & last-sync time to stack vertically. |
- var sessionInnerDiv = createDiv('session_container'); |
- titleContainer.appendChild(sessionInnerDiv); |
- |
- var title = createDiv('session-name'); |
- title.textContent = item.title; |
- title.id = item.titleId || ''; |
- sessionInnerDiv.appendChild(title); |
- |
- var lastSynced = createDiv('session-last-synced'); |
- lastSynced.textContent = |
- templateData.opentabslastsynced + ': ' + item.userVisibleTimestamp; |
- lastSynced.id = item.userVisibleTimestampId || ''; |
- sessionInnerDiv.appendChild(lastSynced); |
- |
- sessionOuterDiv.addEventListener('click', function(evt) { |
- var clickCallback = |
- opt_clickCallback ? opt_clickCallback : itemShortcutClickHandler; |
- clickCallback(item, evt); |
- }); |
- return sessionOuterDiv; |
- } |
- |
- /** |
- * Saves the number of most visited pages and updates promo visibility. |
- * @param {number} n Number of most visited pages. |
- */ |
- function setNumberOfMostVisitedPages(n) { |
- numberOfMostVisitedPages = n; |
- updatePromoVisibility(); |
- } |
- |
- /** |
- * Saves the recently closed tabs flag and updates promo visibility. |
- * @param {boolean} anyTabs Whether there are any recently closed tabs. |
- */ |
- function setHasRecentlyClosedTabs(anyTabs) { |
- hasRecentlyClosedTabs = anyTabs; |
- updatePromoVisibility(); |
- } |
- |
- /** |
- * Updates the most visited pages. |
- * |
- * @param {Array.<Object>} List of data for displaying the list of most |
- * visited pages (see C++ handler for model description). |
- * @param {boolean} hasBlacklistedUrls Whether any blacklisted URLs are |
- * present. |
- */ |
- function setMostVisitedPages(data, hasBlacklistedUrls) { |
- setNumberOfMostVisitedPages(data.length); |
- // limit the number of most visited items to display |
- if (isPhone() && data.length > 6) { |
- data.splice(6, data.length - 6); |
- } else if (isTablet() && data.length > 8) { |
- data.splice(8, data.length - 8); |
- } |
- |
- data.forEach(function(item, index) { |
- item.mostVisitedIndex = index; |
- }); |
- |
- if (equals(data, mostVisitedData_)) |
- return; |
- |
- var clickFunction = function(item) { |
- chrome.send('openedMostVisited'); |
- chrome.send('metricsHandler:recordInHistogram', |
- ['NewTabPage.MostVisited', item.mostVisitedIndex, 8]); |
- window.location = item.url; |
- }; |
- populateData(findList('most_visited'), SectionType.MOST_VISITED, data, |
- makeMostVisitedItem, clickFunction); |
- computeDynamicLayout(); |
- |
- mostVisitedData_ = data; |
- } |
- |
- /** |
- * Updates the recently closed tabs. |
- * |
- * @param {Array.<Object>} List of data for displaying the list of recently |
- * closed tabs (see C++ handler for model description). |
- */ |
- function setRecentlyClosedTabs(data) { |
- var container = $('recently_closed_container'); |
- if (!data || data.length == 0) { |
- // hide the recently closed section if it is empty. |
- container.style.display = 'none'; |
- setHasRecentlyClosedTabs(false); |
- } else { |
- container.style.display = 'block'; |
- setHasRecentlyClosedTabs(true); |
- var decoratorFunc = isPhone() ? makeListEntryItem : |
- makeRecentlyClosedTabletItem; |
- populateData(findList('recently_closed'), SectionType.RECENTLY_CLOSED, |
- data, decoratorFunc, openRecentlyClosedTab); |
- } |
- computeDynamicLayout(); |
- } |
- |
- /** |
- * Updates the bookmarks. |
- * |
- * @param {Array.<Object>} List of data for displaying the bookmarks (see |
- * C++ handler for model description). |
- */ |
- function bookmarks(data) { |
- bookmarkFolderId = data.id; |
- if (!replacedInitialState) { |
- history.replaceState( |
- {folderId: bookmarkFolderId, selectedPaneIndex: currentPaneIndex}, |
- null, null); |
- replacedInitialState = true; |
- } |
- if (syncEnabled == undefined) { |
- // Wait till we know whether or not sync is enabled before displaying any |
- // bookmarks (since they may need to be filtered below) |
- bookmarkData = data; |
- return; |
- } |
- |
- var titleWrapper = $('bookmarks_title_wrapper'); |
- setBookmarkTitleHierarchy( |
- titleWrapper, data, data['hierarchy']); |
- |
- var filteredBookmarks = data.bookmarks; |
- if (!syncEnabled) { |
- filteredBookmarks = filteredBookmarks.filter(function(val) { |
- return (val.type != 'BOOKMARK_BAR' && val.type != 'OTHER_NODE'); |
- }); |
- } |
- if (bookmarkShortcutMode) { |
- populateData(findList('bookmarks'), SectionType.BOOKMARKS, |
- filteredBookmarks, makeBookmarkItem); |
- } else { |
- var clickFunction = function(item) { |
- if (item['folder']) { |
- browseToBookmarkFolder(item.id); |
- } else if (!!item.url) { |
- chrome.send('openedBookmark'); |
- window.location = item.url; |
- } |
- }; |
- populateData(findList('bookmarks'), SectionType.BOOKMARKS, |
- filteredBookmarks, makeBookmarkItem, clickFunction); |
- } |
- |
- var bookmarkContainer = $('bookmarks_container'); |
- |
- // update the shadows on the breadcrumb bar |
- computeDynamicLayout(); |
- |
- if ((loadStatus_ & LoadStatusType.LOAD_BOOKMARKS_FINISHED) != |
- LoadStatusType.LOAD_BOOKMARKS_FINISHED) { |
- loadStatus_ |= LoadStatusType.LOAD_BOOKMARKS_FINISHED; |
- sendNTPNotification(); |
- } |
- } |
- |
- /** |
- * Checks if promo is allowed and MostVisited requirements are satisfied. |
- * @return {boolean} Whether the promo should be shown on most_visited. |
- */ |
- function shouldPromoBeShownOnMostVisited() { |
- return promoIsAllowed && promoIsAllowedOnMostVisited && |
- numberOfMostVisitedPages >= 2 && !hasRecentlyClosedTabs; |
- } |
- |
- /** |
- * Checks if promo is allowed and OpenTabs requirements are satisfied. |
- * @return {boolean} Whether the promo should be shown on open_tabs. |
- */ |
- function shouldPromoBeShownOnOpenTabs() { |
- var snapshotsCount = |
- currentSnapshots == null ? 0 : currentSnapshots.length; |
- var sessionsCount = currentSessions == null ? 0 : currentSessions.length; |
- return promoIsAllowed && promoIsAllowedOnOpenTabs && |
- (snapshotsCount + sessionsCount != 0); |
- } |
- |
- /** |
- * Checks if promo is allowed and SyncPromo requirements are satisfied. |
- * @return {boolean} Whether the promo should be shown on sync_promo. |
- */ |
- function shouldPromoBeShownOnSync() { |
- var snapshotsCount = |
- currentSnapshots == null ? 0 : currentSnapshots.length; |
- var sessionsCount = currentSessions == null ? 0 : currentSessions.length; |
- return promoIsAllowed && promoIsAllowedOnOpenTabs && |
- (snapshotsCount + sessionsCount == 0); |
- } |
- |
- /** |
- * Records a promo impression on a given section if necessary. |
- * @param {string} section Active section name to check. |
- */ |
- function promoUpdateImpressions(section) { |
- if (section == 'most_visited' && shouldPromoBeShownOnMostVisited()) |
- chrome.send('recordImpression', ['most_visited']); |
- else if (section == 'open_tabs' && shouldPromoBeShownOnOpenTabs()) |
- chrome.send('recordImpression', ['open_tabs']); |
- else if (section == 'open_tabs' && shouldPromoBeShownOnSync()) |
- chrome.send('recordImpression', ['sync_promo']); |
- } |
- |
- /** |
- * Updates the visibility on all promo-related items as necessary. |
- */ |
- function updatePromoVisibility() { |
- var mostVisitedEl = $('promo_message_on_most_visited'); |
- var openTabsVCEl = $('promo_vc_list'); |
- var syncPromoLegacyEl = $('promo_message_on_sync_promo_legacy'); |
- var syncPromoReceivedEl = $('promo_message_on_sync_promo_received'); |
- mostVisitedEl.style.display = |
- shouldPromoBeShownOnMostVisited() ? 'block' : 'none'; |
- syncPromoReceivedEl.style.display = |
- shouldPromoBeShownOnSync() ? 'block' : 'none'; |
- syncPromoLegacyEl.style.display = |
- shouldPromoBeShownOnSync() ? 'none' : 'block'; |
- openTabsVCEl.style.display = |
- (shouldPromoBeShownOnOpenTabs() && promoIsAllowedAsVirtualComputer) ? |
- 'block' : 'none'; |
- } |
- |
- /** |
- * Called from native. |
- * Clears the promotion. |
- */ |
- function clearPromotions() { |
- setPromotions({}); |
- } |
- |
- /** |
- * Set the element to a parsed and sanitized promotion HTML string. |
- * @param {Element} el The element to set the promotion string to. |
- * @param {string} html The promotion HTML string. |
- * @throws {Error} In case of non supported markup. |
- */ |
- function setPromotionHtml(el, html) { |
- if (!el) return; |
- el.innerHTML = ''; |
- if (!html) return; |
- var tags = ['BR', 'DIV', 'BUTTON', 'SPAN']; |
- var attrs = { |
- class: function(node, value) { return true; }, |
- style: function(node, value) { return true; }, |
- }; |
- try { |
- var fragment = parseHtmlSubset(html, tags, attrs); |
- el.appendChild(fragment); |
- } catch (err) { |
- console.error(err.toString()); |
- // Ignore all errors while parsing or setting the element. |
- } |
- } |
- |
- /** |
- * Called from native. |
- * Sets the text for all promo-related items, updates |
- * promo-send-email-target items to send email on click and |
- * updates the visibility of items. |
- * @param {Object} promotions Dictionary used to fill-in the text. |
- */ |
- function setPromotions(promotions) { |
- var mostVisitedEl = $('promo_message_on_most_visited'); |
- var openTabsEl = $('promo_message_on_open_tabs'); |
- var syncPromoReceivedEl = $('promo_message_on_sync_promo_received'); |
- |
- promoIsAllowed = !!promotions.promoIsAllowed; |
- promoIsAllowedOnMostVisited = !!promotions.promoIsAllowedOnMostVisited; |
- promoIsAllowedOnOpenTabs = !!promotions.promoIsAllowedOnOpenTabs; |
- promoIsAllowedAsVirtualComputer = !!promotions.promoIsAllowedAsVC; |
- |
- setPromotionHtml(mostVisitedEl, promotions.promoMessage); |
- setPromotionHtml(openTabsEl, promotions.promoMessage); |
- setPromotionHtml(syncPromoReceivedEl, promotions.promoMessageLong); |
- |
- promoInjectedComputerTitleText = promotions.promoVCTitle || ''; |
- promoInjectedComputerLastSyncedText = promotions.promoVCLastSynced || ''; |
- var openTabsVCTitleEl = $('promo_vc_title'); |
- if (openTabsVCTitleEl) |
- openTabsVCTitleEl.textContent = promoInjectedComputerTitleText; |
- var openTabsVCLastSyncEl = $('promo_vc_lastsync'); |
- if (openTabsVCLastSyncEl) |
- openTabsVCLastSyncEl.textContent = promoInjectedComputerLastSyncedText; |
- |
- if (promoIsAllowed) { |
- var promoButtonEls = |
- document.getElementsByClassName('promo-button'); |
- for (var i = 0, len = promoButtonEls.length; i < len; i++) { |
- promoButtonEls[i].onclick = executePromoAction; |
- addActiveTouchListener(promoButtonEls[i], 'promo-button-active'); |
- } |
- } |
- updatePromoVisibility(); |
- } |
- |
- /** |
- * On-click handler for promo email targets. |
- * Performs the promo action "send email". |
- * @param {Object} evt User interface event that triggered the action. |
- */ |
- function executePromoAction(evt) { |
- evt.preventDefault(); |
- chrome.send('promoActionTriggered'); |
- } |
- |
- /** |
- * Called by the browser when a context menu has been selected. |
- * |
- * @param {number} itemId The id of the item that was selected, as specified |
- * when chrome.send('showContextMenu') was called. |
- */ |
- function onCustomMenuSelected(itemId) { |
- if (contextMenuUrl != null) { |
- switch (itemId) { |
- case ContextMenuItemIds.BOOKMARK_OPEN_IN_NEW_TAB: |
- case ContextMenuItemIds.BOOKMARK_OPEN_IN_INCOGNITO_TAB: |
- chrome.send('openedBookmark'); |
- break; |
- |
- case ContextMenuItemIds.MOST_VISITED_OPEN_IN_NEW_TAB: |
- case ContextMenuItemIds.MOST_VISITED_OPEN_IN_INCOGNITO_TAB: |
- chrome.send('openedMostVisited'); |
- if (contextMenuItem) { |
- chrome.send('metricsHandler:recordInHistogram', |
- ['NewTabPage.MostVisited', |
- contextMenuItem.mostVisitedIndex, |
- 8]); |
- } |
- break; |
- |
- case ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_NEW_TAB: |
- case ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_INCOGNITO_TAB: |
- chrome.send('openedRecentlyClosed'); |
- break; |
- } |
- } |
- |
- switch (itemId) { |
- case ContextMenuItemIds.BOOKMARK_OPEN_IN_NEW_TAB: |
- case ContextMenuItemIds.MOST_VISITED_OPEN_IN_NEW_TAB: |
- case ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_NEW_TAB: |
- if (contextMenuUrl != null) |
- chrome.send('openInNewTab', [contextMenuUrl]); |
- break; |
- |
- case ContextMenuItemIds.BOOKMARK_OPEN_IN_INCOGNITO_TAB: |
- case ContextMenuItemIds.MOST_VISITED_OPEN_IN_INCOGNITO_TAB: |
- case ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_INCOGNITO_TAB: |
- if (contextMenuUrl != null) |
- chrome.send('openInIncognitoTab', [contextMenuUrl]); |
- break; |
- |
- case ContextMenuItemIds.BOOKMARK_EDIT: |
- if (contextMenuItem != null) |
- chrome.send('editBookmark', [contextMenuItem.id]); |
- break; |
- |
- case ContextMenuItemIds.BOOKMARK_DELETE: |
- if (contextMenuUrl != null) |
- chrome.send('deleteBookmark', [contextMenuItem.id]); |
- break; |
- |
- case ContextMenuItemIds.MOST_VISITED_REMOVE: |
- if (contextMenuUrl != null) |
- chrome.send('blacklistURLFromMostVisited', [contextMenuUrl]); |
- break; |
- |
- case ContextMenuItemIds.BOOKMARK_SHORTCUT: |
- if (contextMenuUrl != null) |
- chrome.send('createHomeScreenBookmarkShortcut', [contextMenuItem.id]); |
- break; |
- |
- case ContextMenuItemIds.RECENTLY_CLOSED_REMOVE: |
- chrome.send('clearRecentlyClosed'); |
- break; |
- |
- case ContextMenuItemIds.FOREIGN_SESSIONS_REMOVE: |
- if (contextMenuItem != null) { |
- chrome.send( |
- 'deleteForeignSession', [contextMenuItem.sessionTag]); |
- chrome.send('getForeignSessions'); |
- } |
- break; |
- |
- case ContextMenuItemIds.PROMO_VC_SESSION_REMOVE: |
- chrome.send('promoDisabled'); |
- break; |
- |
- default: |
- log.error('Unknown context menu selected id=' + itemId); |
- break; |
- } |
- } |
- |
- /** |
- * Generates the full bookmark folder hierarchy and populates the scrollable |
- * title element. |
- * |
- * @param {Element} wrapperEl The wrapper element containing the scrollable |
- * title. |
- * @param {string} data The current bookmark folder node. |
- * @param {Array.<Object>=} opt_ancestry The folder ancestry of the current |
- * bookmark folder. The list is ordered in order of closest descendant |
- * (the root will always be the last node). The definition of each |
- * element is: |
- * - id {number}: Unique ID of the folder (N/A for root node). |
- * - name {string}: Name of the folder (N/A for root node). |
- * - root {boolean}: Whether this is the root node. |
- */ |
- function setBookmarkTitleHierarchy(wrapperEl, data, opt_ancestry) { |
- var title = wrapperEl.getElementsByClassName('section-title')[0]; |
- title.innerHTML = ''; |
- if (opt_ancestry) { |
- for (var i = opt_ancestry.length - 1; i >= 0; i--) { |
- var titleCrumb = createBookmarkTitleCrumb_(opt_ancestry[i]); |
- title.appendChild(titleCrumb); |
- title.appendChild(createDiv('bookmark-separator')); |
- } |
- } |
- var titleCrumb = createBookmarkTitleCrumb_(data); |
- titleCrumb.classList.add('title-crumb-active'); |
- title.appendChild(titleCrumb); |
- |
- // Ensure the last crumb is as visible as possible. |
- var windowWidth = |
- wrapperEl.getElementsByClassName('section-title-mask')[0].offsetWidth; |
- var crumbWidth = titleCrumb.offsetWidth; |
- var leftOffset = titleCrumb.offsetLeft; |
- |
- var shiftLeft = windowWidth - crumbWidth - leftOffset; |
- if (shiftLeft < 0) { |
- if (crumbWidth > windowWidth) |
- shifLeft = -leftOffset; |
- |
- // Queue up the scrolling initially to allow for the mask element to |
- // be placed into the dom and it's size correctly calculated. |
- setTimeout(function() { |
- handleTitleScroll(wrapperEl, shiftLeft); |
- }, 0); |
- } else { |
- handleTitleScroll(wrapperEl, 0); |
- } |
- } |
- |
- /** |
- * Creates a clickable bookmark title crumb. |
- * @param {Object} data The crumb data (see setBookmarkTitleHierarchy for |
- * definition of the data object). |
- * @return {Element} The clickable title crumb element. |
- * @private |
- */ |
- function createBookmarkTitleCrumb_(data) { |
- var titleCrumb = createDiv('title-crumb'); |
- if (data.root) { |
- titleCrumb.innerText = templateData.bookmarkstitle; |
- } else { |
- titleCrumb.innerText = data.title; |
- } |
- titleCrumb.addEventListener('click', function(evt) { |
- browseToBookmarkFolder(data.root ? '0' : data.id); |
- }); |
- return titleCrumb; |
- } |
- |
- /** |
- * Handles scrolling a title element. |
- * @param {Element} wrapperEl The wrapper element containing the scrollable |
- * title. |
- * @param {number} scrollPosition The position to be scrolled to. |
- */ |
- function handleTitleScroll(wrapperEl, scrollPosition) { |
- var overflowLeftMask = |
- wrapperEl.getElementsByClassName('overflow-left-mask')[0]; |
- var overflowRightMask = |
- wrapperEl.getElementsByClassName('overflow-right-mask')[0]; |
- var title = wrapperEl.getElementsByClassName('section-title')[0]; |
- var titleMask = wrapperEl.getElementsByClassName('section-title-mask')[0]; |
- var titleWidth = title.scrollWidth; |
- var containerWidth = titleMask.offsetWidth; |
- |
- var maxRightScroll = containerWidth - titleWidth; |
- var boundedScrollPosition = |
- Math.max(maxRightScroll, Math.min(scrollPosition, 0)); |
- |
- overflowLeftMask.style.opacity = |
- Math.min( |
- 1, |
- (Math.max(0, -boundedScrollPosition)) + 10 / 30); |
- |
- overflowRightMask.style.opacity = |
- Math.min( |
- 1, |
- (Math.max(0, boundedScrollPosition - maxRightScroll) + 10) / 30); |
- |
- // Set the position of the title. |
- if (titleWidth < containerWidth) { |
- // left-align on LTR and right-align on RTL. |
- title.style.left = ''; |
- } else { |
- title.style.left = boundedScrollPosition + 'px'; |
- } |
- } |
- |
- /** |
- * Initializes a scrolling title element. |
- * @param {Element} wrapperEl The wrapper element of the scrolling title. |
- */ |
- function initializeTitleScroller(wrapperEl) { |
- var title = wrapperEl.getElementsByClassName('section-title')[0]; |
- |
- var inTitleScroll = false; |
- var startingScrollPosition; |
- var startingOffset; |
- wrapperEl.addEventListener(PRESS_START_EVT, function(evt) { |
- inTitleScroll = true; |
- startingScrollPosition = getTouchEventX(evt); |
- startingOffset = title.offsetLeft; |
- }); |
- document.body.addEventListener(PRESS_STOP_EVT, function(evt) { |
- if (!inTitleScroll) |
- return; |
- inTitleScroll = false; |
- }); |
- document.body.addEventListener(PRESS_MOVE_EVT, function(evt) { |
- if (!inTitleScroll) |
- return; |
- handleTitleScroll( |
- wrapperEl, |
- startingOffset - (startingScrollPosition - getTouchEventX(evt))); |
- evt.stopPropagation(); |
- }); |
- } |
- |
- /** |
- * Handles updates from the underlying bookmark model (calls originate |
- * in the WebUI handler for bookmarks). |
- * |
- * @param {Object} status Describes the type of change that occurred. Can |
- * contain the following fields: |
- * - parent_id {string}: Unique id of the parent that was affected by |
- * the change. If the parent is the bookmark |
- * bar, then the ID will be 'root'. |
- * - node_id {string}: The unique ID of the node that was affected. |
- */ |
- function bookmarkChanged(status) { |
- if (status) { |
- var affectedParentNode = status['parent_id']; |
- var affectedNodeId = status['node_id']; |
- var shouldUpdate = (bookmarkFolderId == affectedParentNode || |
- bookmarkFolderId == affectedNodeId); |
- if (shouldUpdate) |
- setCurrentBookmarkFolderData(bookmarkFolderId); |
- } else { |
- // This typically happens when extensive changes could have happened to |
- // the model, such as initial load, import and sync. |
- setCurrentBookmarkFolderData(bookmarkFolderId); |
- } |
- } |
- |
- /** |
- * Loads the bookarks data for a given folder. |
- * |
- * @param {string|number} folderId The ID of the folder to load (or null if |
- * it should load the root folder). |
- */ |
- function setCurrentBookmarkFolderData(folderId) { |
- if (folderId != null) { |
- chrome.send('getBookmarks', [folderId]); |
- } else { |
- chrome.send('getBookmarks'); |
- } |
- try { |
- if (folderId == null) { |
- localStorage.removeItem(DEFAULT_BOOKMARK_FOLDER_KEY); |
- } else { |
- localStorage.setItem(DEFAULT_BOOKMARK_FOLDER_KEY, folderId); |
- } |
- } catch (e) {} |
- } |
- |
- /** |
- * Navigates to the specified folder and handles loading the required data. |
- * Ensures the current folder can be navigated back to using the browser |
- * controls. |
- * |
- * @param {string|number} folderId The ID of the folder to navigate to. |
- */ |
- function browseToBookmarkFolder(folderId) { |
- history.pushState( |
- {folderId: folderId, selectedPaneIndex: currentPaneIndex}, |
- null, null); |
- setCurrentBookmarkFolderData(folderId); |
- } |
- |
- /** |
- * Called to inform the page of the current sync status. If the state has |
- * changed from disabled to enabled, it changes the current and default |
- * bookmark section to the root directory. This makes desktop bookmarks are |
- * visible. |
- */ |
- function setSyncEnabled(enabled) { |
- try { |
- if (syncEnabled != undefined && syncEnabled == enabled) { |
- // The value didn't change |
- return; |
- } |
- syncEnabled = enabled; |
- |
- if (enabled) { |
- if (!localStorage.getItem(SYNC_ENABLED_KEY)) { |
- localStorage.setItem(SYNC_ENABLED_KEY, 'true'); |
- setCurrentBookmarkFolderData('0'); |
- } |
- } else { |
- localStorage.removeItem(SYNC_ENABLED_KEY); |
- } |
- updatePromoVisibility(); |
- |
- if (bookmarkData) { |
- // Bookmark data can now be displayed (or needs to be refiltered) |
- bookmarks(bookmarkData); |
- } |
- |
- updateSyncEmptyState(); |
- } catch (e) {} |
- } |
- |
- /** |
- * Handles adding or removing the 'nothing to see here' text from the session |
- * list depending on the state of snapshots and sessions. |
- * |
- * @param {boolean} Whether the call is occuring because of a schedule |
- * timeout. |
- */ |
- function updateSyncEmptyState(timeout) { |
- if (syncState == SyncState.DISPLAYING_LOADING && !timeout) { |
- // Make sure 'Loading...' is displayed long enough |
- return; |
- } |
- |
- var openTabsList = findList('open_tabs'); |
- var snapshotsList = findList('snapshots'); |
- var syncPromo = $('sync_promo'); |
- var syncLoading = $('sync_loading'); |
- var syncEnableSync = $('sync_enable_sync'); |
- |
- if (syncEnabled == undefined || |
- currentSnapshots == null || |
- currentSessions == null) { |
- if (syncState == SyncState.INITIAL) { |
- // Wait one second for sync data to come in before displaying loading |
- // text. |
- syncState = SyncState.WAITING_FOR_DATA; |
- syncTimerId = setTimeout(function() { updateSyncEmptyState(true); }, |
- SYNC_INITIAL_LOAD_TIMEOUT); |
- } else if (syncState == SyncState.WAITING_FOR_DATA && timeout) { |
- // We've waited for the initial info timeout to pass and still don't |
- // have data. So, display loading text so the user knows something is |
- // happening. |
- syncState = SyncState.DISPLAYING_LOADING; |
- syncLoading.style.display = '-webkit-box'; |
- centerEmptySections(syncLoading); |
- syncTimerId = setTimeout(function() { updateSyncEmptyState(true); }, |
- SYNC_LOADING_TIMEOUT); |
- } else if (syncState == SyncState.DISPLAYING_LOADING) { |
- // Allow the Loading... text to go away once data comes in |
- syncState = SyncState.DISPLAYED_LOADING; |
- } |
- return; |
- } |
- |
- if (syncTimerId != -1) { |
- clearTimeout(syncTimerId); |
- syncTimerId = -1; |
- } |
- syncState = SyncState.LOADED; |
- |
- // Hide everything by default, display selectively below |
- syncEnableSync.style.display = 'none'; |
- syncLoading.style.display = 'none'; |
- syncPromo.style.display = 'none'; |
- |
- var snapshotsCount = |
- currentSnapshots == null ? 0 : currentSnapshots.length; |
- var sessionsCount = currentSessions == null ? 0 : currentSessions.length; |
- |
- if (!syncEnabled) { |
- syncEnableSync.style.display = '-webkit-box'; |
- centerEmptySections(syncEnableSync); |
- } else if (sessionsCount + snapshotsCount == 0) { |
- syncPromo.style.display = '-webkit-box'; |
- centerEmptySections(syncPromo); |
- } else { |
- openTabsList.style.display = sessionsCount == 0 ? 'none' : 'block'; |
- snapshotsList.style.display = snapshotsCount == 0 ? 'none' : 'block'; |
- } |
- updatePromoVisibility(); |
- } |
- |
- /** |
- * Called externally when updated snapshot data is available. |
- * |
- * @param {Object} data The snapshot data |
- */ |
- function snapshots(data) { |
- var list = findList('snapshots'); |
- list.innerHTML = ''; |
- |
- currentSnapshots = data; |
- updateSyncEmptyState(); |
- |
- if (!data || data.length == 0) |
- return; |
- |
- data.sort(function(a, b) { |
- return b.createTime - a.createTime; |
- }); |
- |
- // Create the main container |
- var snapshotsEl = createElement('div'); |
- list.appendChild(snapshotsEl); |
- |
- // Create the header container |
- var headerEl = createDiv('session-header'); |
- snapshotsEl.appendChild(headerEl); |
- |
- // Create the documents container |
- var docsEl = createDiv('session-children-container'); |
- snapshotsEl.appendChild(docsEl); |
- |
- // Create the container for the title & icon |
- var headerInnerEl = createDiv('list-item standard-divider'); |
- addActiveTouchListener(headerInnerEl, ACTIVE_LIST_ITEM_CSS_CLASS); |
- headerEl.appendChild(headerInnerEl); |
- |
- // Create the header icon |
- headerInnerEl.appendChild(createDiv('session-icon documents')); |
- |
- // Create the header title |
- var titleContainer = createElement('span', 'title'); |
- headerInnerEl.appendChild(titleContainer); |
- var title = createDiv('session-name'); |
- title.textContent = templateData.receivedDocuments; |
- titleContainer.appendChild(title); |
- |
- // Add support for expanding and collapsing the children |
- var expando = createDiv(); |
- var expandoFunction = createExpandoFunction(expando, docsEl); |
- headerInnerEl.addEventListener('click', expandoFunction); |
- headerEl.appendChild(expando); |
- |
- // Support for actually opening the document |
- var snapshotClickCallback = function(item) { |
- if (!item) |
- return; |
- if (item.snapshotId) { |
- window.location = 'chrome://snapshot/' + item.snapshotId; |
- } else if (item.printJobId) { |
- window.location = 'chrome://printjob/' + item.printJobId; |
- } else { |
- window.location = item.url; |
- } |
- } |
- |
- // Finally, add the list of documents |
- populateData(docsEl, SectionType.SNAPSHOTS, data, |
- makeListEntryItem, snapshotClickCallback); |
- } |
- |
- /** |
- * Create a function to handle expanding and collapsing a section |
- * |
- * @param {Element} expando The expando div |
- * @param {Element} element The element to expand and collapse |
- * @return {function()} A callback function that should be invoked when the |
- * expando is clicked |
- */ |
- function createExpandoFunction(expando, element) { |
- expando.className = 'expando open'; |
- return function() { |
- if (element.style.height != '0px') { |
- // It seems that '-webkit-transition' only works when explicit pixel |
- // values are used. |
- setTimeout(function() { |
- // If this is the first time to collapse the list, store off the |
- // expanded height and also set the height explicitly on the style. |
- if (!element.expandedHeight) { |
- element.expandedHeight = |
- element.clientHeight + 'px'; |
- element.style.height = element.expandedHeight; |
- } |
- // Now set the height to 0. Note, this is also done in a callback to |
- // give the layout engine a chance to run after possibly setting the |
- // height above. |
- setTimeout(function() { |
- element.style.height = '0px'; |
- }, 0); |
- }, 0); |
- expando.className = 'expando closed'; |
- } else { |
- element.style.height = element.expandedHeight; |
- expando.className = 'expando open'; |
- } |
- } |
- } |
- |
- /** |
- * Initializes the promo_vc_list div to look like a foreign session |
- * with a desktop. |
- */ |
- function createPromoVirtualComputers() { |
- var list = findList('promo_vc'); |
- list.innerHTML = ''; |
- |
- // Set up the container and the "virtual computer" session header. |
- var sessionEl = createDiv(); |
- list.appendChild(sessionEl); |
- var sessionHeader = createDiv('session-header'); |
- sessionEl.appendChild(sessionHeader); |
- |
- // Set up the session children container and the promo as a child. |
- var sessionChildren = createDiv('session-children-container'); |
- var promoMessage = createDiv('promo-message'); |
- promoMessage.id = 'promo_message_on_open_tabs'; |
- sessionChildren.appendChild(promoMessage); |
- sessionEl.appendChild(sessionChildren); |
- |
- // Add support for expanding and collapsing the children. |
- var expando = createDiv(); |
- var expandoFunction = createExpandoFunction(expando, sessionChildren); |
- |
- // Fill-in the contents of the "virtual computer" session header. |
- var headerList = [{ |
- 'title': promoInjectedComputerTitleText, |
- 'titleId': 'promo_vc_title', |
- 'userVisibleTimestamp': promoInjectedComputerLastSyncedText, |
- 'userVisibleTimestampId': 'promo_vc_lastsync', |
- 'iconStyle': 'laptop' |
- }]; |
- |
- populateData(sessionHeader, SectionType.PROMO_VC_SESSION_HEADER, headerList, |
- makeForeignSessionListEntry, expandoFunction); |
- sessionHeader.appendChild(expando); |
- } |
- |
- /** |
- * Called externally when updated synced sessions data is available. |
- * |
- * @param {Object} data The snapshot data |
- */ |
- function setForeignSessions(data, tabSyncEnabled) { |
- var list = findList('open_tabs'); |
- list.innerHTML = ''; |
- |
- currentSessions = data; |
- updateSyncEmptyState(); |
- |
- // Sort the windows within each client such that more recently |
- // modified windows appear first. |
- data.forEach(function(client) { |
- if (client.windows != null) { |
- client.windows.sort(function(a, b) { |
- if (b.timestamp == null) { |
- return -1; |
- } else if (a.timestamp == null) { |
- return 1; |
- } else { |
- return b.timestamp - a.timestamp; |
- } |
- }); |
- } |
- }); |
- |
- // Sort so more recently modified clients appear first. |
- data.sort(function(aClient, bClient) { |
- var aWindows = aClient.windows; |
- var bWindows = bClient.windows; |
- if (bWindows == null || bWindows.length == 0 || |
- bWindows[0].timestamp == null) { |
- return -1; |
- } else if (aWindows == null || aWindows.length == 0 || |
- aWindows[0].timestamp == null) { |
- return 1; |
- } else { |
- return bWindows[0].timestamp - aWindows[0].timestamp; |
- } |
- }); |
- |
- data.forEach(function(client, clientNum) { |
- |
- var windows = client.windows; |
- if (windows == null || windows.length == 0) |
- return; |
- |
- // Set up the container for the session header |
- var sessionEl = createElement('div'); |
- list.appendChild(sessionEl); |
- var sessionHeader = createDiv('session-header'); |
- sessionEl.appendChild(sessionHeader); |
- |
- // Set up the container for the session children |
- var sessionChildren = createDiv('session-children-container'); |
- sessionEl.appendChild(sessionChildren); |
- |
- var clientName = 'Client ' + clientNum; |
- if (client.name) |
- clientName = client.name; |
- |
- var iconStyle; |
- var deviceType = client.deviceType; |
- if (deviceType == 'win' || |
- deviceType == 'macosx' || |
- deviceType == 'linux' || |
- deviceType == 'chromeos' || |
- deviceType == 'other') { |
- iconStyle = 'laptop'; |
- } else if (deviceType == 'phone') { |
- iconStyle = 'phone'; |
- } else if (deviceType == 'tablet') { |
- iconStyle = 'tablet'; |
- } else { |
- console.error('Unknown sync device type found: ', deviceType); |
- iconStyle = 'laptop'; |
- } |
- var headerList = [{ |
- 'title': clientName, |
- 'userVisibleTimestamp': windows[0].userVisibleTimestamp, |
- 'iconStyle': iconStyle, |
- 'sessionTag': client.tag, |
- }]; |
- |
- var expando = createDiv(); |
- var expandoFunction = createExpandoFunction(expando, sessionChildren); |
- populateData(sessionHeader, SectionType.FOREIGN_SESSION_HEADER, |
- headerList, makeForeignSessionListEntry, expandoFunction); |
- sessionHeader.appendChild(expando); |
- |
- // Populate the session children container |
- var openTabsList = new Array(); |
- for (var winNum = 0; winNum < windows.length; winNum++) { |
- win = windows[winNum]; |
- var tabs = win.tabs; |
- for (var tabNum = 0; tabNum < tabs.length; tabNum++) { |
- var tab = tabs[tabNum]; |
- // If this is the last tab in the window and there are more windows, |
- // use a section divider. |
- var needSectionDivider = |
- (tabNum + 1 == tabs.length) && (winNum + 1 < windows.length); |
- tab.icon = tab.icon || 'chrome://favicon/size/16@1x/' + tab.url; |
- |
- openTabsList.push({ |
- timestamp: tab.timestamp, |
- title: tab.title, |
- url: tab.url, |
- sessionTag: client.tag, |
- winNum: winNum, |
- sessionId: tab.sessionId, |
- icon: tab.icon, |
- iconSize: 16, |
- divider: needSectionDivider ? 'section' : 'standard', |
- }); |
- } |
- } |
- var tabCallback = function(item, evt) { |
- var buttonIndex = 0; |
- var altKeyPressed = false; |
- var ctrlKeyPressed = false; |
- var metaKeyPressed = false; |
- var shiftKeyPressed = false; |
- if (evt instanceof MouseEvent) { |
- buttonIndex = evt.button; |
- altKeyPressed = evt.altKey; |
- ctrlKeyPressed = evt.ctrlKey; |
- metaKeyPressed = evt.metaKey; |
- shiftKeyPressed = evt.shiftKey; |
- } |
- chrome.send('openedForeignSession'); |
- chrome.send('openForeignSession', [String(item.sessionTag), |
- String(item.winNum), String(item.sessionId), buttonIndex, |
- altKeyPressed, ctrlKeyPressed, metaKeyPressed, shiftKeyPressed]); |
- }; |
- populateData(sessionChildren, SectionType.FOREIGN_SESSION, openTabsList, |
- makeListEntryItem, tabCallback); |
- }); |
- } |
- |
- /** |
- * Updates the dominant favicon color for a given index. |
- * |
- * @param {number} index The index of the favicon whose dominant color is |
- * being specified. |
- * @param {string} color The string encoded color. |
- */ |
- function setFaviconDominantColor(index, color) { |
- var colorstrips = document.getElementsByClassName('colorstrip-' + index); |
- for (var i = 0; i < colorstrips.length; i++) |
- colorstrips[i].style.background = color; |
- |
- var id = 'fold_' + index; |
- var fold = $(id); |
- if (!fold) |
- return; |
- var zoom = window.getComputedStyle(fold).zoom; |
- var scale = 1 / window.getComputedStyle(fold).zoom; |
- |
- // The width/height of the canvas. Set to 24 so it looks good across all |
- // resolutions. |
- var cw = 24; |
- var ch = 24; |
- |
- // Get the fold canvas and create a path for the fold shape |
- var ctx = document.getCSSCanvasContext( |
- '2d', 'fold_' + index, cw * scale, ch * scale); |
- ctx.beginPath(); |
- ctx.moveTo(0, 0); |
- ctx.lineTo(0, ch * 0.75 * scale); |
- ctx.quadraticCurveTo( |
- 0, ch * scale, |
- cw * .25 * scale, ch * scale); |
- ctx.lineTo(cw * scale, ch * scale); |
- ctx.closePath(); |
- |
- // Create a gradient for the fold and fill it |
- var gradient = ctx.createLinearGradient(cw * scale, 0, 0, ch * scale); |
- if (color.indexOf('#') == 0) { |
- var r = parseInt(color.substring(1, 3), 16); |
- var g = parseInt(color.substring(3, 5), 16); |
- var b = parseInt(color.substring(5, 7), 16); |
- gradient.addColorStop(0, 'rgba(' + r + ', ' + g + ', ' + b + ', 0.6)'); |
- } else { |
- // assume the color is in the 'rgb(#, #, #)' format |
- var rgbBase = color.substring(4, color.length - 1); |
- gradient.addColorStop(0, 'rgba(' + rgbBase + ', 0.6)'); |
- } |
- gradient.addColorStop(1, color); |
- ctx.fillStyle = gradient; |
- ctx.fill(); |
- |
- // Stroke the fold |
- ctx.lineWidth = Math.floor(scale); |
- ctx.strokeStyle = color; |
- ctx.stroke(); |
- ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)'; |
- ctx.stroke(); |
- |
- } |
- |
- /** |
- * Finds the list element corresponding to the given name. |
- * @param {string} name The name prefix of the DOM element (<prefix>_list). |
- * @return {Element} The list element corresponding with the name. |
- */ |
- function findList(name) { |
- return $(name + '_list'); |
- } |
- |
- /** |
- * Render the given data into the given list, and hide or show the entire |
- * container based on whether there are any elements. The decorator function |
- * is used to create the element to be inserted based on the given data |
- * object. |
- * |
- * @param {holder} The dom element that the generated list items will be put |
- * into. |
- * @param {SectionType} section The section that data is for. |
- * @param {Object} data The data to be populated. |
- * @param {function(Object, boolean)} decorator The function that will |
- * handle decorating each item in the data. |
- * @param {function(Object, Object)} opt_clickCallback The function that is |
- * called when the item is clicked. |
- */ |
- function populateData(holder, section, data, decorator, |
- opt_clickCallback) { |
- // Empty other items in the list, if present. |
- holder.innerHTML = ''; |
- var fragment = document.createDocumentFragment(); |
- if (!data || data.length == 0) { |
- fragment.innerHTML = ''; |
- } else { |
- data.forEach(function(item) { |
- var el = decorator(item, opt_clickCallback); |
- el.setAttribute(SECTION_KEY, section); |
- el.id = section + fragment.childNodes.length; |
- fragment.appendChild(el); |
- }); |
- } |
- holder.appendChild(fragment); |
- if (holder.classList.contains(GRID_CSS_CLASS)) |
- centerGrid(holder); |
- centerEmptySections(holder); |
- } |
- |
- /** |
- * Given an element containing a list of child nodes arranged in |
- * a grid, this will center the grid in the window based on the |
- * remaining space. |
- * @param {Element} el Container holding the grid cell items. |
- */ |
- function centerGrid(el) { |
- var childEl = el.firstChild; |
- if (!childEl) |
- return; |
- |
- // Find the element to actually set the margins on. |
- var toCenter = el; |
- var curEl = toCenter; |
- while (curEl && curEl.classList) { |
- if (curEl.classList.contains(GRID_CENTER_CSS_CLASS)) { |
- toCenter = curEl; |
- break; |
- } |
- curEl = curEl.parentNode; |
- } |
- var setItemMargins = el.classList.contains(GRID_SET_ITEM_MARGINS); |
- var itemWidth = getItemWidth(childEl, setItemMargins); |
- var windowWidth = document.documentElement.offsetWidth; |
- if (itemWidth >= windowWidth) { |
- toCenter.style.paddingLeft = '0'; |
- toCenter.style.paddingRight = '0'; |
- } else { |
- var numColumns = el.getAttribute(GRID_COLUMNS); |
- if (numColumns) { |
- numColumns = parseInt(numColumns); |
- } else { |
- numColumns = Math.floor(windowWidth / itemWidth); |
- } |
- |
- if (setItemMargins) { |
- // In this case, try to size each item to fill as much space as |
- // possible. |
- var gutterSize = |
- (windowWidth - itemWidth * numColumns) / (numColumns + 1); |
- var childLeftMargin = Math.round(gutterSize / 2); |
- var childRightMargin = Math.floor(gutterSize - childLeftMargin); |
- var children = el.childNodes; |
- for (var i = 0; i < children.length; i++) { |
- children[i].style.marginLeft = childLeftMargin + 'px'; |
- children[i].style.marginRight = childRightMargin + 'px'; |
- } |
- itemWidth += childLeftMargin + childRightMargin; |
- } |
- |
- var remainder = windowWidth - itemWidth * numColumns; |
- var leftPadding = Math.round(remainder / 2); |
- var rightPadding = Math.floor(remainder - leftPadding); |
- toCenter.style.paddingLeft = leftPadding + 'px'; |
- toCenter.style.paddingRight = rightPadding + 'px'; |
- |
- if (toCenter.classList.contains(GRID_SET_TOP_MARGIN_CLASS)) { |
- var childStyle = window.getComputedStyle(childEl); |
- var childLeftPadding = parseInt( |
- childStyle.getPropertyValue('padding-left')); |
- toCenter.style.paddingTop = |
- (childLeftMargin + childLeftPadding + leftPadding) + 'px'; |
- } |
- } |
- } |
- |
- /** |
- * Finds and centers all child grid elements for a given node (the grids |
- * do not need to be direct descendants and can reside anywhere in the node |
- * hierarchy). |
- * @param {Element} el The node containing the grid child nodes. |
- */ |
- function centerChildGrids(el) { |
- var grids = el.getElementsByClassName(GRID_CSS_CLASS); |
- for (var i = 0; i < grids.length; i++) |
- centerGrid(grids[i]); |
- } |
- |
- /** |
- * Finds and vertically centers all 'empty' elements for a given node (the |
- * 'empty' elements do not need to be direct descendants and can reside |
- * anywhere in the node hierarchy). |
- * @param {Element} el The node containing the 'empty' child nodes. |
- */ |
- function centerEmptySections(el) { |
- if (el.classList && |
- el.classList.contains(CENTER_EMPTY_CONTAINER_CSS_CLASS)) { |
- centerEmptySection(el); |
- } |
- var empties = el.getElementsByClassName(CENTER_EMPTY_CONTAINER_CSS_CLASS); |
- for (var i = 0; i < empties.length; i++) { |
- centerEmptySection(empties[i]); |
- } |
- } |
- |
- /** |
- * Set the top of the given element to the top of the parent and set the |
- * height to (bottom of document - top). |
- * |
- * @param {Element} el Container holding the centered content. |
- */ |
- function centerEmptySection(el) { |
- var parent = el.parentNode; |
- var top = parent.offsetTop; |
- var bottom = ( |
- document.documentElement.offsetHeight - getButtonBarPadding()); |
- el.style.height = (bottom - top) + 'px'; |
- el.style.top = top + 'px'; |
- } |
- |
- /** |
- * Finds the index of the panel specified by its prefix. |
- * @param {string} The string prefix for the panel. |
- * @return {number} The index of the panel. |
- */ |
- function getPaneIndex(panePrefix) { |
- var pane = $(panePrefix + '_container'); |
- |
- if (pane != null) { |
- var index = panes.indexOf(pane); |
- |
- if (index >= 0) |
- return index; |
- } |
- return 0; |
- } |
- |
- /** |
- * Finds the index of the panel specified by location hash. |
- * @return {number} The index of the panel. |
- */ |
- function getPaneIndexFromHash() { |
- var paneIndex; |
- if (window.location.hash == '#bookmarks') { |
- paneIndex = getPaneIndex('bookmarks'); |
- } else if (window.location.hash == '#bookmark_shortcut') { |
- paneIndex = getPaneIndex('bookmarks'); |
- } else if (window.location.hash == '#most_visited') { |
- paneIndex = getPaneIndex('most_visited'); |
- } else if (window.location.hash == '#open_tabs') { |
- paneIndex = getPaneIndex('open_tabs'); |
- } else if (window.location.hash == '#incognito') { |
- paneIndex = getPaneIndex('incognito'); |
- } else { |
- // Couldn't find a good section |
- paneIndex = -1; |
- } |
- return paneIndex; |
- } |
- |
- /** |
- * Selects a pane from the top level list (Most Visited, Bookmarks, etc...). |
- * @param {number} paneIndex The index of the pane to be selected. |
- * @return {boolean} Whether the selected pane has changed. |
- */ |
- function scrollToPane(paneIndex) { |
- var pane = panes[paneIndex]; |
- |
- if (pane == currentPane) |
- return false; |
- |
- var newHash = '#' + sectionPrefixes[paneIndex]; |
- // If updated hash matches the current one in the URL, we need to call |
- // updatePaneOnHash directly as updating the hash to the same value will |
- // not trigger the 'hashchange' event. |
- if (bookmarkShortcutMode || newHash == document.location.hash) |
- updatePaneOnHash(); |
- computeDynamicLayout(); |
- promoUpdateImpressions(sectionPrefixes[paneIndex]); |
- return true; |
- } |
- |
- /** |
- * Updates the pane based on the current hash. |
- */ |
- function updatePaneOnHash() { |
- var paneIndex = getPaneIndexFromHash(); |
- var pane = panes[paneIndex]; |
- |
- if (currentPane) |
- currentPane.classList.remove('selected'); |
- pane.classList.add('selected'); |
- currentPane = pane; |
- currentPaneIndex = paneIndex; |
- |
- setScrollTopForDocument(document, 0); |
- |
- var panelPrefix = sectionPrefixes[paneIndex]; |
- var title = templateData[panelPrefix + '_document_title']; |
- if (!title) |
- title = templateData['title']; |
- document.title = title; |
- |
- sendNTPTitleLoadedNotification(); |
- |
- // TODO (dtrainor): Could potentially add logic to reset the bookmark state |
- // if they are moving to that pane. This logic was in there before, but |
- // was removed due to the fact that we have to go to this pane as part of |
- // the history navigation. |
- } |
- |
- /** |
- * Adds a top level section to the NTP. |
- * @param {string} panelPrefix The prefix of the element IDs corresponding |
- * to the container of the content. |
- * @param {boolean=} opt_canBeDefault Whether this section can be marked as |
- * the default starting point for subsequent instances of the NTP. The |
- * default value for this is true. |
- */ |
- function addMainSection(panelPrefix) { |
- var paneEl = $(panelPrefix + '_container'); |
- var paneIndex = panes.push(paneEl) - 1; |
- sectionPrefixes.push(panelPrefix); |
- } |
- |
- /** |
- * Handles the dynamic layout of the components on the new tab page. Only |
- * layouts that require calculation based on the screen size should go in |
- * this function as it will be called during all resize changes |
- * (orientation, keyword being displayed). |
- */ |
- function computeDynamicLayout() { |
- // Update the scrolling titles to ensure they are not in a now invalid |
- // scroll position. |
- var titleScrollers = |
- document.getElementsByClassName('section-title-wrapper'); |
- for (var i = 0, len = titleScrollers.length; i < len; i++) { |
- var titleEl = |
- titleScrollers[i].getElementsByClassName('section-title')[0]; |
- handleTitleScroll( |
- titleScrollers[i], |
- titleEl.offsetLeft); |
- } |
- |
- updateMostVisitedStyle(); |
- updateMostVisitedHeight(); |
- } |
- |
- /** |
- * The centering of the 'recently closed' section is different depending on |
- * the orientation of the device. In landscape, it should be left-aligned |
- * with the 'most used' section. In portrait, it should be centered in the |
- * screen. |
- */ |
- function updateMostVisitedStyle() { |
- if (isTablet()) { |
- updateMostVisitedStyleTablet(); |
- } else { |
- updateMostVisitedStylePhone(); |
- } |
- } |
- |
- /** |
- * Updates the style of the most visited pane for the phone. |
- */ |
- function updateMostVisitedStylePhone() { |
- var mostVisitedList = $('most_visited_list'); |
- var childEl = mostVisitedList.firstChild; |
- if (!childEl) |
- return; |
- |
- // 'natural' height and width of the thumbnail |
- var thumbHeight = 72; |
- var thumbWidth = 108; |
- var labelHeight = 25; |
- var labelWidth = thumbWidth + 20; |
- var labelLeft = (thumbWidth - labelWidth) / 2; |
- var itemHeight = thumbHeight + labelHeight; |
- |
- // default vertical margin between items |
- var itemMarginTop = 0; |
- var itemMarginBottom = 0; |
- var itemMarginLeft = 20; |
- var itemMarginRight = 20; |
- |
- var listHeight = 0; |
- |
- var screenHeight = |
- document.documentElement.offsetHeight - |
- getButtonBarPadding(); |
- |
- if (isPortrait()) { |
- mostVisitedList.setAttribute(GRID_COLUMNS, '2'); |
- listHeight = screenHeight * .85; |
- // Ensure that listHeight is not too small and not too big. |
- listHeight = Math.max(listHeight, (itemHeight * 3) + 20); |
- listHeight = Math.min(listHeight, 420); |
- // Size for 3 rows (4 gutters) |
- itemMarginTop = (listHeight - (itemHeight * 3)) / 4; |
- } else { |
- mostVisitedList.setAttribute(GRID_COLUMNS, '3'); |
- listHeight = screenHeight; |
- |
- // If the screen height is less than targetHeight, scale the size of the |
- // thumbnails such that the margin between the thumbnails remains |
- // constant. |
- var targetHeight = 220; |
- if (screenHeight < targetHeight) { |
- var targetRemainder = targetHeight - 2 * (thumbHeight + labelHeight); |
- var scale = (screenHeight - 2 * labelHeight - |
- targetRemainder) / (2 * thumbHeight); |
- // update values based on scale |
- thumbWidth = Math.round(thumbWidth * scale); |
- thumbHeight = Math.round(thumbHeight * scale); |
- labelWidth = thumbWidth + 20; |
- itemHeight = thumbHeight + labelHeight; |
- } |
- |
- // scale the vertical margin such that the items fit perfectly on the |
- // screen |
- var remainder = screenHeight - (2 * itemHeight); |
- var margin = (remainder / 2); |
- margin = margin > 24 ? 24 : margin; |
- itemMarginTop = Math.round(margin / 2); |
- itemMarginBottom = Math.round(margin - itemMarginTop); |
- } |
- |
- mostVisitedList.style.minHeight = listHeight + 'px'; |
- |
- modifyCssRule('body[device="phone"] .thumbnail-cell', |
- 'height', itemHeight + 'px'); |
- modifyCssRule('body[device="phone"] #most_visited_list .thumbnail', |
- 'height', thumbHeight + 'px'); |
- modifyCssRule('body[device="phone"] #most_visited_list .thumbnail', |
- 'width', thumbWidth + 'px'); |
- modifyCssRule( |
- 'body[device="phone"] #most_visited_list .thumbnail-container', |
- 'height', thumbHeight + 'px'); |
- modifyCssRule( |
- 'body[device="phone"] #most_visited_list .thumbnail-container', |
- 'width', thumbWidth + 'px'); |
- modifyCssRule('body[device="phone"] #most_visited_list .title', |
- 'width', labelWidth + 'px'); |
- modifyCssRule('body[device="phone"] #most_visited_list .title', |
- 'left', labelLeft + 'px'); |
- modifyCssRule('body[device="phone"] #most_visited_list .inner-border', |
- 'height', thumbHeight - 2 + 'px'); |
- modifyCssRule('body[device="phone"] #most_visited_list .inner-border', |
- 'width', thumbWidth - 2 + 'px'); |
- |
- modifyCssRule('body[device="phone"] .thumbnail-cell', |
- 'margin-left', itemMarginLeft + 'px'); |
- modifyCssRule('body[device="phone"] .thumbnail-cell', |
- 'margin-right', itemMarginRight + 'px'); |
- modifyCssRule('body[device="phone"] .thumbnail-cell', |
- 'margin-top', itemMarginTop + 'px'); |
- modifyCssRule('body[device="phone"] .thumbnail-cell', |
- 'margin-bottom', itemMarginBottom + 'px'); |
- |
- centerChildGrids($('most_visited_container')); |
- } |
- |
- /** |
- * Updates the style of the most visited pane for the tablet. |
- */ |
- function updateMostVisitedStyleTablet() { |
- function setCenterIconGrid(el, set) { |
- if (set) { |
- el.classList.add(GRID_CENTER_CSS_CLASS); |
- } else { |
- el.classList.remove(GRID_CENTER_CSS_CLASS); |
- el.style.paddingLeft = '0px'; |
- el.style.paddingRight = '0px'; |
- } |
- } |
- var isPortrait = document.documentElement.offsetWidth < |
- document.documentElement.offsetHeight; |
- var mostVisitedContainer = $('most_visited_container'); |
- var mostVisitedList = $('most_visited_list'); |
- var recentlyClosedContainer = $('recently_closed_container'); |
- var recentlyClosedList = $('recently_closed_list'); |
- |
- setCenterIconGrid(mostVisitedContainer, !isPortrait); |
- setCenterIconGrid(mostVisitedList, isPortrait); |
- setCenterIconGrid(recentlyClosedContainer, isPortrait); |
- if (isPortrait) { |
- recentlyClosedList.classList.add(GRID_CSS_CLASS); |
- } else { |
- recentlyClosedList.classList.remove(GRID_CSS_CLASS); |
- } |
- |
- // Make the recently closed list visually left align with the most recently |
- // closed items in landscape mode. It will be reset by the grid centering |
- // in portrait mode. |
- if (!isPortrait) |
- recentlyClosedContainer.style.paddingLeft = '14px'; |
- } |
- |
- /** |
- * This handles updating some of the spacing to make the 'recently closed' |
- * section appear at the bottom of the page. |
- */ |
- function updateMostVisitedHeight() { |
- if (!isTablet()) |
- return; |
- // subtract away height of button bar |
- var windowHeight = document.documentElement.offsetHeight; |
- var padding = parseInt(window.getComputedStyle(document.body) |
- .getPropertyValue('padding-bottom')); |
- $('most_visited_container').style.minHeight = |
- (windowHeight - padding) + 'px'; |
- } |
- |
- /** |
- * Called by the native toolbar to open a different section. This handles |
- * updating the hash url which in turns makes a history entry. |
- * |
- * @param {string} section The section to switch to. |
- */ |
- var openSection = function(section) { |
- if (!scrollToPane(getPaneIndex(section))) |
- return; |
- // Update the url so the native toolbar knows the pane has changed and |
- // to create a history entry. |
- document.location.hash = '#' + section; |
- } |
- |
- ///////////////////////////////////////////////////////////////////////////// |
- // NTP Scoped Window Event Listeners. |
- ///////////////////////////////////////////////////////////////////////////// |
- |
- /** |
- * Handles history on pop state changes. |
- */ |
- function onPopStateHandler(event) { |
- if (event.state != null) { |
- var evtState = event.state; |
- // Navigate back to the previously selected panel and ensure the same |
- // bookmarks are loaded. |
- var selectedPaneIndex = evtState.selectedPaneIndex == undefined ? |
- 0 : evtState.selectedPaneIndex; |
- |
- scrollToPane(selectedPaneIndex); |
- setCurrentBookmarkFolderData(evtState.folderId); |
- } else { |
- // When loading the page, replace the default state with one that |
- // specifies the default panel loaded via localStorage as well as the |
- // default bookmark folder. |
- history.replaceState( |
- {folderId: bookmarkFolderId, selectedPaneIndex: currentPaneIndex}, |
- null, null); |
- } |
- } |
- |
- /** |
- * Handles window resize events. |
- */ |
- function windowResizeHandler() { |
- // Scroll to the current pane to refactor all the margins and offset. |
- scrollToPane(currentPaneIndex); |
- computeDynamicLayout(); |
- // Center the padding for each of the grid views. |
- centerChildGrids(document); |
- centerEmptySections(document); |
- } |
- |
- /* |
- * We implement the context menu ourselves. |
- */ |
- function contextMenuHandler(evt) { |
- var section = SectionType.UNKNOWN; |
- contextMenuUrl = null; |
- contextMenuItem = null; |
- // The node with a menu have been tagged with their section and url. |
- // Let's find these tags. |
- var node = evt.target; |
- while (node) { |
- if (section == SectionType.UNKNOWN && |
- node.getAttribute && |
- node.getAttribute(SECTION_KEY) != null) { |
- section = node.getAttribute(SECTION_KEY); |
- if (contextMenuUrl != null) |
- break; |
- } |
- if (contextMenuUrl == null) { |
- contextMenuUrl = node.getAttribute(CONTEXT_MENU_URL_KEY); |
- contextMenuItem = node.contextMenuItem; |
- if (section != SectionType.UNKNOWN) |
- break; |
- } |
- node = node.parentNode; |
- } |
- |
- var menuOptions; |
- |
- if (section == SectionType.BOOKMARKS && |
- !contextMenuItem.folder && !isIncognito) { |
- menuOptions = [ |
- [ |
- ContextMenuItemIds.BOOKMARK_OPEN_IN_NEW_TAB, |
- templateData.elementopeninnewtab |
- ] |
- ]; |
- if (isIncognitoEnabled) { |
- menuOptions.push([ |
- ContextMenuItemIds.BOOKMARK_OPEN_IN_INCOGNITO_TAB, |
- templateData.elementopeninincognitotab |
- ]); |
- } |
- if (contextMenuItem.editable) { |
- menuOptions.push( |
- [ContextMenuItemIds.BOOKMARK_EDIT, templateData.bookmarkedit], |
- [ContextMenuItemIds.BOOKMARK_DELETE, templateData.bookmarkdelete]); |
- } |
- } else if (section == SectionType.BOOKMARKS && |
- !contextMenuItem.folder && |
- isIncognito) { |
- menuOptions = [ |
- [ |
- ContextMenuItemIds.BOOKMARK_OPEN_IN_INCOGNITO_TAB, |
- templateData.elementopeninincognitotab |
- ] |
- ]; |
- } else if (section == SectionType.BOOKMARKS && |
- contextMenuItem.folder && |
- contextMenuItem.editable && |
- !isIncognito) { |
- menuOptions = [ |
- [ContextMenuItemIds.BOOKMARK_EDIT, templateData.editfolder], |
- [ContextMenuItemIds.BOOKMARK_DELETE, templateData.deletefolder] |
- ]; |
- } else if (section == SectionType.MOST_VISITED) { |
- menuOptions = [ |
- [ |
- ContextMenuItemIds.MOST_VISITED_OPEN_IN_NEW_TAB, |
- templateData.elementopeninnewtab |
- ], |
- ]; |
- if (isIncognitoEnabled) { |
- menuOptions.push([ |
- ContextMenuItemIds.MOST_VISITED_OPEN_IN_INCOGNITO_TAB, |
- templateData.elementopeninincognitotab |
- ]); |
- } |
- menuOptions.push( |
- [ContextMenuItemIds.MOST_VISITED_REMOVE, templateData.elementremove]); |
- } else if (section == SectionType.RECENTLY_CLOSED) { |
- menuOptions = [ |
- [ |
- ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_NEW_TAB, |
- templateData.elementopeninnewtab |
- ], |
- ]; |
- if (isIncognitoEnabled) { |
- menuOptions.push([ |
- ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_INCOGNITO_TAB, |
- templateData.elementopeninincognitotab |
- ]); |
- } |
- menuOptions.push( |
- [ContextMenuItemIds.RECENTLY_CLOSED_REMOVE, templateData.removeall]); |
- } else if (section == SectionType.FOREIGN_SESSION_HEADER) { |
- menuOptions = [ |
- [ |
- ContextMenuItemIds.FOREIGN_SESSIONS_REMOVE, |
- templateData.elementremove |
- ] |
- ]; |
- } else if (section == SectionType.PROMO_VC_SESSION_HEADER) { |
- menuOptions = [ |
- [ |
- ContextMenuItemIds.PROMO_VC_SESSION_REMOVE, |
- templateData.elementremove |
- ] |
- ]; |
- } |
- |
- if (menuOptions) |
- chrome.send('showContextMenu', menuOptions); |
- |
- return false; |
- } |
- |
- // Return an object with all the exports |
- return { |
- bookmarks: bookmarks, |
- bookmarkChanged: bookmarkChanged, |
- clearPromotions: clearPromotions, |
- init: init, |
- setIncognitoEnabled: setIncognitoEnabled, |
- onCustomMenuSelected: onCustomMenuSelected, |
- openSection: openSection, |
- setFaviconDominantColor: setFaviconDominantColor, |
- setForeignSessions: setForeignSessions, |
- setIncognitoMode: setIncognitoMode, |
- setMostVisitedPages: setMostVisitedPages, |
- setPromotions: setPromotions, |
- setRecentlyClosedTabs: setRecentlyClosedTabs, |
- setSyncEnabled: setSyncEnabled, |
- snapshots: snapshots |
- }; |
-}); |
- |
-///////////////////////////////////////////////////////////////////////////// |
-//Utility Functions. |
-///////////////////////////////////////////////////////////////////////////// |
- |
-/** |
- * A best effort approach for checking simple data object equality. |
- * @param {?} val1 The first value to check equality for. |
- * @param {?} val2 The second value to check equality for. |
- * @return {boolean} Whether the two objects are equal(ish). |
- */ |
-function equals(val1, val2) { |
- if (typeof val1 != 'object' || typeof val2 != 'object') |
- return val1 === val2; |
- |
- // Object and array equality checks. |
- var keyCountVal1 = 0; |
- for (var key in val1) { |
- if (!(key in val2) || !equals(val1[key], val2[key])) |
- return false; |
- keyCountVal1++; |
- } |
- var keyCountVal2 = 0; |
- for (var key in val2) |
- keyCountVal2++; |
- if (keyCountVal1 != keyCountVal2) |
- return false; |
- return true; |
-} |
- |
-/** |
- * Alias for document.getElementById. |
- * @param {string} id The ID of the element to find. |
- * @return {HTMLElement} The found element or null if not found. |
- */ |
-function $(id) { |
- return document.getElementById(id); |
-} |
- |
-/** |
- * @return {boolean} Whether the device is currently in portrait mode. |
- */ |
-function isPortrait() { |
- return document.documentElement.offsetWidth < |
- document.documentElement.offsetHeight; |
-} |
- |
-/** |
- * Determine if the page should be formatted for tablets. |
- * @return {boolean} true if the device is a tablet, false otherwise. |
- */ |
-function isTablet() { |
- return document.body.getAttribute('device') == 'tablet'; |
-} |
- |
-/** |
- * Determine if the page should be formatted for phones. |
- * @return {boolean} true if the device is a phone, false otherwise. |
- */ |
-function isPhone() { |
- return document.body.getAttribute('device') == 'phone'; |
-} |
- |
-/** |
- * Get the page X coordinate of a touch event. |
- * @param {TouchEvent} evt The touch event triggered by the browser. |
- * @return {number} The page X coordinate of the touch event. |
- */ |
-function getTouchEventX(evt) { |
- return (evt.touches[0] || e.changedTouches[0]).pageX; |
-} |
- |
-/** |
- * Get the page Y coordinate of a touch event. |
- * @param {TouchEvent} evt The touch event triggered by the browser. |
- * @return {number} The page Y coordinate of the touch event. |
- */ |
-function getTouchEventY(evt) { |
- return (evt.touches[0] || e.changedTouches[0]).pageY; |
-} |
- |
-/** |
- * @param {Element} el The item to get the width of. |
- * @param {boolean} excludeMargin If true, exclude the width of the margin. |
- * @return {number} The total width of a given item. |
- */ |
-function getItemWidth(el, excludeMargin) { |
- var elStyle = window.getComputedStyle(el); |
- var width = el.offsetWidth; |
- if (!width || width == 0) { |
- width = parseInt(elStyle.getPropertyValue('width')); |
- width += |
- parseInt(elStyle.getPropertyValue('border-left-width')) + |
- parseInt(elStyle.getPropertyValue('border-right-width')); |
- width += |
- parseInt(elStyle.getPropertyValue('padding-left')) + |
- parseInt(elStyle.getPropertyValue('padding-right')); |
- } |
- if (!excludeMargin) { |
- width += parseInt(elStyle.getPropertyValue('margin-left')) + |
- parseInt(elStyle.getPropertyValue('margin-right')); |
- } |
- return width; |
-} |
- |
-/** |
- * @return {number} The padding height of the body due to the button bar |
- */ |
-function getButtonBarPadding() { |
- var body = document.getElementsByTagName('body')[0]; |
- var style = window.getComputedStyle(body); |
- return parseInt(style.getPropertyValue('padding-bottom')); |
-} |
- |
-/** |
- * Modify a css rule |
- * @param {string} selector The selector for the rule (passed to findCssRule()) |
- * @param {string} property The property to update |
- * @param {string} value The value to update the property to |
- * @return {boolean} true if the rule was updated, false otherwise. |
- */ |
-function modifyCssRule(selector, property, value) { |
- var rule = findCssRule(selector); |
- if (!rule) |
- return false; |
- rule.style[property] = value; |
- return true; |
-} |
- |
-/** |
- * Find a particular CSS rule. The stylesheets attached to the document |
- * are traversed in reverse order. The rules in each stylesheet are also |
- * traversed in reverse order. The first rule found to match the selector |
- * is returned. |
- * @param {string} selector The selector for the rule. |
- * @return {Object} The rule if one was found, null otherwise |
- */ |
-function findCssRule(selector) { |
- var styleSheets = document.styleSheets; |
- for (i = styleSheets.length - 1; i >= 0; i--) { |
- var styleSheet = styleSheets[i]; |
- var rules = styleSheet.cssRules; |
- if (rules == null) |
- continue; |
- for (j = rules.length - 1; j >= 0; j--) { |
- if (rules[j].selectorText == selector) |
- return rules[j]; |
- } |
- } |
-} |
- |
-///////////////////////////////////////////////////////////////////////////// |
-// NTP Entry point. |
-///////////////////////////////////////////////////////////////////////////// |
- |
-/* |
- * Handles initializing the UI when the page has finished loading. |
- */ |
-window.addEventListener('DOMContentLoaded', function(evt) { |
- ntp.init(); |
- $('content-area').style.display = 'block'; |
-}); |