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