Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / bookmark_manager / js / main.js
blob7dc284f4cdeb0129ff518ba9b715e60660cdc5f3
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;
159   var sameParent = bmm.list.parentId == id;
160   if (!sameParent)
161     updateParentId(id);
163   updateAllCommands();
165   var metricsId = folderMetricsNameMap[id.replace(/^q=.*/, 'q=')] ||
166                   folderMetricsNameMap['subfolder'];
167   chrome.metricsPrivate.recordUserAction(
168       'BookmarkManager_NavigateTo_' + metricsId);
170   if (opt_callback) {
171     if (sameParent)
172       opt_callback();
173     else
174       addOneShotEventListener(bmm.list, 'load', opt_callback);
175   }
179  * Updates the parent ID of the bookmark list and selects the correct tree item.
180  * @param {string} id The id.
181  */
182 function updateParentId(id) {
183   // Setting list.parentId fires 'load' event.
184   bmm.list.parentId = id;
186   // When tree.selectedItem changed, tree view calls navigatTo() then it
187   // calls updateHash() when list view displayed specified folder.
188   bmm.tree.selectedItem = bmm.treeLookup[id] || bmm.tree.selectedItem;
191 // Process the location hash. This is called by onhashchange and when the page
192 // is first loaded.
193 function processHash() {
194   var id = window.location.hash.slice(1);
195   if (!id) {
196     // If we do not have a hash, select first item in the tree.
197     id = bmm.tree.items[0].bookmarkId;
198   }
200   var valid = false;
201   if (/^e=/.test(id)) {
202     id = id.slice(2);
204     // If hash contains e=, edit the item specified.
205     chrome.bookmarks.get(id, function(bookmarkNodes) {
206       // Verify the node to edit is a valid node.
207       if (!bookmarkNodes || bookmarkNodes.length != 1)
208         return;
209       var bookmarkNode = bookmarkNodes[0];
211       // After the list reloads, edit the desired bookmark.
212       var editBookmark = function() {
213         var index = bmm.list.dataModel.findIndexById(bookmarkNode.id);
214         if (index != -1) {
215           var sm = bmm.list.selectionModel;
216           sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
217           scrollIntoViewAndMakeEditable(index);
218         }
219       };
221       var parentId = assert(bookmarkNode.parentId);
222       navigateTo(parentId, editBookmark);
223     });
225     // We handle the two cases of navigating to the bookmark to be edited
226     // above. Don't run the standard navigation code below.
227     return;
228   } else if (/^q=/.test(id)) {
229     // In case we got a search hash, update the text input and the
230     // bmm.treeLookup to use the new id.
231     setSearch(id.slice(2));
232     valid = true;
233   }
235   // Navigate to bookmark 'id' (which may be a query of the form q=query).
236   if (valid) {
237     updateParentId(id);
238   } else {
239     // We need to verify that this is a correct ID.
240     chrome.bookmarks.get(id, function(items) {
241       if (items && items.length == 1)
242         updateParentId(id);
243     });
244   }
247 // Activate is handled by the open-in-same-window-command.
248 function handleDoubleClickForList(e) {
249   if (e.button == 0)
250     $('open-in-same-window-command').execute();
253 // The list dispatches an event when the user clicks on the URL or the Show in
254 // folder part.
255 function handleUrlClickedForList(e) {
256   getLinkController().openUrlFromEvent(e.url, e.originalEvent);
257   chrome.bookmarkManagerPrivate.recordLaunch();
260 function handleSearch(e) {
261   setSearch(this.value);
265  * Navigates to the search results for the search text.
266  * @param {string} searchText The text to search for.
267  */
268 function setSearch(searchText) {
269   if (searchText) {
270     // Only update search item if we have a search term. We never want the
271     // search item to be for an empty search.
272     delete bmm.treeLookup[searchTreeItem.bookmarkId];
273     var id = searchTreeItem.bookmarkId = 'q=' + searchText;
274     bmm.treeLookup[searchTreeItem.bookmarkId] = searchTreeItem;
275   }
277   var input = $('term');
278   // Do not update the input if the user is actively using the text input.
279   if (document.activeElement != input)
280     input.value = searchText;
282   if (searchText) {
283     bmm.tree.add(searchTreeItem);
284     bmm.tree.selectedItem = searchTreeItem;
285   } else {
286     // Go "home".
287     bmm.tree.selectedItem = bmm.tree.items[0];
288     id = bmm.tree.selectedItem.bookmarkId;
289   }
291   navigateTo(id);
295  * This returns the user visible path to the folder where the bookmark is
296  * located.
297  * @param {number} parentId The ID of the parent folder.
298  * @return {string} The path to the the bookmark,
299  */
300 function getFolder(parentId) {
301   var parentNode = bmm.tree.getBookmarkNodeById(parentId);
302   if (parentNode) {
303     var s = parentNode.title;
304     if (parentNode.parentId != bmm.ROOT_ID) {
305       return getFolder(parentNode.parentId) + '/' + s;
306     }
307     return s;
308   }
311 function handleLoadForTree(e) {
312   processHash();
316  * Returns a promise for all the URLs in the {@code nodes} and the direct
317  * children of {@code nodes}.
318  * @param {!Array<BookmarkTreeNode>} nodes .
319  * @return {!Promise<Array<string>>} .
320  */
321 function getAllUrls(nodes) {
322   var urls = [];
324   // Adds the node and all its direct children.
325   // TODO(deepak.m1): Here node should exist. When we delete the nodes then
326   // datamodel gets updated but still it shows deleted items as selected items
327   // and accessing those nodes throws chrome.runtime.lastError. This cause
328   // undefined value for node. Please refer https://crbug.com/480935.
329   function addNodes(node) {
330     if (!node || node.id == 'new')
331       return;
333     if (node.children) {
334       node.children.forEach(function(child) {
335         if (!bmm.isFolder(child))
336           urls.push(child.url);
337       });
338     } else {
339       urls.push(node.url);
340     }
341   }
343   // Get a future promise for the nodes.
344   var promises = nodes.map(function(node) {
345     if (bmm.isFolder(assert(node)))
346       return bmm.loadSubtree(node.id);
347     // Not a folder so we already have all the data we need.
348     return Promise.resolve(node);
349   });
351   return Promise.all(promises).then(function(nodes) {
352     nodes.forEach(addNodes);
353     return urls;
354   });
358  * Returns the nodes (non recursive) to use for the open commands.
359  * @param {HTMLElement} target
360  * @return {!Array<BookmarkTreeNode>}
361  */
362 function getNodesForOpen(target) {
363   if (target == bmm.tree) {
364     if (bmm.tree.selectedItem != searchTreeItem)
365       return bmm.tree.selectedFolders;
366     // Fall through to use all nodes in the list.
367   } else {
368     var items = bmm.list.selectedItems;
369     if (items.length)
370       return items;
371   }
373   // The list starts off with a null dataModel. We can get here during startup.
374   if (!bmm.list.dataModel)
375     return [];
377   // Return an array based on the dataModel.
378   return bmm.list.dataModel.slice();
382  * Returns a promise that will contain all URLs of all the selected bookmarks
383  * and the nested bookmarks for use with the open commands.
384  * @param {HTMLElement} target The target list or tree.
385  * @return {Promise<Array<string>>} .
386  */
387 function getUrlsForOpenCommands(target) {
388   return getAllUrls(getNodesForOpen(target));
391 function notNewNode(node) {
392   return node.id != 'new';
396  * Helper function that updates the canExecute and labels for the open-like
397  * commands.
398  * @param {!cr.ui.CanExecuteEvent} e The event fired by the command system.
399  * @param {!cr.ui.Command} command The command we are currently processing.
400  * @param {string} singularId The string id of singular form of the menu label.
401  * @param {string} pluralId The string id of menu label if the singular form is
402        not used.
403  * @param {boolean} commandDisabled Whether the menu item should be disabled
404        no matter what bookmarks are selected.
405  */
406 function updateOpenCommand(e, command, singularId, pluralId, commandDisabled) {
407   if (singularId) {
408     // The command label reflects the selection which might not reflect
409     // how many bookmarks will be opened. For example if you right click an
410     // empty area in a folder with 1 bookmark the text should still say "all".
411     var selectedNodes = getSelectedBookmarkNodes(e.target).filter(notNewNode);
412     var singular = selectedNodes.length == 1 && !bmm.isFolder(selectedNodes[0]);
413     command.label = loadTimeData.getString(singular ? singularId : pluralId);
414   }
416   if (commandDisabled) {
417     command.disabled = true;
418     e.canExecute = false;
419     return;
420   }
422   getUrlsForOpenCommands(assertInstanceof(e.target, HTMLElement)).then(
423       function(urls) {
424     var disabled = !urls.length;
425     command.disabled = disabled;
426     e.canExecute = !disabled;
427   });
431  * Calls the backend to figure out if we can paste the clipboard into the active
432  * folder.
433  * @param {Function=} opt_f Function to call after the state has been updated.
434  */
435 function updatePasteCommand(opt_f) {
436   function update(commandId, canPaste) {
437     $(commandId).disabled = !canPaste;
438   }
440   var promises = [];
442   // The folders menu.
443   // We can not paste into search item in tree.
444   if (bmm.tree.selectedItem && bmm.tree.selectedItem != searchTreeItem) {
445     promises.push(new Promise(function(resolve) {
446       var id = bmm.tree.selectedItem.bookmarkId;
447       chrome.bookmarkManagerPrivate.canPaste(id, function(canPaste) {
448         update('paste-from-folders-menu-command', canPaste);
449         resolve(canPaste);
450       });
451     }));
452   } else {
453     // Tree's not loaded yet.
454     update('paste-from-folders-menu-command', false);
455   }
457   // The organize menu.
458   var listId = bmm.list.parentId;
459   if (bmm.list.isSearch() || !listId) {
460     // We cannot paste into search view or the list isn't ready.
461     update('paste-from-organize-menu-command', false);
462   } else {
463     promises.push(new Promise(function(resolve) {
464       chrome.bookmarkManagerPrivate.canPaste(listId, function(canPaste) {
465         update('paste-from-organize-menu-command', canPaste);
466         resolve(canPaste);
467       });
468     }));
469   }
471   Promise.all(promises).then(function() {
472     var cmd;
473     if (document.activeElement == bmm.list)
474       cmd = 'paste-from-organize-menu-command';
475     else if (document.activeElement == bmm.tree)
476       cmd = 'paste-from-folders-menu-command';
478     if (cmd)
479       update('paste-from-context-menu-command', !$(cmd).disabled);
481     if (opt_f) opt_f();
482   });
485 function handleCanExecuteForSearchBox(e) {
486   var command = e.command;
487   switch (command.id) {
488     case 'delete-command':
489     case 'undo-command':
490       // Pass the delete and undo commands through
491       // (fixes http://crbug.com/278112).
492       e.canExecute = false;
493       break;
494   }
497 function handleCanExecuteForDocument(e) {
498   var command = e.command;
499   switch (command.id) {
500     case 'import-menu-command':
501       e.canExecute = canEdit;
502       break;
504     case 'export-menu-command':
505       // We can always execute the export-menu command.
506       e.canExecute = true;
507       break;
509     case 'sort-command':
510       e.canExecute = !bmm.list.isSearch() &&
511           bmm.list.dataModel && bmm.list.dataModel.length > 1 &&
512           !isUnmodifiable(bmm.tree.getBookmarkNodeById(bmm.list.parentId));
513       break;
515     case 'undo-command':
516       // Because the global undo command has no visible UI, always enable it,
517       // and just make it a no-op if undo is not possible.
518       e.canExecute = true;
519       break;
521     default:
522       canExecuteForList(e);
523       if (!e.defaultPrevented)
524         canExecuteForTree(e);
525       break;
526   }
530  * Helper function for handling canExecute for the list and the tree.
531  * @param {!cr.ui.CanExecuteEvent} e Can execute event object.
532  * @param {boolean} isSearch Whether the user is trying to do a command on
533  *     search.
534  */
535 function canExecuteShared(e, isSearch) {
536   var command = e.command;
537   switch (command.id) {
538     case 'paste-from-folders-menu-command':
539     case 'paste-from-organize-menu-command':
540     case 'paste-from-context-menu-command':
541       updatePasteCommand();
542       break;
544     case 'add-new-bookmark-command':
545     case 'new-folder-command':
546     case 'new-folder-from-folders-menu-command':
547       var parentId = computeParentFolderForNewItem();
548       var unmodifiable = isUnmodifiable(
549           bmm.tree.getBookmarkNodeById(parentId));
550       e.canExecute = !isSearch && canEdit && !unmodifiable;
551       break;
553     case 'open-in-new-tab-command':
554       updateOpenCommand(e, command, 'open_in_new_tab', 'open_all', false);
555       break;
557     case 'open-in-background-tab-command':
558       updateOpenCommand(e, command, '', '', false);
559       break;
561     case 'open-in-new-window-command':
562       updateOpenCommand(e, command,
563           'open_in_new_window', 'open_all_new_window',
564           // Disabled when incognito is forced.
565           incognitoModeAvailability == 'forced' || !canOpenNewWindows);
566       break;
568     case 'open-incognito-window-command':
569       updateOpenCommand(e, command,
570           'open_incognito', 'open_all_incognito',
571           // Not available when incognito is disabled.
572           incognitoModeAvailability == 'disabled');
573       break;
575     case 'undo-delete-command':
576       e.canExecute = !!lastDeleted;
577       break;
578   }
582  * Helper function for handling canExecute for the list and document.
583  * @param {!cr.ui.CanExecuteEvent} e Can execute event object.
584  */
585 function canExecuteForList(e) {
586   function hasSelected() {
587     return !!bmm.list.selectedItem;
588   }
590   function hasSingleSelected() {
591     return bmm.list.selectedItems.length == 1;
592   }
594   function canCopyItem(item) {
595     return item.id != 'new';
596   }
598   function canCopyItems() {
599     var selectedItems = bmm.list.selectedItems;
600     return selectedItems && selectedItems.some(canCopyItem);
601   }
603   function isSearch() {
604     return bmm.list.isSearch();
605   }
607   var command = e.command;
608   switch (command.id) {
609     case 'rename-folder-command':
610       // Show rename if a single folder is selected.
611       var items = bmm.list.selectedItems;
612       if (items.length != 1) {
613         e.canExecute = false;
614         command.hidden = true;
615       } else {
616         var isFolder = bmm.isFolder(items[0]);
617         e.canExecute = isFolder && canEdit && !hasUnmodifiable(items);
618         command.hidden = !isFolder;
619       }
620       break;
622     case 'edit-command':
623       // Show the edit command if not a folder.
624       var items = bmm.list.selectedItems;
625       if (items.length != 1) {
626         e.canExecute = false;
627         command.hidden = false;
628       } else {
629         var isFolder = bmm.isFolder(items[0]);
630         e.canExecute = !isFolder && canEdit && !hasUnmodifiable(items);
631         command.hidden = isFolder;
632       }
633       break;
635     case 'show-in-folder-command':
636       e.canExecute = isSearch() && hasSingleSelected();
637       break;
639     case 'delete-command':
640     case 'cut-command':
641       e.canExecute = canCopyItems() && canEdit &&
642           !hasUnmodifiable(bmm.list.selectedItems);
643       break;
645     case 'copy-command':
646       e.canExecute = canCopyItems();
647       break;
649     case 'open-in-same-window-command':
650       e.canExecute = (e.target == bmm.list) && hasSelected();
651       break;
653     default:
654       canExecuteShared(e, isSearch());
655   }
658 // Update canExecute for the commands when the list is the active element.
659 function handleCanExecuteForList(e) {
660   if (e.target != bmm.list) return;
661   canExecuteForList(e);
664 // Update canExecute for the commands when the tree is the active element.
665 function handleCanExecuteForTree(e) {
666   if (e.target != bmm.tree) return;
667   canExecuteForTree(e);
670 function canExecuteForTree(e) {
671   function hasSelected() {
672     return !!bmm.tree.selectedItem;
673   }
675   function isSearch() {
676     return bmm.tree.selectedItem == searchTreeItem;
677   }
679   function isTopLevelItem() {
680     return bmm.tree.selectedItem &&
681            bmm.tree.selectedItem.parentNode == bmm.tree;
682   }
684   var command = e.command;
685   switch (command.id) {
686     case 'rename-folder-command':
687     case 'rename-folder-from-folders-menu-command':
688       command.hidden = false;
689       e.canExecute = hasSelected() && !isTopLevelItem() && canEdit &&
690           !hasUnmodifiable(bmm.tree.selectedFolders);
691       break;
693     case 'edit-command':
694       command.hidden = true;
695       e.canExecute = false;
696       break;
698     case 'delete-command':
699     case 'delete-from-folders-menu-command':
700     case 'cut-command':
701     case 'cut-from-folders-menu-command':
702       e.canExecute = hasSelected() && !isTopLevelItem() && canEdit &&
703           !hasUnmodifiable(bmm.tree.selectedFolders);
704       break;
706     case 'copy-command':
707     case 'copy-from-folders-menu-command':
708       e.canExecute = hasSelected() && !isTopLevelItem();
709       break;
711     case 'undo-delete-from-folders-menu-command':
712       e.canExecute = lastDeleted && lastDeleted.target == bmm.tree;
713       break;
715     default:
716       canExecuteShared(e, isSearch());
717   }
721  * Update the canExecute state of all the commands.
722  */
723 function updateAllCommands() {
724   var commands = document.querySelectorAll('command');
725   for (var i = 0; i < commands.length; i++) {
726     commands[i].canExecuteChange();
727   }
730 function updateEditingCommands() {
731   var editingCommands = [
732     'add-new-bookmark',
733     'cut',
734     'cut-from-folders-menu',
735     'delete',
736     'edit',
737     'new-folder',
738     'paste-from-context-menu',
739     'paste-from-folders-menu',
740     'paste-from-organize-menu',
741     'rename-folder',
742     'sort',
743   ];
745   chrome.bookmarkManagerPrivate.canEdit(function(result) {
746     if (result != canEdit) {
747       canEdit = result;
748       editingCommands.forEach(function(baseId) {
749         $(baseId + '-command').canExecuteChange();
750       });
751     }
752   });
755 function handleChangeForTree(e) {
756   navigateTo(bmm.tree.selectedItem.bookmarkId);
759 function handleMenuButtonClicked(e) {
760   updateEditingCommands();
762   if (e.currentTarget.id == 'folders-menu') {
763     $('copy-from-folders-menu-command').canExecuteChange();
764     $('undo-delete-from-folders-menu-command').canExecuteChange();
765   } else {
766     $('copy-command').canExecuteChange();
767   }
770 function handleRename(e) {
771   var item = e.target;
772   var bookmarkNode = item.bookmarkNode;
773   chrome.bookmarks.update(bookmarkNode.id, {title: item.label});
774   performGlobalUndo = null;  // This can't be undone, so disable global undo.
777 function handleEdit(e) {
778   var item = e.target;
779   var bookmarkNode = item.bookmarkNode;
780   var context = {
781     title: bookmarkNode.title
782   };
783   if (!bmm.isFolder(bookmarkNode))
784     context.url = bookmarkNode.url;
786   if (bookmarkNode.id == 'new') {
787     selectItemsAfterUserAction(/** @type {BookmarkList} */(bmm.list));
789     // New page
790     context.parentId = bookmarkNode.parentId;
791     chrome.bookmarks.create(context, function(node) {
792       // A new node was created and will get added to the list due to the
793       // handler.
794       var dataModel = bmm.list.dataModel;
795       var index = dataModel.indexOf(bookmarkNode);
796       dataModel.splice(index, 1);
798       // Select new item.
799       var newIndex = dataModel.findIndexById(node.id);
800       if (newIndex != -1) {
801         var sm = bmm.list.selectionModel;
802         bmm.list.scrollIndexIntoView(newIndex);
803         sm.leadIndex = sm.anchorIndex = sm.selectedIndex = newIndex;
804       }
805     });
806   } else {
807     // Edit
808     chrome.bookmarks.update(bookmarkNode.id, context);
809   }
810   performGlobalUndo = null;  // This can't be undone, so disable global undo.
813 function handleCancelEdit(e) {
814   var item = e.target;
815   var bookmarkNode = item.bookmarkNode;
816   if (bookmarkNode.id == 'new') {
817     var dataModel = bmm.list.dataModel;
818     var index = dataModel.findIndexById('new');
819     dataModel.splice(index, 1);
820   }
824  * Navigates to the folder that the selected item is in and selects it. This is
825  * used for the show-in-folder command.
826  */
827 function showInFolder() {
828   var bookmarkNode = bmm.list.selectedItem;
829   if (!bookmarkNode)
830     return;
831   var parentId = bookmarkNode.parentId;
833   // After the list is loaded we should select the revealed item.
834   function selectItem() {
835     var index = bmm.list.dataModel.findIndexById(bookmarkNode.id);
836     if (index == -1)
837       return;
838     var sm = bmm.list.selectionModel;
839     sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
840     bmm.list.scrollIndexIntoView(index);
841   }
843   var treeItem = bmm.treeLookup[parentId];
844   treeItem.reveal();
846   navigateTo(parentId, selectItem);
850  * @return {!cr.LinkController} The link controller used to open links based on
851  *     user clicks and keyboard actions.
852  */
853 function getLinkController() {
854   return linkController ||
855       (linkController = new cr.LinkController(loadTimeData));
859  * Returns the selected bookmark nodes of the provided tree or list.
860  * If |opt_target| is not provided or null the active element is used.
861  * Only call this if the list or the tree is focused.
862  * @param {EventTarget=} opt_target The target list or tree.
863  * @return {!Array} Array of bookmark nodes.
864  */
865 function getSelectedBookmarkNodes(opt_target) {
866   return (opt_target || document.activeElement) == bmm.tree ?
867       bmm.tree.selectedFolders : bmm.list.selectedItems;
871  * @param {EventTarget=} opt_target The target list or tree.
872  * @return {!Array<string>} An array of the selected bookmark IDs.
873  */
874 function getSelectedBookmarkIds(opt_target) {
875   var selectedNodes = getSelectedBookmarkNodes(opt_target);
876   selectedNodes.sort(function(a, b) { return a.index - b.index });
877   return selectedNodes.map(function(node) {
878     return node.id;
879   });
883  * @param {BookmarkTreeNode} node The node to test.
884  * @return {boolean} Whether the given node is unmodifiable.
885  */
886 function isUnmodifiable(node) {
887   return !!(node && node.unmodifiable);
891  * @param {Array<BookmarkTreeNode>} nodes A list of BookmarkTreeNodes.
892  * @return {boolean} Whether any of the nodes is managed.
893  */
894 function hasUnmodifiable(nodes) {
895   return nodes.some(isUnmodifiable);
899  * Opens the selected bookmarks.
900  * @param {cr.LinkKind} kind The kind of link we want to open.
901  * @param {HTMLElement=} opt_eventTarget The target of the user initiated event.
902  */
903 function openBookmarks(kind, opt_eventTarget) {
904   // If we have selected any folders, we need to find all the bookmarks one
905   // level down. We use multiple async calls to getSubtree instead of getting
906   // the whole tree since we would like to minimize the amount of data sent.
908   var urlsP = getUrlsForOpenCommands(opt_eventTarget ? opt_eventTarget : null);
909   urlsP.then(function(urls) {
910     getLinkController().openUrls(assert(urls), kind);
911     chrome.bookmarkManagerPrivate.recordLaunch();
912   });
916  * Opens an item in the list.
917  */
918 function openItem() {
919   var bookmarkNodes = getSelectedBookmarkNodes();
920   // If we double clicked or pressed enter on a single folder, navigate to it.
921   if (bookmarkNodes.length == 1 && bmm.isFolder(bookmarkNodes[0]))
922     navigateTo(bookmarkNodes[0].id);
923   else
924     openBookmarks(LinkKind.FOREGROUND_TAB);
928  * Refreshes search results after delete or undo-delete.
929  * This ensures children of deleted folders do not remain in results
930  */
931 function updateSearchResults() {
932   if (bmm.list.isSearch())
933     bmm.list.reload();
937  * Deletes the selected bookmarks. The bookmarks are saved in memory in case
938  * the user needs to undo the deletion.
939  * @param {EventTarget=} opt_target The deleter of bookmarks.
940  */
941 function deleteBookmarks(opt_target) {
942   var selectedIds = getSelectedBookmarkIds(opt_target);
943   if (!selectedIds.length)
944     return;
946   var filteredIds = getFilteredSelectedBookmarkIds(opt_target);
947   lastDeleted = {nodes: [], target: opt_target || document.activeElement};
949   function performDelete() {
950     // Only remove filtered ids.
951     chrome.bookmarkManagerPrivate.removeTrees(filteredIds);
952     $('undo-delete-command').canExecuteChange();
953     $('undo-delete-from-folders-menu-command').canExecuteChange();
954     performGlobalUndo = undoDelete;
955   }
957   // First, store information about the bookmarks being deleted.
958   // Store all selected ids.
959   selectedIds.forEach(function(id) {
960     chrome.bookmarks.getSubTree(id, function(results) {
961       lastDeleted.nodes.push(results);
963       // When all nodes have been saved, perform the deletion.
964       if (lastDeleted.nodes.length === selectedIds.length) {
965         performDelete();
966         updateSearchResults();
967       }
968     });
969   });
973  * Restores a tree of bookmarks under a specified folder.
974  * @param {BookmarkTreeNode} node The node to restore.
975  * @param {(string|number)=} opt_parentId If a string is passed, it's the ID of
976  *     the folder to restore under. If not specified or a number is passed, the
977  *     original parentId of the node will be used.
978  */
979 function restoreTree(node, opt_parentId) {
980   var bookmarkInfo = {
981     parentId: typeof opt_parentId == 'string' ? opt_parentId : node.parentId,
982     title: node.title,
983     index: node.index,
984     url: node.url
985   };
987   chrome.bookmarks.create(bookmarkInfo, function(result) {
988     if (!result) {
989       console.error('Failed to restore bookmark.');
990       return;
991     }
993     if (node.children) {
994       // Restore the children using the new ID for this node.
995       node.children.forEach(function(child) {
996         restoreTree(child, result.id);
997       });
998     }
1000     updateSearchResults();
1001   });
1005  * Restores the last set of bookmarks that was deleted.
1006  */
1007 function undoDelete() {
1008   lastDeleted.nodes.forEach(function(arr) {
1009     arr.forEach(restoreTree);
1010   });
1011   lastDeleted = null;
1012   $('undo-delete-command').canExecuteChange();
1013   $('undo-delete-from-folders-menu-command').canExecuteChange();
1015   // Only a single level of undo is supported, so disable global undo now.
1016   performGlobalUndo = null;
1020  * Computes folder for "Add Page" and "Add Folder".
1021  * @return {string} The id of folder node where we'll create new page/folder.
1022  */
1023 function computeParentFolderForNewItem() {
1024   if (document.activeElement == bmm.tree)
1025     return bmm.list.parentId;
1026   var selectedItem = bmm.list.selectedItem;
1027   return selectedItem && bmm.isFolder(selectedItem) ?
1028       selectedItem.id : bmm.list.parentId;
1032  * Callback for rename folder and edit command. This starts editing for
1033  * the passed in target, or the selected item.
1034  * @param {EventTarget=} opt_target The target to start editing. If absent or
1035  *     null, the selected item will be edited instead.
1036  */
1037 function editItem(opt_target) {
1038   if ((opt_target || document.activeElement) == bmm.tree) {
1039     bmm.tree.selectedItem.editing = true;
1040   } else {
1041     var li = bmm.list.getListItem(bmm.list.selectedItem);
1042     if (li)
1043       li.editing = true;
1044   }
1048  * Callback for the new folder command. This creates a new folder and starts
1049  * a rename of it.
1050  * @param {EventTarget=} opt_target The target to create a new folder in.
1051  */
1052 function newFolder(opt_target) {
1053   performGlobalUndo = null;  // This can't be undone, so disable global undo.
1055   var parentId = computeParentFolderForNewItem();
1056   var selectedItems = bmm.list.selectedItems;
1057   var newIndex;
1058   // Callback is called after tree and list data model updated.
1059   function createFolder(callback) {
1060     if (selectedItems.length == 1 && document.activeElement != bmm.tree &&
1061         !bmm.isFolder(selectedItems[0]) && selectedItems[0].id != 'new') {
1062       newIndex = bmm.list.dataModel.indexOf(selectedItems[0]) + 1;
1063     }
1064     chrome.bookmarks.create({
1065       title: loadTimeData.getString('new_folder_name'),
1066       parentId: parentId,
1067       index: newIndex
1068     }, callback);
1069   }
1071   if ((opt_target || document.activeElement) == bmm.tree) {
1072     createFolder(function(newNode) {
1073       navigateTo(newNode.id, function() {
1074         bmm.treeLookup[newNode.id].editing = true;
1075       });
1076     });
1077     return;
1078   }
1080   function editNewFolderInList() {
1081     createFolder(function(newNode) {
1082       var index = newNode.index;
1083       var sm = bmm.list.selectionModel;
1084       sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
1085       scrollIntoViewAndMakeEditable(index);
1086     });
1087   }
1089   navigateTo(parentId, editNewFolderInList);
1093  * Scrolls the list item into view and makes it editable.
1094  * @param {number} index The index of the item to make editable.
1095  */
1096 function scrollIntoViewAndMakeEditable(index) {
1097   bmm.list.scrollIndexIntoView(index);
1098   // onscroll is now dispatched asynchronously so we have to postpone
1099   // the rest.
1100   setTimeout(function() {
1101     var item = bmm.list.getListItemByIndex(index);
1102     if (item)
1103       item.editing = true;
1104   }, 0);
1108  * Adds a page to the current folder. This is called by the
1109  * add-new-bookmark-command handler.
1110  */
1111 function addPage() {
1112   var parentId = computeParentFolderForNewItem();
1113   var selectedItems = bmm.list.selectedItems;
1114   var newIndex;
1115   function editNewBookmark() {
1116     if (selectedItems.length == 1 && document.activeElement != bmm.tree &&
1117         !bmm.isFolder(selectedItems[0])) {
1118       newIndex = bmm.list.dataModel.indexOf(selectedItems[0]) + 1;
1119     }
1121     var fakeNode = {
1122       title: '',
1123       url: '',
1124       parentId: parentId,
1125       index: newIndex,
1126       id: 'new'
1127     };
1128     var dataModel = bmm.list.dataModel;
1129     var index = dataModel.length;
1130     if (newIndex != undefined)
1131       index = newIndex;
1132     dataModel.splice(index, 0, fakeNode);
1133     var sm = bmm.list.selectionModel;
1134     sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
1135     scrollIntoViewAndMakeEditable(index);
1136   };
1138   navigateTo(parentId, editNewBookmark);
1142  * This function is used to select items after a user action such as paste, drop
1143  * add page etc.
1144  * @param {BookmarkList|BookmarkTree} target The target of the user action.
1145  * @param {string=} opt_selectedTreeId If provided, then select that tree id.
1146  */
1147 function selectItemsAfterUserAction(target, opt_selectedTreeId) {
1148   // We get one onCreated event per item so we delay the handling until we get
1149   // no more events coming.
1151   var ids = [];
1152   var timer;
1154   function handle(id, bookmarkNode) {
1155     clearTimeout(timer);
1156     if (opt_selectedTreeId || bmm.list.parentId == bookmarkNode.parentId)
1157       ids.push(id);
1158     timer = setTimeout(handleTimeout, 50);
1159   }
1161   function handleTimeout() {
1162     chrome.bookmarks.onCreated.removeListener(handle);
1163     chrome.bookmarks.onMoved.removeListener(handle);
1165     if (opt_selectedTreeId && ids.indexOf(opt_selectedTreeId) != -1) {
1166       var index = ids.indexOf(opt_selectedTreeId);
1167       if (index != -1 && opt_selectedTreeId in bmm.treeLookup) {
1168         bmm.tree.selectedItem = bmm.treeLookup[opt_selectedTreeId];
1169       }
1170     } else if (target == bmm.list) {
1171       var dataModel = bmm.list.dataModel;
1172       var firstIndex = dataModel.findIndexById(ids[0]);
1173       var lastIndex = dataModel.findIndexById(ids[ids.length - 1]);
1174       if (firstIndex != -1 && lastIndex != -1) {
1175         var selectionModel = bmm.list.selectionModel;
1176         selectionModel.selectedIndex = -1;
1177         selectionModel.selectRange(firstIndex, lastIndex);
1178         selectionModel.anchorIndex = selectionModel.leadIndex = lastIndex;
1179         bmm.list.focus();
1180       }
1181     }
1183     bmm.list.endBatchUpdates();
1184   }
1186   bmm.list.startBatchUpdates();
1188   chrome.bookmarks.onCreated.addListener(handle);
1189   chrome.bookmarks.onMoved.addListener(handle);
1190   timer = setTimeout(handleTimeout, 300);
1194  * Record user action.
1195  * @param {string} name An user action name.
1196  */
1197 function recordUserAction(name) {
1198   chrome.metricsPrivate.recordUserAction('BookmarkManager_Command_' + name);
1202  * The currently selected bookmark, based on where the user is clicking.
1203  * @return {string} The ID of the currently selected bookmark (could be from
1204  *     tree view or list view).
1205  */
1206 function getSelectedId() {
1207   if (document.activeElement == bmm.tree)
1208     return bmm.tree.selectedItem.bookmarkId;
1209   var selectedItem = bmm.list.selectedItem;
1210   return selectedItem && bmm.isFolder(selectedItem) ?
1211       selectedItem.id : bmm.tree.selectedItem.bookmarkId;
1215  * Pastes the copied/cutted bookmark into the right location depending whether
1216  * if it was called from Organize Menu or from Context Menu.
1217  * @param {string} id The id of the element being pasted from.
1218  */
1219 function pasteBookmark(id) {
1220   recordUserAction('Paste');
1221   selectItemsAfterUserAction(/** @type {BookmarkList} */(bmm.list));
1222   chrome.bookmarkManagerPrivate.paste(id, getSelectedBookmarkIds());
1226  * Returns true if child is contained in another selected folder.
1227  * Traces parent nodes up the tree until a selected ancestor or root is found.
1228  */
1229 function hasSelectedAncestor(parentNode) {
1230   function contains(arr, item) {
1231     for (var i = 0; i < arr.length; i++)
1232         if (arr[i] === item)
1233           return true;
1234     return false;
1235   }
1237   // Don't search top level, cannot select permanent nodes in search.
1238   if (parentNode == null || parentNode.id <= 2)
1239     return false;
1241   // Found selected ancestor.
1242   if (contains(getSelectedBookmarkNodes(), parentNode))
1243     return true;
1245   // Keep digging.
1246   return hasSelectedAncestor(
1247       bmm.tree.getBookmarkNodeById(parentNode.parentId));
1251  * @param {EventTarget=} opt_target A target to get bookmark IDs from.
1252  * @return {Array<string>} An array of bookmarks IDs.
1253  */
1254 function getFilteredSelectedBookmarkIds(opt_target) {
1255   // Remove duplicates from filteredIds and return.
1256   var filteredIds = [];
1257   // Selected nodes to iterate through for matches.
1258   var nodes = getSelectedBookmarkNodes(opt_target);
1260   for (var i = 0; i < nodes.length; i++)
1261     if (!hasSelectedAncestor(bmm.tree.getBookmarkNodeById(nodes[i].parentId)))
1262       filteredIds.splice(0, 0, nodes[i].id);
1264   return filteredIds;
1268  * Handler for the command event. This is used for context menu of list/tree
1269  * and organized menu.
1270  * @param {!Event} e The event object.
1271  */
1272 function handleCommand(e) {
1273   var command = e.command;
1274   var target = assertInstanceof(e.target, HTMLElement);
1275   switch (command.id) {
1276     case 'import-menu-command':
1277       recordUserAction('Import');
1278       chrome.bookmarks.import();
1279       break;
1281     case 'export-menu-command':
1282       recordUserAction('Export');
1283       chrome.bookmarks.export();
1284       break;
1286     case 'undo-command':
1287       if (performGlobalUndo) {
1288         recordUserAction('UndoGlobal');
1289         performGlobalUndo();
1290       } else {
1291         recordUserAction('UndoNone');
1292       }
1293       break;
1295     case 'show-in-folder-command':
1296       recordUserAction('ShowInFolder');
1297       showInFolder();
1298       break;
1300     case 'open-in-new-tab-command':
1301     case 'open-in-background-tab-command':
1302       recordUserAction('OpenInNewTab');
1303       openBookmarks(LinkKind.BACKGROUND_TAB, target);
1304       break;
1306     case 'open-in-new-window-command':
1307       recordUserAction('OpenInNewWindow');
1308       openBookmarks(LinkKind.WINDOW, target);
1309       break;
1311     case 'open-incognito-window-command':
1312       recordUserAction('OpenIncognito');
1313       openBookmarks(LinkKind.INCOGNITO, target);
1314       break;
1316     case 'delete-from-folders-menu-command':
1317       target = bmm.tree;
1318     case 'delete-command':
1319       recordUserAction('Delete');
1320       deleteBookmarks(target);
1321       break;
1323     case 'copy-from-folders-menu-command':
1324       target = bmm.tree;
1325     case 'copy-command':
1326       recordUserAction('Copy');
1327       chrome.bookmarkManagerPrivate.copy(getSelectedBookmarkIds(target),
1328                                          updatePasteCommand);
1329       break;
1331     case 'cut-from-folders-menu-command':
1332       target = bmm.tree;
1333     case 'cut-command':
1334       recordUserAction('Cut');
1335       chrome.bookmarkManagerPrivate.cut(getSelectedBookmarkIds(target),
1336                                         function() {
1337                                           updatePasteCommand();
1338                                           updateSearchResults();
1339                                         });
1340       break;
1342     case 'paste-from-organize-menu-command':
1343       pasteBookmark(bmm.list.parentId);
1344       break;
1346     case 'paste-from-folders-menu-command':
1347       pasteBookmark(bmm.tree.selectedItem.bookmarkId);
1348       break;
1350     case 'paste-from-context-menu-command':
1351       pasteBookmark(getSelectedId());
1352       break;
1354     case 'sort-command':
1355       recordUserAction('Sort');
1356       chrome.bookmarkManagerPrivate.sortChildren(bmm.list.parentId);
1357       break;
1360     case 'rename-folder-from-folders-menu-command':
1361       target = bmm.tree;
1362     case 'rename-folder-command':
1363       editItem(target);
1364       break;
1366     case 'edit-command':
1367       recordUserAction('Edit');
1368       editItem();
1369       break;
1371     case 'new-folder-from-folders-menu-command':
1372       target = bmm.tree;
1373     case 'new-folder-command':
1374       recordUserAction('NewFolder');
1375       newFolder(target);
1376       break;
1378     case 'add-new-bookmark-command':
1379       recordUserAction('AddPage');
1380       addPage();
1381       break;
1383     case 'open-in-same-window-command':
1384       recordUserAction('OpenInSame');
1385       openItem();
1386       break;
1388     case 'undo-delete-command':
1389     case 'undo-delete-from-folders-menu-command':
1390       recordUserAction('UndoDelete');
1391       undoDelete();
1392       break;
1393   }
1396 // Execute the copy, cut and paste commands when those events are dispatched by
1397 // the browser. This allows us to rely on the browser to handle the keyboard
1398 // shortcuts for these commands.
1399 function installEventHandlerForCommand(eventName, commandId) {
1400   function handle(e) {
1401     if (document.activeElement != bmm.list &&
1402         document.activeElement != bmm.tree)
1403       return;
1404     var command = $(commandId);
1405     if (!command.disabled) {
1406       command.execute();
1407       if (e)
1408         e.preventDefault();  // Prevent the system beep.
1409     }
1410   }
1411   if (eventName == 'paste') {
1412     // Paste is a bit special since we need to do an async call to see if we
1413     // can paste because the paste command might not be up to date.
1414     document.addEventListener(eventName, function(e) {
1415       updatePasteCommand(handle);
1416     });
1417   } else {
1418     document.addEventListener(eventName, handle);
1419   }
1422 function initializeSplitter() {
1423   var splitter = document.querySelector('.main > .splitter');
1424   Splitter.decorate(splitter);
1426   var splitterStyle = splitter.previousElementSibling.style;
1428   // The splitter persists the size of the left component in the local store.
1429   if ('treeWidth' in window.localStorage)
1430     splitterStyle.width = window.localStorage['treeWidth'];
1432   splitter.addEventListener('resize', function(e) {
1433     window.localStorage['treeWidth'] = splitterStyle.width;
1434   });
1437 function initializeBookmarkManager() {
1438   // Sometimes the extension API is not initialized.
1439   if (!chrome.bookmarks)
1440     console.error('Bookmarks extension API is not available');
1442   chrome.bookmarkManagerPrivate.getStrings(continueInitializeBookmarkManager);
1445 function continueInitializeBookmarkManager(localizedStrings) {
1446   loadLocalizedStrings(localizedStrings);
1448   bmm.treeLookup[searchTreeItem.bookmarkId] = searchTreeItem;
1450   cr.ui.decorate('cr-menu', Menu);
1451   cr.ui.decorate('button[menu]', MenuButton);
1452   cr.ui.decorate('command', Command);
1453   BookmarkList.decorate($('list'));
1454   BookmarkTree.decorate($('tree'));
1456   bmm.list.addEventListener('canceledit', handleCancelEdit);
1457   bmm.list.addEventListener('canExecute', handleCanExecuteForList);
1458   bmm.list.addEventListener('change', updateAllCommands);
1459   bmm.list.addEventListener('contextmenu', updateEditingCommands);
1460   bmm.list.addEventListener('dblclick', handleDoubleClickForList);
1461   bmm.list.addEventListener('edit', handleEdit);
1462   bmm.list.addEventListener('rename', handleRename);
1463   bmm.list.addEventListener('urlClicked', handleUrlClickedForList);
1465   bmm.tree.addEventListener('canExecute', handleCanExecuteForTree);
1466   bmm.tree.addEventListener('change', handleChangeForTree);
1467   bmm.tree.addEventListener('contextmenu', updateEditingCommands);
1468   bmm.tree.addEventListener('rename', handleRename);
1469   bmm.tree.addEventListener('load', handleLoadForTree);
1471   cr.ui.contextMenuHandler.addContextMenuProperty(
1472       /** @type {!Element} */(bmm.tree));
1473   bmm.list.contextMenu = $('context-menu');
1474   bmm.tree.contextMenu = $('context-menu');
1476   // We listen to hashchange so that we can update the currently shown folder
1477   // when // the user goes back and forward in the history.
1478   window.addEventListener('hashchange', processHash);
1480   document.querySelector('header form').onsubmit =
1481       /** @type {function(Event=)} */(function(e) {
1482     setSearch($('term').value);
1483     e.preventDefault();
1484   });
1486   $('term').addEventListener('search', handleSearch);
1487   $('term').addEventListener('canExecute', handleCanExecuteForSearchBox);
1489   $('folders-button').addEventListener('click', handleMenuButtonClicked);
1490   $('organize-button').addEventListener('click', handleMenuButtonClicked);
1492   document.addEventListener('canExecute', handleCanExecuteForDocument);
1493   document.addEventListener('command', handleCommand);
1495   // Listen to copy, cut and paste events and execute the associated commands.
1496   installEventHandlerForCommand('copy', 'copy-command');
1497   installEventHandlerForCommand('cut', 'cut-command');
1498   installEventHandlerForCommand('paste', 'paste-from-organize-menu-command');
1500   // Install shortcuts
1501   for (var name in commandShortcutMap) {
1502     $(name + '-command').shortcut = commandShortcutMap[name];
1503   }
1505   // Disable almost all commands at startup.
1506   var commands = document.querySelectorAll('command');
1507   for (var i = 0, command; command = commands[i]; ++i) {
1508     if (command.id != 'import-menu-command' &&
1509         command.id != 'export-menu-command') {
1510       command.disabled = true;
1511     }
1512   }
1514   chrome.bookmarkManagerPrivate.canEdit(function(result) {
1515     canEdit = result;
1516   });
1518   chrome.systemPrivate.getIncognitoModeAvailability(function(result) {
1519     // TODO(rustema): propagate policy value to the bookmark manager when it
1520     // changes.
1521     incognitoModeAvailability = result;
1522   });
1524   chrome.bookmarkManagerPrivate.canOpenNewWindows(function(result) {
1525     canOpenNewWindows = result;
1526   });
1528   cr.ui.FocusOutlineManager.forDocument(document);
1529   initializeSplitter();
1530   bmm.addBookmarkModelListeners();
1531   dnd.init(selectItemsAfterUserAction);
1532   bmm.tree.reload();
1535 initializeBookmarkManager();
1536 })();