Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / resources / bookmark_manager / js / main.js
blob1c97b64eb7bdce8754e4441e869099bd4cadf2e5
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 (function() {
6 'use strict';
8 /** @const */ var BookmarkList = bmm.BookmarkList;
9 /** @const */ var BookmarkTree = bmm.BookmarkTree;
10 /** @const */ var Command = cr.ui.Command;
11 /** @const */ var LinkKind = cr.LinkKind;
12 /** @const */ var ListItem = cr.ui.ListItem;
13 /** @const */ var Menu = cr.ui.Menu;
14 /** @const */ var MenuButton = cr.ui.MenuButton;
15 /** @const */ var Splitter = cr.ui.Splitter;
16 /** @const */ var TreeItem = cr.ui.TreeItem;
18 /**
19  * An array containing the BookmarkTreeNodes that were deleted in the last
20  * deletion action. This is used for implementing undo.
21  * @type {?{nodes: Array<Array<BookmarkTreeNode>>, target: EventTarget}}
22  */
23 var lastDeleted;
25 /**
26  *
27  * Holds the last DOMTimeStamp when mouse pointer hovers on folder in tree
28  * view. Zero means pointer doesn't hover on folder.
29  * @type {number}
30  */
31 var lastHoverOnFolderTimeStamp = 0;
33 /**
34  * Holds a function that will undo that last action, if global undo is enabled.
35  * @type {Function}
36  */
37 var performGlobalUndo;
39 /**
40  * Holds a link controller singleton. Use getLinkController() rarther than
41  * accessing this variabie.
42  * @type {cr.LinkController}
43  */
44 var linkController;
46 /**
47  * New Windows are not allowed in Windows 8 metro mode.
48  */
49 var canOpenNewWindows = true;
51 /**
52  * Incognito mode availability can take the following values: ,
53  *   - 'enabled' for when both normal and incognito modes are available;
54  *   - 'disabled' for when incognito mode is disabled;
55  *   - 'forced' for when incognito mode is forced (normal mode is unavailable).
56  */
57 var incognitoModeAvailability = 'enabled';
59 /**
60  * Whether bookmarks can be modified.
61  * @type {boolean}
62  */
63 var canEdit = true;
65 /**
66  * @type {TreeItem}
67  * @const
68  */
69 var searchTreeItem = new TreeItem({
70   bookmarkId: 'q='
71 });
73 /**
74  * Command shortcut mapping.
75  * @const
76  */
77 var commandShortcutMap = cr.isMac ? {
78   'edit': 'Enter',
79   // On Mac we also allow Meta+Backspace.
80   'delete': 'U+007F  U+0008 Meta-U+0008',
81   'open-in-background-tab': 'Meta-Enter',
82   'open-in-new-tab': 'Shift-Meta-Enter',
83   'open-in-same-window': 'Meta-Down',
84   'open-in-new-window': 'Shift-Enter',
85   'rename-folder': 'Enter',
86   // Global undo is Command-Z. It is not in any menu.
87   'undo': 'Meta-U+005A',
88 } : {
89   'edit': 'F2',
90   'delete': 'U+007F',
91   'open-in-background-tab': 'Ctrl-Enter',
92   'open-in-new-tab': 'Shift-Ctrl-Enter',
93   'open-in-same-window': 'Enter',
94   'open-in-new-window': 'Shift-Enter',
95   'rename-folder': 'F2',
96   // Global undo is Ctrl-Z. It is not in any menu.
97   'undo': 'Ctrl-U+005A',
101  * Mapping for folder id to suffix of UMA. These names will be appeared
102  * after "BookmarkManager_NavigateTo_" in UMA dashboard.
103  * @const
104  */
105 var folderMetricsNameMap = {
106   '1': 'BookmarkBar',
107   '2': 'Other',
108   '3': 'Mobile',
109   'q=': 'Search',
110   'subfolder': 'SubFolder',
114  * Adds an event listener to a node that will remove itself after firing once.
115  * @param {!Element} node The DOM node to add the listener to.
116  * @param {string} name The name of the event listener to add to.
117  * @param {function(Event)} handler Function called when the event fires.
118  */
119 function addOneShotEventListener(node, name, handler) {
120   var f = function(e) {
121     handler(e);
122     node.removeEventListener(name, f);
123   };
124   node.addEventListener(name, f);
127 // Get the localized strings from the backend via bookmakrManagerPrivate API.
128 function loadLocalizedStrings(data) {
129   // The strings may contain & which we need to strip.
130   for (var key in data) {
131     data[key] = data[key].replace(/&/, '');
132   }
134   loadTimeData.data = data;
135   i18nTemplate.process(document, loadTimeData);
137   searchTreeItem.label = loadTimeData.getString('search');
138   searchTreeItem.icon = isRTL() ? 'images/bookmark_manager_search_rtl.png' :
139                                   'images/bookmark_manager_search.png';
143  * Updates the location hash to reflect the current state of the application.
144  */
145 function updateHash() {
146   window.location.hash = bmm.tree.selectedItem.bookmarkId;
147   updateAllCommands();
151  * Navigates to a bookmark ID.
152  * @param {string} id The ID to navigate to.
153  * @param {function()=} opt_callback Function called when list view loaded or
154  *     displayed specified folder.
155  */
156 function navigateTo(id, opt_callback) {
157   window.location.hash = id;
158   updateAllCommands();
160   var metricsId = folderMetricsNameMap[id.replace(/^q=.*/, 'q=')] ||
161                   folderMetricsNameMap['subfolder'];
162   chrome.metricsPrivate.recordUserAction(
163       'BookmarkManager_NavigateTo_' + metricsId);
165   if (opt_callback) {
166     if (bmm.list.parentId == id)
167       opt_callback();
168     else
169       addOneShotEventListener(bmm.list, 'load', opt_callback);
170   }
174  * Updates the parent ID of the bookmark list and selects the correct tree item.
175  * @param {string} id The id.
176  */
177 function updateParentId(id) {
178   // Setting list.parentId fires 'load' event.
179   bmm.list.parentId = id;
181   // When tree.selectedItem changed, tree view calls navigatTo() then it
182   // calls updateHash() when list view displayed specified folder.
183   bmm.tree.selectedItem = bmm.treeLookup[id] || bmm.tree.selectedItem;
186 // Process the location hash. This is called by onhashchange and when the page
187 // is first loaded.
188 function processHash() {
189   var id = window.location.hash.slice(1);
190   if (!id) {
191     // If we do not have a hash, select first item in the tree.
192     id = bmm.tree.items[0].bookmarkId;
193   }
195   var valid = false;
196   if (/^e=/.test(id)) {
197     id = id.slice(2);
199     // If hash contains e=, edit the item specified.
200     chrome.bookmarks.get(id, function(bookmarkNodes) {
201       // Verify the node to edit is a valid node.
202       if (!bookmarkNodes || bookmarkNodes.length != 1)
203         return;
204       var bookmarkNode = bookmarkNodes[0];
206       // After the list reloads, edit the desired bookmark.
207       var editBookmark = function() {
208         var index = bmm.list.dataModel.findIndexById(bookmarkNode.id);
209         if (index != -1) {
210           var sm = bmm.list.selectionModel;
211           sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
212           scrollIntoViewAndMakeEditable(index);
213         }
214       };
216       var parentId = assert(bookmarkNode.parentId);
217       navigateTo(parentId, editBookmark);
218     });
220     // We handle the two cases of navigating to the bookmark to be edited
221     // above. Don't run the standard navigation code below.
222     return;
223   } else if (/^q=/.test(id)) {
224     // In case we got a search hash, update the text input and the
225     // bmm.treeLookup to use the new id.
226     setSearch(id.slice(2));
227     valid = true;
228   }
230   // Navigate to bookmark 'id' (which may be a query of the form q=query).
231   if (valid) {
232     updateParentId(id);
233   } else {
234     // We need to verify that this is a correct ID.
235     chrome.bookmarks.get(id, function(items) {
236       if (items && items.length == 1)
237         updateParentId(id);
238     });
239   }
242 // Activate is handled by the open-in-same-window-command.
243 function handleDoubleClickForList(e) {
244   if (e.button == 0)
245     $('open-in-same-window-command').execute();
248 // The list dispatches an event when the user clicks on the URL or the Show in
249 // folder part.
250 function handleUrlClickedForList(e) {
251   getLinkController().openUrlFromEvent(e.url, e.originalEvent);
252   chrome.bookmarkManagerPrivate.recordLaunch();
255 function handleSearch(e) {
256   setSearch(this.value);
260  * Navigates to the search results for the search text.
261  * @param {string} searchText The text to search for.
262  */
263 function setSearch(searchText) {
264   if (searchText) {
265     // Only update search item if we have a search term. We never want the
266     // search item to be for an empty search.
267     delete bmm.treeLookup[searchTreeItem.bookmarkId];
268     var id = searchTreeItem.bookmarkId = 'q=' + searchText;
269     bmm.treeLookup[searchTreeItem.bookmarkId] = searchTreeItem;
270   }
272   var input = $('term');
273   // Do not update the input if the user is actively using the text input.
274   if (document.activeElement != input)
275     input.value = searchText;
277   if (searchText) {
278     bmm.tree.add(searchTreeItem);
279     bmm.tree.selectedItem = searchTreeItem;
280   } else {
281     // Go "home".
282     bmm.tree.selectedItem = bmm.tree.items[0];
283     id = bmm.tree.selectedItem.bookmarkId;
284   }
286   navigateTo(id);
290  * This returns the user visible path to the folder where the bookmark is
291  * located.
292  * @param {number} parentId The ID of the parent folder.
293  * @return {string} The path to the the bookmark,
294  */
295 function getFolder(parentId) {
296   var parentNode = bmm.tree.getBookmarkNodeById(parentId);
297   if (parentNode) {
298     var s = parentNode.title;
299     if (parentNode.parentId != bmm.ROOT_ID) {
300       return getFolder(parentNode.parentId) + '/' + s;
301     }
302     return s;
303   }
306 function handleLoadForTree(e) {
307   processHash();
311  * Returns a promise for all the URLs in the {@code nodes} and the direct
312  * children of {@code nodes}.
313  * @param {!Array<BookmarkTreeNode>} nodes .
314  * @return {!Promise<Array<string>>} .
315  */
316 function getAllUrls(nodes) {
317   var urls = [];
319   // Adds the node and all its direct children.
320   // TODO(deepak.m1): Here node should exist. When we delete the nodes then
321   // datamodel gets updated but still it shows deleted items as selected items
322   // and accessing those nodes throws chrome.runtime.lastError. This cause
323   // undefined value for node. Please refer https://crbug.com/480935.
324   function addNodes(node) {
325     if (!node || node.id == 'new')
326       return;
328     if (node.children) {
329       node.children.forEach(function(child) {
330         if (!bmm.isFolder(child))
331           urls.push(child.url);
332       });
333     } else {
334       urls.push(node.url);
335     }
336   }
338   // Get a future promise for the nodes.
339   var promises = nodes.map(function(node) {
340     if (bmm.isFolder(assert(node)))
341       return bmm.loadSubtree(node.id);
342     // Not a folder so we already have all the data we need.
343     return Promise.resolve(node);
344   });
346   return Promise.all(promises).then(function(nodes) {
347     nodes.forEach(addNodes);
348     return urls;
349   });
353  * Returns the nodes (non recursive) to use for the open commands.
354  * @param {HTMLElement} target
355  * @return {!Array<BookmarkTreeNode>}
356  */
357 function getNodesForOpen(target) {
358   if (target == bmm.tree) {
359     if (bmm.tree.selectedItem != searchTreeItem)
360       return bmm.tree.selectedFolders;
361     // Fall through to use all nodes in the list.
362   } else {
363     var items = bmm.list.selectedItems;
364     if (items.length)
365       return items;
366   }
368   // The list starts off with a null dataModel. We can get here during startup.
369   if (!bmm.list.dataModel)
370     return [];
372   // Return an array based on the dataModel.
373   return bmm.list.dataModel.slice();
377  * Returns a promise that will contain all URLs of all the selected bookmarks
378  * and the nested bookmarks for use with the open commands.
379  * @param {HTMLElement} target The target list or tree.
380  * @return {Promise<Array<string>>} .
381  */
382 function getUrlsForOpenCommands(target) {
383   return getAllUrls(getNodesForOpen(target));
386 function notNewNode(node) {
387   return node.id != 'new';
391  * Helper function that updates the canExecute and labels for the open-like
392  * commands.
393  * @param {!cr.ui.CanExecuteEvent} e The event fired by the command system.
394  * @param {!cr.ui.Command} command The command we are currently processing.
395  * @param {string} singularId The string id of singular form of the menu label.
396  * @param {string} pluralId The string id of menu label if the singular form is
397        not used.
398  * @param {boolean} commandDisabled Whether the menu item should be disabled
399        no matter what bookmarks are selected.
400  */
401 function updateOpenCommand(e, command, singularId, pluralId, commandDisabled) {
402   if (singularId) {
403     // The command label reflects the selection which might not reflect
404     // how many bookmarks will be opened. For example if you right click an
405     // empty area in a folder with 1 bookmark the text should still say "all".
406     var selectedNodes = getSelectedBookmarkNodes(e.target).filter(notNewNode);
407     var singular = selectedNodes.length == 1 && !bmm.isFolder(selectedNodes[0]);
408     command.label = loadTimeData.getString(singular ? singularId : pluralId);
409   }
411   if (commandDisabled) {
412     command.disabled = true;
413     e.canExecute = false;
414     return;
415   }
417   getUrlsForOpenCommands(assertInstanceof(e.target, HTMLElement)).then(
418       function(urls) {
419     var disabled = !urls.length;
420     command.disabled = disabled;
421     e.canExecute = !disabled;
422   });
426  * Calls the backend to figure out if we can paste the clipboard into the active
427  * folder.
428  * @param {Function=} opt_f Function to call after the state has been updated.
429  */
430 function updatePasteCommand(opt_f) {
431   function update(commandId, canPaste) {
432     $(commandId).disabled = !canPaste;
433   }
435   var promises = [];
437   // The folders menu.
438   // We can not paste into search item in tree.
439   if (bmm.tree.selectedItem && bmm.tree.selectedItem != searchTreeItem) {
440     promises.push(new Promise(function(resolve) {
441       var id = bmm.tree.selectedItem.bookmarkId;
442       chrome.bookmarkManagerPrivate.canPaste(id, function(canPaste) {
443         update('paste-from-folders-menu-command', canPaste);
444         resolve(canPaste);
445       });
446     }));
447   } else {
448     // Tree's not loaded yet.
449     update('paste-from-folders-menu-command', false);
450   }
452   // The organize menu.
453   var listId = bmm.list.parentId;
454   if (bmm.list.isSearch() || !listId) {
455     // We cannot paste into search view or the list isn't ready.
456     update('paste-from-organize-menu-command', false);
457   } else {
458     promises.push(new Promise(function(resolve) {
459       chrome.bookmarkManagerPrivate.canPaste(listId, function(canPaste) {
460         update('paste-from-organize-menu-command', canPaste);
461         resolve(canPaste);
462       });
463     }));
464   }
466   Promise.all(promises).then(function() {
467     var cmd;
468     if (document.activeElement == bmm.list)
469       cmd = 'paste-from-organize-menu-command';
470     else if (document.activeElement == bmm.tree)
471       cmd = 'paste-from-folders-menu-command';
473     if (cmd)
474       update('paste-from-context-menu-command', !$(cmd).disabled);
476     if (opt_f) opt_f();
477   });
480 function handleCanExecuteForSearchBox(e) {
481   var command = e.command;
482   switch (command.id) {
483     case 'delete-command':
484     case 'undo-command':
485       // Pass the delete and undo commands through
486       // (fixes http://crbug.com/278112).
487       e.canExecute = false;
488       break;
489   }
492 function handleCanExecuteForDocument(e) {
493   var command = e.command;
494   switch (command.id) {
495     case 'import-menu-command':
496       e.canExecute = canEdit;
497       break;
499     case 'export-menu-command':
500       // We can always execute the export-menu command.
501       e.canExecute = true;
502       break;
504     case 'sort-command':
505       e.canExecute = !bmm.list.isSearch() &&
506           bmm.list.dataModel && bmm.list.dataModel.length > 1 &&
507           !isUnmodifiable(bmm.tree.getBookmarkNodeById(bmm.list.parentId));
508       break;
510     case 'undo-command':
511       // Because the global undo command has no visible UI, always enable it,
512       // and just make it a no-op if undo is not possible.
513       e.canExecute = true;
514       break;
516     default:
517       canExecuteForList(e);
518       if (!e.defaultPrevented)
519         canExecuteForTree(e);
520       break;
521   }
525  * Helper function for handling canExecute for the list and the tree.
526  * @param {!cr.ui.CanExecuteEvent} e Can execute event object.
527  * @param {boolean} isSearch Whether the user is trying to do a command on
528  *     search.
529  */
530 function canExecuteShared(e, isSearch) {
531   var command = e.command;
532   switch (command.id) {
533     case 'paste-from-folders-menu-command':
534     case 'paste-from-organize-menu-command':
535     case 'paste-from-context-menu-command':
536       updatePasteCommand();
537       break;
539     case 'add-new-bookmark-command':
540     case 'new-folder-command':
541     case 'new-folder-from-folders-menu-command':
542       var parentId = computeParentFolderForNewItem();
543       var unmodifiable = isUnmodifiable(
544           bmm.tree.getBookmarkNodeById(parentId));
545       e.canExecute = !isSearch && canEdit && !unmodifiable;
546       break;
548     case 'open-in-new-tab-command':
549       updateOpenCommand(e, command, 'open_in_new_tab', 'open_all', false);
550       break;
552     case 'open-in-background-tab-command':
553       updateOpenCommand(e, command, '', '', false);
554       break;
556     case 'open-in-new-window-command':
557       updateOpenCommand(e, command,
558           'open_in_new_window', 'open_all_new_window',
559           // Disabled when incognito is forced.
560           incognitoModeAvailability == 'forced' || !canOpenNewWindows);
561       break;
563     case 'open-incognito-window-command':
564       updateOpenCommand(e, command,
565           'open_incognito', 'open_all_incognito',
566           // Not available when incognito is disabled.
567           incognitoModeAvailability == 'disabled');
568       break;
570     case 'undo-delete-command':
571       e.canExecute = !!lastDeleted;
572       break;
573   }
577  * Helper function for handling canExecute for the list and document.
578  * @param {!cr.ui.CanExecuteEvent} e Can execute event object.
579  */
580 function canExecuteForList(e) {
581   function hasSelected() {
582     return !!bmm.list.selectedItem;
583   }
585   function hasSingleSelected() {
586     return bmm.list.selectedItems.length == 1;
587   }
589   function canCopyItem(item) {
590     return item.id != 'new';
591   }
593   function canCopyItems() {
594     var selectedItems = bmm.list.selectedItems;
595     return selectedItems && selectedItems.some(canCopyItem);
596   }
598   function isSearch() {
599     return bmm.list.isSearch();
600   }
602   var command = e.command;
603   switch (command.id) {
604     case 'rename-folder-command':
605       // Show rename if a single folder is selected.
606       var items = bmm.list.selectedItems;
607       if (items.length != 1) {
608         e.canExecute = false;
609         command.hidden = true;
610       } else {
611         var isFolder = bmm.isFolder(items[0]);
612         e.canExecute = isFolder && canEdit && !hasUnmodifiable(items);
613         command.hidden = !isFolder;
614       }
615       break;
617     case 'edit-command':
618       // Show the edit command if not a folder.
619       var items = bmm.list.selectedItems;
620       if (items.length != 1) {
621         e.canExecute = false;
622         command.hidden = false;
623       } else {
624         var isFolder = bmm.isFolder(items[0]);
625         e.canExecute = !isFolder && canEdit && !hasUnmodifiable(items);
626         command.hidden = isFolder;
627       }
628       break;
630     case 'show-in-folder-command':
631       e.canExecute = isSearch() && hasSingleSelected();
632       break;
634     case 'delete-command':
635     case 'cut-command':
636       e.canExecute = canCopyItems() && canEdit &&
637           !hasUnmodifiable(bmm.list.selectedItems);
638       break;
640     case 'copy-command':
641       e.canExecute = canCopyItems();
642       break;
644     case 'open-in-same-window-command':
645       e.canExecute = (e.target == bmm.list) && hasSelected();
646       break;
648     default:
649       canExecuteShared(e, isSearch());
650   }
653 // Update canExecute for the commands when the list is the active element.
654 function handleCanExecuteForList(e) {
655   if (e.target != bmm.list) return;
656   canExecuteForList(e);
659 // Update canExecute for the commands when the tree is the active element.
660 function handleCanExecuteForTree(e) {
661   if (e.target != bmm.tree) return;
662   canExecuteForTree(e);
665 function canExecuteForTree(e) {
666   function hasSelected() {
667     return !!bmm.tree.selectedItem;
668   }
670   function isSearch() {
671     return bmm.tree.selectedItem == searchTreeItem;
672   }
674   function isTopLevelItem() {
675     return bmm.tree.selectedItem &&
676            bmm.tree.selectedItem.parentNode == bmm.tree;
677   }
679   var command = e.command;
680   switch (command.id) {
681     case 'rename-folder-command':
682     case 'rename-folder-from-folders-menu-command':
683       command.hidden = false;
684       e.canExecute = hasSelected() && !isTopLevelItem() && canEdit &&
685           !hasUnmodifiable(bmm.tree.selectedFolders);
686       break;
688     case 'edit-command':
689       command.hidden = true;
690       e.canExecute = false;
691       break;
693     case 'delete-command':
694     case 'delete-from-folders-menu-command':
695     case 'cut-command':
696     case 'cut-from-folders-menu-command':
697       e.canExecute = hasSelected() && !isTopLevelItem() && canEdit &&
698           !hasUnmodifiable(bmm.tree.selectedFolders);
699       break;
701     case 'copy-command':
702     case 'copy-from-folders-menu-command':
703       e.canExecute = hasSelected() && !isTopLevelItem();
704       break;
706     case 'undo-delete-from-folders-menu-command':
707       e.canExecute = lastDeleted && lastDeleted.target == bmm.tree;
708       break;
710     default:
711       canExecuteShared(e, isSearch());
712   }
716  * Update the canExecute state of all the commands.
717  */
718 function updateAllCommands() {
719   var commands = document.querySelectorAll('command');
720   for (var i = 0; i < commands.length; i++) {
721     commands[i].canExecuteChange();
722   }
725 function updateEditingCommands() {
726   var editingCommands = [
727     'add-new-bookmark',
728     'cut',
729     'cut-from-folders-menu',
730     'delete',
731     'edit',
732     'new-folder',
733     'paste-from-context-menu',
734     'paste-from-folders-menu',
735     'paste-from-organize-menu',
736     'rename-folder',
737     'sort',
738   ];
740   chrome.bookmarkManagerPrivate.canEdit(function(result) {
741     if (result != canEdit) {
742       canEdit = result;
743       editingCommands.forEach(function(baseId) {
744         $(baseId + '-command').canExecuteChange();
745       });
746     }
747   });
750 function handleChangeForTree(e) {
751   navigateTo(bmm.tree.selectedItem.bookmarkId);
754 function handleMenuButtonClicked(e) {
755   updateEditingCommands();
757   if (e.currentTarget.id == 'folders-menu') {
758     $('copy-from-folders-menu-command').canExecuteChange();
759     $('undo-delete-from-folders-menu-command').canExecuteChange();
760   } else {
761     $('copy-command').canExecuteChange();
762   }
765 function handleRename(e) {
766   var item = e.target;
767   var bookmarkNode = item.bookmarkNode;
768   chrome.bookmarks.update(bookmarkNode.id, {title: item.label});
769   performGlobalUndo = null;  // This can't be undone, so disable global undo.
772 function handleEdit(e) {
773   var item = e.target;
774   var bookmarkNode = item.bookmarkNode;
775   var context = {
776     title: bookmarkNode.title
777   };
778   if (!bmm.isFolder(bookmarkNode))
779     context.url = bookmarkNode.url;
781   if (bookmarkNode.id == 'new') {
782     selectItemsAfterUserAction(/** @type {BookmarkList} */(bmm.list));
784     // New page
785     context.parentId = bookmarkNode.parentId;
786     chrome.bookmarks.create(context, function(node) {
787       // A new node was created and will get added to the list due to the
788       // handler.
789       var dataModel = bmm.list.dataModel;
790       var index = dataModel.indexOf(bookmarkNode);
791       dataModel.splice(index, 1);
793       // Select new item.
794       var newIndex = dataModel.findIndexById(node.id);
795       if (newIndex != -1) {
796         var sm = bmm.list.selectionModel;
797         bmm.list.scrollIndexIntoView(newIndex);
798         sm.leadIndex = sm.anchorIndex = sm.selectedIndex = newIndex;
799       }
800     });
801   } else {
802     // Edit
803     chrome.bookmarks.update(bookmarkNode.id, context);
804   }
805   performGlobalUndo = null;  // This can't be undone, so disable global undo.
808 function handleCancelEdit(e) {
809   var item = e.target;
810   var bookmarkNode = item.bookmarkNode;
811   if (bookmarkNode.id == 'new') {
812     var dataModel = bmm.list.dataModel;
813     var index = dataModel.findIndexById('new');
814     dataModel.splice(index, 1);
815   }
819  * Navigates to the folder that the selected item is in and selects it. This is
820  * used for the show-in-folder command.
821  */
822 function showInFolder() {
823   var bookmarkNode = bmm.list.selectedItem;
824   if (!bookmarkNode)
825     return;
826   var parentId = bookmarkNode.parentId;
828   // After the list is loaded we should select the revealed item.
829   function selectItem() {
830     var index = bmm.list.dataModel.findIndexById(bookmarkNode.id);
831     if (index == -1)
832       return;
833     var sm = bmm.list.selectionModel;
834     sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
835     bmm.list.scrollIndexIntoView(index);
836   }
838   var treeItem = bmm.treeLookup[parentId];
839   treeItem.reveal();
841   navigateTo(parentId, selectItem);
845  * @return {!cr.LinkController} The link controller used to open links based on
846  *     user clicks and keyboard actions.
847  */
848 function getLinkController() {
849   return linkController ||
850       (linkController = new cr.LinkController(loadTimeData));
854  * Returns the selected bookmark nodes of the provided tree or list.
855  * If |opt_target| is not provided or null the active element is used.
856  * Only call this if the list or the tree is focused.
857  * @param {EventTarget=} opt_target The target list or tree.
858  * @return {!Array} Array of bookmark nodes.
859  */
860 function getSelectedBookmarkNodes(opt_target) {
861   return (opt_target || document.activeElement) == bmm.tree ?
862       bmm.tree.selectedFolders : bmm.list.selectedItems;
866  * @param {EventTarget=} opt_target The target list or tree.
867  * @return {!Array<string>} An array of the selected bookmark IDs.
868  */
869 function getSelectedBookmarkIds(opt_target) {
870   var selectedNodes = getSelectedBookmarkNodes(opt_target);
871   selectedNodes.sort(function(a, b) { return a.index - b.index });
872   return selectedNodes.map(function(node) {
873     return node.id;
874   });
878  * @param {BookmarkTreeNode} node The node to test.
879  * @return {boolean} Whether the given node is unmodifiable.
880  */
881 function isUnmodifiable(node) {
882   return !!(node && node.unmodifiable);
886  * @param {Array<BookmarkTreeNode>} nodes A list of BookmarkTreeNodes.
887  * @return {boolean} Whether any of the nodes is managed.
888  */
889 function hasUnmodifiable(nodes) {
890   return nodes.some(isUnmodifiable);
894  * Opens the selected bookmarks.
895  * @param {cr.LinkKind} kind The kind of link we want to open.
896  * @param {HTMLElement=} opt_eventTarget The target of the user initiated event.
897  */
898 function openBookmarks(kind, opt_eventTarget) {
899   // If we have selected any folders, we need to find all the bookmarks one
900   // level down. We use multiple async calls to getSubtree instead of getting
901   // the whole tree since we would like to minimize the amount of data sent.
903   var urlsP = getUrlsForOpenCommands(opt_eventTarget ? opt_eventTarget : null);
904   urlsP.then(function(urls) {
905     getLinkController().openUrls(assert(urls), kind);
906     chrome.bookmarkManagerPrivate.recordLaunch();
907   });
911  * Opens an item in the list.
912  */
913 function openItem() {
914   var bookmarkNodes = getSelectedBookmarkNodes();
915   // If we double clicked or pressed enter on a single folder, navigate to it.
916   if (bookmarkNodes.length == 1 && bmm.isFolder(bookmarkNodes[0]))
917     navigateTo(bookmarkNodes[0].id);
918   else
919     openBookmarks(LinkKind.FOREGROUND_TAB);
923  * Refreshes search results after delete or undo-delete.
924  * This ensures children of deleted folders do not remain in results
925  */
926 function updateSearchResults() {
927   if (bmm.list.isSearch())
928     bmm.list.reload();
932  * Deletes the selected bookmarks. The bookmarks are saved in memory in case
933  * the user needs to undo the deletion.
934  * @param {EventTarget=} opt_target The deleter of bookmarks.
935  */
936 function deleteBookmarks(opt_target) {
937   var selectedIds = getSelectedBookmarkIds(opt_target);
938   if (!selectedIds.length)
939     return;
941   var filteredIds = getFilteredSelectedBookmarkIds(opt_target);
942   lastDeleted = {nodes: [], target: opt_target || document.activeElement};
944   function performDelete() {
945     // Only remove filtered ids.
946     chrome.bookmarkManagerPrivate.removeTrees(filteredIds);
947     $('undo-delete-command').canExecuteChange();
948     $('undo-delete-from-folders-menu-command').canExecuteChange();
949     performGlobalUndo = undoDelete;
950   }
952   // First, store information about the bookmarks being deleted.
953   // Store all selected ids.
954   selectedIds.forEach(function(id) {
955     chrome.bookmarks.getSubTree(id, function(results) {
956       lastDeleted.nodes.push(results);
958       // When all nodes have been saved, perform the deletion.
959       if (lastDeleted.nodes.length === selectedIds.length) {
960         performDelete();
961         updateSearchResults();
962       }
963     });
964   });
968  * Restores a tree of bookmarks under a specified folder.
969  * @param {BookmarkTreeNode} node The node to restore.
970  * @param {(string|number)=} opt_parentId If a string is passed, it's the ID of
971  *     the folder to restore under. If not specified or a number is passed, the
972  *     original parentId of the node will be used.
973  */
974 function restoreTree(node, opt_parentId) {
975   var bookmarkInfo = {
976     parentId: typeof opt_parentId == 'string' ? opt_parentId : node.parentId,
977     title: node.title,
978     index: node.index,
979     url: node.url
980   };
982   chrome.bookmarks.create(bookmarkInfo, function(result) {
983     if (!result) {
984       console.error('Failed to restore bookmark.');
985       return;
986     }
988     if (node.children) {
989       // Restore the children using the new ID for this node.
990       node.children.forEach(function(child) {
991         restoreTree(child, result.id);
992       });
993     }
995     updateSearchResults();
996   });
1000  * Restores the last set of bookmarks that was deleted.
1001  */
1002 function undoDelete() {
1003   lastDeleted.nodes.forEach(function(arr) {
1004     arr.forEach(restoreTree);
1005   });
1006   lastDeleted = null;
1007   $('undo-delete-command').canExecuteChange();
1008   $('undo-delete-from-folders-menu-command').canExecuteChange();
1010   // Only a single level of undo is supported, so disable global undo now.
1011   performGlobalUndo = null;
1015  * Computes folder for "Add Page" and "Add Folder".
1016  * @return {string} The id of folder node where we'll create new page/folder.
1017  */
1018 function computeParentFolderForNewItem() {
1019   if (document.activeElement == bmm.tree)
1020     return bmm.list.parentId;
1021   var selectedItem = bmm.list.selectedItem;
1022   return selectedItem && bmm.isFolder(selectedItem) ?
1023       selectedItem.id : bmm.list.parentId;
1027  * Callback for rename folder and edit command. This starts editing for
1028  * the passed in target, or the selected item.
1029  * @param {EventTarget=} opt_target The target to start editing. If absent or
1030  *     null, the selected item will be edited instead.
1031  */
1032 function editItem(opt_target) {
1033   if ((opt_target || document.activeElement) == bmm.tree) {
1034     bmm.tree.selectedItem.editing = true;
1035   } else {
1036     var li = bmm.list.getListItem(bmm.list.selectedItem);
1037     if (li)
1038       li.editing = true;
1039   }
1043  * Callback for the new folder command. This creates a new folder and starts
1044  * a rename of it.
1045  * @param {EventTarget=} opt_target The target to create a new folder in.
1046  */
1047 function newFolder(opt_target) {
1048   performGlobalUndo = null;  // This can't be undone, so disable global undo.
1050   var parentId = computeParentFolderForNewItem();
1051   var selectedItems = bmm.list.selectedItems;
1052   var newIndex;
1053   // Callback is called after tree and list data model updated.
1054   function createFolder(callback) {
1055     if (selectedItems.length == 1 && document.activeElement != bmm.tree &&
1056         !bmm.isFolder(selectedItems[0]) && selectedItems[0].id != 'new') {
1057       newIndex = bmm.list.dataModel.indexOf(selectedItems[0]) + 1;
1058     }
1059     chrome.bookmarks.create({
1060       title: loadTimeData.getString('new_folder_name'),
1061       parentId: parentId,
1062       index: newIndex
1063     }, callback);
1064   }
1066   if ((opt_target || document.activeElement) == bmm.tree) {
1067     createFolder(function(newNode) {
1068       navigateTo(newNode.id, function() {
1069         bmm.treeLookup[newNode.id].editing = true;
1070       });
1071     });
1072     return;
1073   }
1075   function editNewFolderInList() {
1076     createFolder(function(newNode) {
1077       var index = newNode.index;
1078       var sm = bmm.list.selectionModel;
1079       sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
1080       scrollIntoViewAndMakeEditable(index);
1081     });
1082   }
1084   navigateTo(parentId, editNewFolderInList);
1088  * Scrolls the list item into view and makes it editable.
1089  * @param {number} index The index of the item to make editable.
1090  */
1091 function scrollIntoViewAndMakeEditable(index) {
1092   bmm.list.scrollIndexIntoView(index);
1093   // onscroll is now dispatched asynchronously so we have to postpone
1094   // the rest.
1095   setTimeout(function() {
1096     var item = bmm.list.getListItemByIndex(index);
1097     if (item)
1098       item.editing = true;
1099   }, 0);
1103  * Adds a page to the current folder. This is called by the
1104  * add-new-bookmark-command handler.
1105  */
1106 function addPage() {
1107   var parentId = computeParentFolderForNewItem();
1108   var selectedItems = bmm.list.selectedItems;
1109   var newIndex;
1110   function editNewBookmark() {
1111     if (selectedItems.length == 1 && document.activeElement != bmm.tree &&
1112         !bmm.isFolder(selectedItems[0])) {
1113       newIndex = bmm.list.dataModel.indexOf(selectedItems[0]) + 1;
1114     }
1116     var fakeNode = {
1117       title: '',
1118       url: '',
1119       parentId: parentId,
1120       index: newIndex,
1121       id: 'new'
1122     };
1123     var dataModel = bmm.list.dataModel;
1124     var index = dataModel.length;
1125     if (newIndex != undefined)
1126       index = newIndex;
1127     dataModel.splice(index, 0, fakeNode);
1128     var sm = bmm.list.selectionModel;
1129     sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
1130     scrollIntoViewAndMakeEditable(index);
1131   };
1133   navigateTo(parentId, editNewBookmark);
1137  * This function is used to select items after a user action such as paste, drop
1138  * add page etc.
1139  * @param {BookmarkList|BookmarkTree} target The target of the user action.
1140  * @param {string=} opt_selectedTreeId If provided, then select that tree id.
1141  */
1142 function selectItemsAfterUserAction(target, opt_selectedTreeId) {
1143   // We get one onCreated event per item so we delay the handling until we get
1144   // no more events coming.
1146   var ids = [];
1147   var timer;
1149   function handle(id, bookmarkNode) {
1150     clearTimeout(timer);
1151     if (opt_selectedTreeId || bmm.list.parentId == bookmarkNode.parentId)
1152       ids.push(id);
1153     timer = setTimeout(handleTimeout, 50);
1154   }
1156   function handleTimeout() {
1157     chrome.bookmarks.onCreated.removeListener(handle);
1158     chrome.bookmarks.onMoved.removeListener(handle);
1160     if (opt_selectedTreeId && ids.indexOf(opt_selectedTreeId) != -1) {
1161       var index = ids.indexOf(opt_selectedTreeId);
1162       if (index != -1 && opt_selectedTreeId in bmm.treeLookup) {
1163         bmm.tree.selectedItem = bmm.treeLookup[opt_selectedTreeId];
1164       }
1165     } else if (target == bmm.list) {
1166       var dataModel = bmm.list.dataModel;
1167       var firstIndex = dataModel.findIndexById(ids[0]);
1168       var lastIndex = dataModel.findIndexById(ids[ids.length - 1]);
1169       if (firstIndex != -1 && lastIndex != -1) {
1170         var selectionModel = bmm.list.selectionModel;
1171         selectionModel.selectedIndex = -1;
1172         selectionModel.selectRange(firstIndex, lastIndex);
1173         selectionModel.anchorIndex = selectionModel.leadIndex = lastIndex;
1174         bmm.list.focus();
1175       }
1176     }
1178     bmm.list.endBatchUpdates();
1179   }
1181   bmm.list.startBatchUpdates();
1183   chrome.bookmarks.onCreated.addListener(handle);
1184   chrome.bookmarks.onMoved.addListener(handle);
1185   timer = setTimeout(handleTimeout, 300);
1189  * Record user action.
1190  * @param {string} name An user action name.
1191  */
1192 function recordUserAction(name) {
1193   chrome.metricsPrivate.recordUserAction('BookmarkManager_Command_' + name);
1197  * The currently selected bookmark, based on where the user is clicking.
1198  * @return {string} The ID of the currently selected bookmark (could be from
1199  *     tree view or list view).
1200  */
1201 function getSelectedId() {
1202   if (document.activeElement == bmm.tree)
1203     return bmm.tree.selectedItem.bookmarkId;
1204   var selectedItem = bmm.list.selectedItem;
1205   return selectedItem && bmm.isFolder(selectedItem) ?
1206       selectedItem.id : bmm.tree.selectedItem.bookmarkId;
1210  * Pastes the copied/cutted bookmark into the right location depending whether
1211  * if it was called from Organize Menu or from Context Menu.
1212  * @param {string} id The id of the element being pasted from.
1213  */
1214 function pasteBookmark(id) {
1215   recordUserAction('Paste');
1216   selectItemsAfterUserAction(/** @type {BookmarkList} */(bmm.list));
1217   chrome.bookmarkManagerPrivate.paste(id, getSelectedBookmarkIds());
1221  * Returns true if child is contained in another selected folder.
1222  * Traces parent nodes up the tree until a selected ancestor or root is found.
1223  */
1224 function hasSelectedAncestor(parentNode) {
1225   function contains(arr, item) {
1226     for (var i = 0; i < arr.length; i++)
1227         if (arr[i] === item)
1228           return true;
1229     return false;
1230   }
1232   // Don't search top level, cannot select permanent nodes in search.
1233   if (parentNode == null || parentNode.id <= 2)
1234     return false;
1236   // Found selected ancestor.
1237   if (contains(getSelectedBookmarkNodes(), parentNode))
1238     return true;
1240   // Keep digging.
1241   return hasSelectedAncestor(
1242       bmm.tree.getBookmarkNodeById(parentNode.parentId));
1246  * @param {EventTarget=} opt_target A target to get bookmark IDs from.
1247  * @return {Array<string>} An array of bookmarks IDs.
1248  */
1249 function getFilteredSelectedBookmarkIds(opt_target) {
1250   // Remove duplicates from filteredIds and return.
1251   var filteredIds = [];
1252   // Selected nodes to iterate through for matches.
1253   var nodes = getSelectedBookmarkNodes(opt_target);
1255   for (var i = 0; i < nodes.length; i++)
1256     if (!hasSelectedAncestor(bmm.tree.getBookmarkNodeById(nodes[i].parentId)))
1257       filteredIds.splice(0, 0, nodes[i].id);
1259   return filteredIds;
1263  * Handler for the command event. This is used for context menu of list/tree
1264  * and organized menu.
1265  * @param {!Event} e The event object.
1266  */
1267 function handleCommand(e) {
1268   var command = e.command;
1269   var target = assertInstanceof(e.target, HTMLElement);
1270   switch (command.id) {
1271     case 'import-menu-command':
1272       recordUserAction('Import');
1273       chrome.bookmarks.import();
1274       break;
1276     case 'export-menu-command':
1277       recordUserAction('Export');
1278       chrome.bookmarks.export();
1279       break;
1281     case 'undo-command':
1282       if (performGlobalUndo) {
1283         recordUserAction('UndoGlobal');
1284         performGlobalUndo();
1285       } else {
1286         recordUserAction('UndoNone');
1287       }
1288       break;
1290     case 'show-in-folder-command':
1291       recordUserAction('ShowInFolder');
1292       showInFolder();
1293       break;
1295     case 'open-in-new-tab-command':
1296     case 'open-in-background-tab-command':
1297       recordUserAction('OpenInNewTab');
1298       openBookmarks(LinkKind.BACKGROUND_TAB, target);
1299       break;
1301     case 'open-in-new-window-command':
1302       recordUserAction('OpenInNewWindow');
1303       openBookmarks(LinkKind.WINDOW, target);
1304       break;
1306     case 'open-incognito-window-command':
1307       recordUserAction('OpenIncognito');
1308       openBookmarks(LinkKind.INCOGNITO, target);
1309       break;
1311     case 'delete-from-folders-menu-command':
1312       target = bmm.tree;
1313     case 'delete-command':
1314       recordUserAction('Delete');
1315       deleteBookmarks(target);
1316       break;
1318     case 'copy-from-folders-menu-command':
1319       target = bmm.tree;
1320     case 'copy-command':
1321       recordUserAction('Copy');
1322       chrome.bookmarkManagerPrivate.copy(getSelectedBookmarkIds(target),
1323                                          updatePasteCommand);
1324       break;
1326     case 'cut-from-folders-menu-command':
1327       target = bmm.tree;
1328     case 'cut-command':
1329       recordUserAction('Cut');
1330       chrome.bookmarkManagerPrivate.cut(getSelectedBookmarkIds(target),
1331                                         function() {
1332                                           updatePasteCommand();
1333                                           updateSearchResults();
1334                                         });
1335       break;
1337     case 'paste-from-organize-menu-command':
1338       pasteBookmark(bmm.list.parentId);
1339       break;
1341     case 'paste-from-folders-menu-command':
1342       pasteBookmark(bmm.tree.selectedItem.bookmarkId);
1343       break;
1345     case 'paste-from-context-menu-command':
1346       pasteBookmark(getSelectedId());
1347       break;
1349     case 'sort-command':
1350       recordUserAction('Sort');
1351       chrome.bookmarkManagerPrivate.sortChildren(bmm.list.parentId);
1352       break;
1355     case 'rename-folder-from-folders-menu-command':
1356       target = bmm.tree;
1357     case 'rename-folder-command':
1358       editItem(target);
1359       break;
1361     case 'edit-command':
1362       recordUserAction('Edit');
1363       editItem();
1364       break;
1366     case 'new-folder-from-folders-menu-command':
1367       target = bmm.tree;
1368     case 'new-folder-command':
1369       recordUserAction('NewFolder');
1370       newFolder(target);
1371       break;
1373     case 'add-new-bookmark-command':
1374       recordUserAction('AddPage');
1375       addPage();
1376       break;
1378     case 'open-in-same-window-command':
1379       recordUserAction('OpenInSame');
1380       openItem();
1381       break;
1383     case 'undo-delete-command':
1384     case 'undo-delete-from-folders-menu-command':
1385       recordUserAction('UndoDelete');
1386       undoDelete();
1387       break;
1388   }
1391 // Execute the copy, cut and paste commands when those events are dispatched by
1392 // the browser. This allows us to rely on the browser to handle the keyboard
1393 // shortcuts for these commands.
1394 function installEventHandlerForCommand(eventName, commandId) {
1395   function handle(e) {
1396     if (document.activeElement != bmm.list &&
1397         document.activeElement != bmm.tree)
1398       return;
1399     var command = $(commandId);
1400     if (!command.disabled) {
1401       command.execute();
1402       if (e)
1403         e.preventDefault();  // Prevent the system beep.
1404     }
1405   }
1406   if (eventName == 'paste') {
1407     // Paste is a bit special since we need to do an async call to see if we
1408     // can paste because the paste command might not be up to date.
1409     document.addEventListener(eventName, function(e) {
1410       updatePasteCommand(handle);
1411     });
1412   } else {
1413     document.addEventListener(eventName, handle);
1414   }
1417 function initializeSplitter() {
1418   var splitter = document.querySelector('.main > .splitter');
1419   Splitter.decorate(splitter);
1421   var splitterStyle = splitter.previousElementSibling.style;
1423   // The splitter persists the size of the left component in the local store.
1424   if ('treeWidth' in window.localStorage)
1425     splitterStyle.width = window.localStorage['treeWidth'];
1427   splitter.addEventListener('resize', function(e) {
1428     window.localStorage['treeWidth'] = splitterStyle.width;
1429   });
1432 function initializeBookmarkManager() {
1433   // Sometimes the extension API is not initialized.
1434   if (!chrome.bookmarks)
1435     console.error('Bookmarks extension API is not available');
1437   chrome.bookmarkManagerPrivate.getStrings(continueInitializeBookmarkManager);
1440 function continueInitializeBookmarkManager(localizedStrings) {
1441   loadLocalizedStrings(localizedStrings);
1443   bmm.treeLookup[searchTreeItem.bookmarkId] = searchTreeItem;
1445   cr.ui.decorate('cr-menu', Menu);
1446   cr.ui.decorate('button[menu]', MenuButton);
1447   cr.ui.decorate('command', Command);
1448   BookmarkList.decorate($('list'));
1449   BookmarkTree.decorate($('tree'));
1451   bmm.list.addEventListener('canceledit', handleCancelEdit);
1452   bmm.list.addEventListener('canExecute', handleCanExecuteForList);
1453   bmm.list.addEventListener('change', updateAllCommands);
1454   bmm.list.addEventListener('contextmenu', updateEditingCommands);
1455   bmm.list.addEventListener('dblclick', handleDoubleClickForList);
1456   bmm.list.addEventListener('edit', handleEdit);
1457   bmm.list.addEventListener('rename', handleRename);
1458   bmm.list.addEventListener('urlClicked', handleUrlClickedForList);
1460   bmm.tree.addEventListener('canExecute', handleCanExecuteForTree);
1461   bmm.tree.addEventListener('change', handleChangeForTree);
1462   bmm.tree.addEventListener('contextmenu', updateEditingCommands);
1463   bmm.tree.addEventListener('rename', handleRename);
1464   bmm.tree.addEventListener('load', handleLoadForTree);
1466   cr.ui.contextMenuHandler.addContextMenuProperty(
1467       /** @type {!Element} */(bmm.tree));
1468   bmm.list.contextMenu = $('context-menu');
1469   bmm.tree.contextMenu = $('context-menu');
1471   // We listen to hashchange so that we can update the currently shown folder
1472   // when // the user goes back and forward in the history.
1473   window.addEventListener('hashchange', processHash);
1475   document.querySelector('header form').onsubmit =
1476       /** @type {function(Event=)} */(function(e) {
1477     setSearch($('term').value);
1478     e.preventDefault();
1479   });
1481   $('term').addEventListener('search', handleSearch);
1482   $('term').addEventListener('canExecute', handleCanExecuteForSearchBox);
1484   $('folders-button').addEventListener('click', handleMenuButtonClicked);
1485   $('organize-button').addEventListener('click', handleMenuButtonClicked);
1487   document.addEventListener('canExecute', handleCanExecuteForDocument);
1488   document.addEventListener('command', handleCommand);
1490   // Listen to copy, cut and paste events and execute the associated commands.
1491   installEventHandlerForCommand('copy', 'copy-command');
1492   installEventHandlerForCommand('cut', 'cut-command');
1493   installEventHandlerForCommand('paste', 'paste-from-organize-menu-command');
1495   // Install shortcuts
1496   for (var name in commandShortcutMap) {
1497     $(name + '-command').shortcut = commandShortcutMap[name];
1498   }
1500   // Disable almost all commands at startup.
1501   var commands = document.querySelectorAll('command');
1502   for (var i = 0, command; command = commands[i]; ++i) {
1503     if (command.id != 'import-menu-command' &&
1504         command.id != 'export-menu-command') {
1505       command.disabled = true;
1506     }
1507   }
1509   chrome.bookmarkManagerPrivate.canEdit(function(result) {
1510     canEdit = result;
1511   });
1513   chrome.systemPrivate.getIncognitoModeAvailability(function(result) {
1514     // TODO(rustema): propagate policy value to the bookmark manager when it
1515     // changes.
1516     incognitoModeAvailability = result;
1517   });
1519   chrome.bookmarkManagerPrivate.canOpenNewWindows(function(result) {
1520     canOpenNewWindows = result;
1521   });
1523   cr.ui.FocusOutlineManager.forDocument(document);
1524   initializeSplitter();
1525   bmm.addBookmarkModelListeners();
1526   dnd.init(selectItemsAfterUserAction);
1527   bmm.tree.reload();
1530 initializeBookmarkManager();
1531 })();