910e62b5创建于 1月15日历史提交
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_UI_COCOA_BOOKMARKS_BOOKMARK_MENU_BRIDGE_H_
#define CHROME_BROWSER_UI_COCOA_BOOKMARKS_BOOKMARK_MENU_BRIDGE_H_

#include <map>

#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/bookmarks/bookmark_merged_surface_service_observer.h"
#include "components/bookmarks/browser/bookmark_model.h"

class BookmarkMergedSurfaceService;
struct BookmarkParentFolder;
class Profile;
@class NSImage;
@class NSMenu;
@class NSMenuItem;
@class BookmarkMenuCocoaController;

namespace bookmarks {
class BookmarkNode;
}

// C++ controller for the bookmark menu; one per AppController (which
// means there is only one).  When bookmarks are changed, this class
// takes care of updating Cocoa bookmark menus.  This is not named
// BookmarkMenuController to help avoid confusion between languages.
// This class needs to be C++, not ObjC, since it derives from
// BookmarkMergedSurfaceServiceObserver.
//
// Most Chromium Cocoa menu items are static from a nib (e.g. New
// Tab), but may be enabled/disabled under certain circumstances
// (e.g. Cut and Paste).  In addition, most Cocoa menu items have
// firstResponder: as a target.  Unusually, bookmark menu items are
// created dynamically.  They also have a target of
// BookmarkMenuCocoaController instead of firstResponder.
// See BookmarkMenuBridge::AddNodeToMenu()).
class BookmarkMenuBridge : public BookmarkMergedSurfaceServiceObserver {
 public:
  BookmarkMenuBridge(Profile* profile, NSMenu* menu_root);

  BookmarkMenuBridge(const BookmarkMenuBridge&) = delete;
  BookmarkMenuBridge& operator=(const BookmarkMenuBridge&) = delete;

  ~BookmarkMenuBridge() override;

  // BookmarkMergedSurfaceServiceObserver:
  void BookmarkMergedSurfaceServiceLoaded() override;
  void BookmarkMergedSurfaceServiceBeingDeleted() override;
  void BookmarkNodeAdded(const BookmarkParentFolder& parent,
                         size_t index) override;
  void BookmarkNodesRemoved(
      const BookmarkParentFolder& parent,
      const base::flat_set<const bookmarks::BookmarkNode*>& nodes) override;
  void BookmarkNodeMoved(const BookmarkParentFolder& old_parent,
                         size_t old_index,
                         const BookmarkParentFolder& new_parent,
                         size_t new_index) override;
  void BookmarkNodeChanged(const bookmarks::BookmarkNode* node) override;
  void BookmarkNodeFaviconChanged(const bookmarks::BookmarkNode* node) override;
  void BookmarkParentFolderChildrenReordered(
      const BookmarkParentFolder& folder) override;
  void BookmarkAllUserNodesRemoved() override;

  bool IsMenuRoot(NSMenu* menu);

  // Builds the main bookmark menu if it has been marked invalid. Its submenus
  // will NOT be built recursively.
  void UpdateRootMenuIfInvalid();

  // Builds a bookmark folder submenu on demand. Submenus of `menu` will NOT be
  // built recursively.
  void UpdateNonRootMenu(NSMenu* menu, const BookmarkParentFolder& folder);

  // I wish I had a "friend @class" construct.
  bookmarks::BookmarkModel* GetBookmarkModelForTesting();
  Profile* GetProfile();
  const base::FilePath& GetProfileDir() const;

  // Return the Bookmark menu.
  NSMenu* BookmarkMenu();

  // Clear all bookmarks from |menu_root_|.
  void ClearBookmarkMenu();

  // Resets |profile_| to nullptr. Called before the Profile is destroyed, if
  // this bridge is still needed. Rebuilds the entire menu recursively, so it
  // remains functional after the Profile is destroyed.
  //
  // Also performs some internal cleanup, like resetting observers and pointers
  // to the Profile and KeyedServices.
  void OnProfileWillBeDestroyed();

  // Returns the GUID of the BookmarkNode for |tag|. If |tag| is not the tag of
  // an NSMenuItem in this menu, returns the invalid GUID.
  base::Uuid TagToGUID(int64_t tag) const;

  // Returns the NSMenuItem for a given BookmarkNode, exposed publicly for
  // testing.
  NSMenuItem* MenuItemForNodeForTest(const bookmarks::BookmarkNode* node);

 private:
  friend class BookmarkMenuBridgeTest;

  // Returns true if the parent folder has at least one child.
  bool HasContent(const BookmarkParentFolder& folder);

  void BuildRootMenu(bool recurse);

  // Marks the bookmark menu as being invalid.
  void InvalidateMenu() { is_menu_valid_ = false; }
  bool IsMenuValid() const { return is_menu_valid_; }

  // Adds a submenu representing |folder| to |menu|. Uses the title of
  // |folder|'s underlying nodes as the submenu's title and the provided |image|
  // as its icon. If |recurse| is true, recursively adds all child nodes of
  // |node|.
  void AddSubmenu(NSMenu* menu,
                  const BookmarkParentFolder& folder,
                  NSImage* image,
                  bool recurse);

  // Adds all child nodes of |folder| to |menu|. If |recurse| is true,
  // recursively adds children of the child nodes.
  void AddChildrenToMenu(const BookmarkParentFolder& folder,
                         NSMenu* menu,
                         bool recurse);

  // Adds |node| as an item or a submenu to the bookmark menu. If |recurse| is
  // true and |node| has children, recursively adds them.
  //
  // TODO(jrg): add a counter to enforce maximum nodes added
  void AddNodeToMenu(const bookmarks::BookmarkNode* node,
                     NSMenu* menu,
                     bool recurse);

  // Helper for adding an item to our bookmark menu. An item which has a
  // localized title specified by |message_id| will be added to |menu|.
  // The item is also bound to |node| by tag. |command_id| selects the action.
  void AddItemToMenu(int command_id,
                     int message_id,
                     const bookmarks::BookmarkNode* node,
                     NSMenu* menu,
                     bool enabled);

  // This configures an NSMenuItem with all the data from a BookmarkNode. This
  // is used to update existing menu items, as well as to configure newly
  // created ones, like in AddNodeToMenu().
  void ConfigureMenuItem(const bookmarks::BookmarkNode* node, NSMenuItem* item);

  // Returns the NSMenuItem for a given BookmarkNode.
  NSMenuItem* MenuItemForNode(const bookmarks::BookmarkNode* node);

  // True iff the menu is up to date with the BookmarkMergedSurfaceService.
  bool is_menu_valid_;

  raw_ptr<Profile> profile_;  // weak
  raw_ptr<BookmarkMergedSurfaceService>
      bookmark_service_;  // owned by |profile_|.

  BookmarkMenuCocoaController* __strong controller_;
  NSMenu* __strong menu_root_;

  base::FilePath profile_dir_;  // Remembered after OnProfileWillBeDestroyed().

  // The folder image so we can use one copy for all.
  NSImage* __strong folder_image_;

  // In order to appropriately update items in the bookmark menu, without
  // forcing a rebuild, map the model's nodes to menu items.
  std::map<const bookmarks::BookmarkNode*, NSMenuItem*> bookmark_nodes_;

  // Tags are NSIntegers, so they're not necessarily large enough to hold a
  // GUID. Instead, map the tags to the corresponding GUIDs.
  std::map<int64_t, base::Uuid> tag_to_guid_;

  base::ScopedObservation<BookmarkMergedSurfaceService,
                          BookmarkMergedSurfaceServiceObserver>
      bookmark_service_observation_{this};
};

#endif  // CHROME_BROWSER_UI_COCOA_BOOKMARKS_BOOKMARK_MENU_BRIDGE_H_