text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
  
  #include <stddef.h>
  
  #include "base/logging.h"
  #include "build/build_config.h"
  #include "chrome/browser/bookmarks/bookmark_model_factory.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/search/search.h"
  #include "chrome/common/pref_names.h"
  #include "chrome/common/url_constants.h"
  #include "components/bookmarks/browser/bookmark_model.h"
  #include "components/bookmarks/browser/bookmark_node_data.h"
  #include "components/bookmarks/common/bookmark_pref_names.h"
  #include "components/prefs/pref_service.h"
  #include "components/search/search.h"
  #include "components/url_formatter/url_formatter.h"
  #include "components/user_prefs/user_prefs.h"
  #include "content/public/browser/web_contents.h"
  #include "extensions/features/features.h"
  #include "ui/base/dragdrop/drag_drop_types.h"
  #include "ui/base/dragdrop/drop_target_event.h"
  
  #if BUILDFLAG(ENABLE_EXTENSIONS)
  #include "chrome/browser/extensions/api/commands/command_service.h"
  #include "extensions/browser/extension_registry.h"
  #include "extensions/common/extension_set.h"
  #endif
  
  #if defined(TOOLKIT_VIEWS)
  #include "ui/gfx/color_utils.h"
  #include "ui/gfx/paint_vector_icon.h"
  #include "ui/gfx/vector_icons_public.h"
  #endif
  
  #if defined(OS_WIN)
  #include "chrome/grit/theme_resources.h"
  #include "ui/base/material_design/material_design_controller.h"
  #include "ui/base/resource/resource_bundle.h"
  #endif
  
  using bookmarks::BookmarkModel;
  using bookmarks::BookmarkNode;
  
  namespace chrome {
  
  namespace {
  
  // The ways in which extensions may customize the bookmark shortcut.
  enum BookmarkShortcutDisposition {
    BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED,
    BOOKMARK_SHORTCUT_DISPOSITION_REMOVED,
    BOOKMARK_SHORTCUT_DISPOSITION_OVERRIDE_REQUESTED
  };
  
  // Indicates how the bookmark shortcut has been changed by extensions associated
  // with |profile|, if at all.
  BookmarkShortcutDisposition GetBookmarkShortcutDisposition(Profile* profile) {
  #if BUILDFLAG(ENABLE_EXTENSIONS)
    extensions::CommandService* command_service =
        extensions::CommandService::Get(profile);
  
    extensions::ExtensionRegistry* registry =
        extensions::ExtensionRegistry::Get(profile);
    if (!registry)
      return BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED;
  
    const extensions::ExtensionSet& extension_set =
        registry->enabled_extensions();
  
    // This flag tracks whether any extension wants the disposition to be
    // removed.
    bool removed = false;
    for (extensions::ExtensionSet::const_iterator i = extension_set.begin();
         i != extension_set.end();
         ++i) {
      // Use the overridden disposition if any extension wants it.
      if (command_service->RequestsBookmarkShortcutOverride(i->get()))
        return BOOKMARK_SHORTCUT_DISPOSITION_OVERRIDE_REQUESTED;
  
      if (!removed &&
          extensions::CommandService::RemovesBookmarkShortcut(i->get())) {
        removed = true;
      }
    }
  
    if (removed)
      return BOOKMARK_SHORTCUT_DISPOSITION_REMOVED;
  #endif
    return BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED;
  }
  
  #if defined(TOOLKIT_VIEWS) && !defined(OS_WIN)
  gfx::ImageSkia GetFolderIcon(gfx::VectorIconId id, SkColor text_color) {
    return gfx::CreateVectorIcon(id,
                                 color_utils::DeriveDefaultIconColor(text_color));
  }
  #endif
  
  }  // namespace
  
  GURL GetURLToBookmark(content::WebContents* web_contents) {
    DCHECK(web_contents);
    return search::IsInstantNTP(web_contents) ? GURL(kChromeUINewTabURL)
                                              : web_contents->GetURL();
  }
  
  void GetURLAndTitleToBookmark(content::WebContents* web_contents,
                                GURL* url,
                                base::string16* title) {
    *url = GetURLToBookmark(web_contents);
    *title = web_contents->GetTitle();
  }
  
  void ToggleBookmarkBarWhenVisible(content::BrowserContext* browser_context) {
    PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
    const bool always_show =
        !prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar);
  
    // The user changed when the bookmark bar is shown, update the preferences.
    prefs->SetBoolean(bookmarks::prefs::kShowBookmarkBar, always_show);
  }
  
  base::string16 FormatBookmarkURLForDisplay(const GURL& url) {
    // Because this gets re-parsed by FixupURL(), it's safe to omit the scheme
    // and trailing slash, and unescape most characters. However, it's
    // important not to drop any username/password, or unescape anything that
    // changes the URL's meaning.
    url_formatter::FormatUrlTypes format_types =
        url_formatter::kFormatUrlOmitAll &
        ~url_formatter::kFormatUrlOmitUsernamePassword;
  
    // If username is present, we must not omit the scheme because FixupURL() will
    // subsequently interpret the username as a scheme. crbug.com/639126
    if (url.has_username())
      format_types &= ~url_formatter::kFormatUrlOmitHTTP;
  
    return url_formatter::FormatUrl(url, format_types, net::UnescapeRule::SPACES,
                                    nullptr, nullptr, nullptr);
  }
  
  bool IsAppsShortcutEnabled(Profile* profile) {
    // Legacy supervised users can not have apps installed currently so there's no
    // need to show the apps shortcut.
    if (profile->IsLegacySupervised())
      return false;
  
  #if defined(USE_ASH)
    // Don't show the apps shortcut in ash since the app launcher is enabled.
    return false;
  #else
    return search::IsInstantExtendedAPIEnabled() && !profile->IsOffTheRecord();
  #endif
  }
  
  bool ShouldShowAppsShortcutInBookmarkBar(Profile* profile) {
    return IsAppsShortcutEnabled(profile) &&
           profile->GetPrefs()->GetBoolean(
               bookmarks::prefs::kShowAppsShortcutInBookmarkBar);
  }
  
  bool ShouldRemoveBookmarkThisPageUI(Profile* profile) {
    return GetBookmarkShortcutDisposition(profile) ==
           BOOKMARK_SHORTCUT_DISPOSITION_REMOVED;
  }
  
  bool ShouldRemoveBookmarkOpenPagesUI(Profile* profile) {
  #if BUILDFLAG(ENABLE_EXTENSIONS)
    extensions::ExtensionRegistry* registry =
        extensions::ExtensionRegistry::Get(profile);
    if (!registry)
      return false;
  
    const extensions::ExtensionSet& extension_set =
        registry->enabled_extensions();
  
    for (extensions::ExtensionSet::const_iterator i = extension_set.begin();
         i != extension_set.end();
         ++i) {
      if (extensions::CommandService::RemovesBookmarkOpenPagesShortcut(i->get()))
        return true;
    }
  #endif
  
    return false;
  }
  
  int GetBookmarkDragOperation(content::BrowserContext* browser_context,
                               const BookmarkNode* node) {
    PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
    BookmarkModel* model =
        BookmarkModelFactory::GetForBrowserContext(browser_context);
  
    int move = ui::DragDropTypes::DRAG_MOVE;
    if (!prefs->GetBoolean(bookmarks::prefs::kEditBookmarksEnabled) ||
        !model->client()->CanBeEditedByUser(node)) {
      move = ui::DragDropTypes::DRAG_NONE;
    }
    if (node->is_url())
      return ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK | move;
    return ui::DragDropTypes::DRAG_COPY | move;
  }
  
  int GetPreferredBookmarkDropOperation(int source_operations, int operations) {
    int common_ops = (source_operations & operations);
    if (!common_ops)
      return ui::DragDropTypes::DRAG_NONE;
    if (ui::DragDropTypes::DRAG_COPY & common_ops)
      return ui::DragDropTypes::DRAG_COPY;
    if (ui::DragDropTypes::DRAG_LINK & common_ops)
      return ui::DragDropTypes::DRAG_LINK;
    if (ui::DragDropTypes::DRAG_MOVE & common_ops)
      return ui::DragDropTypes::DRAG_MOVE;
    return ui::DragDropTypes::DRAG_NONE;
  }
  
  int GetBookmarkDropOperation(Profile* profile,
                               const ui::DropTargetEvent& event,
                               const bookmarks::BookmarkNodeData& data,
                               const BookmarkNode* parent,
                               int index) {
    const base::FilePath& profile_path = profile->GetPath();
  
    if (data.IsFromProfilePath(profile_path) && data.size() > 1)
      // Currently only accept one dragged node at a time.
      return ui::DragDropTypes::DRAG_NONE;
  
    if (!IsValidBookmarkDropLocation(profile, data, parent, index))
      return ui::DragDropTypes::DRAG_NONE;
  
    BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
    if (!model->client()->CanBeEditedByUser(parent))
      return ui::DragDropTypes::DRAG_NONE;
  
    const BookmarkNode* dragged_node =
        data.GetFirstNode(model, profile->GetPath());
    if (dragged_node) {
      // User is dragging from this profile.
      if (!model->client()->CanBeEditedByUser(dragged_node)) {
        // Do a copy instead of a move when dragging bookmarks that the user can't
        // modify.
        return ui::DragDropTypes::DRAG_COPY;
      }
      return ui::DragDropTypes::DRAG_MOVE;
    }
  
    // User is dragging from another app, copy.
    return GetPreferredBookmarkDropOperation(event.source_operations(),
        ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK);
  }
  
  bool IsValidBookmarkDropLocation(Profile* profile,
                                   const bookmarks::BookmarkNodeData& data,
                                   const BookmarkNode* drop_parent,
                                   int index) {
    if (!drop_parent->is_folder()) {
      NOTREACHED();
      return false;
    }
  
    if (!data.is_valid())
      return false;
  
    BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
    if (!model->client()->CanBeEditedByUser(drop_parent))
      return false;
  
    const base::FilePath& profile_path = profile->GetPath();
    if (data.IsFromProfilePath(profile_path)) {
      std::vector<const BookmarkNode*> nodes = data.GetNodes(model, profile_path);
      for (size_t i = 0; i < nodes.size(); ++i) {
        // Don't allow the drop if the user is attempting to drop on one of the
        // nodes being dragged.
        const BookmarkNode* node = nodes[i];
        int node_index = (drop_parent == node->parent()) ?
            drop_parent->GetIndexOf(nodes[i]) : -1;
        if (node_index != -1 && (index == node_index || index == node_index + 1))
          return false;
  
        // drop_parent can't accept a child that is an ancestor.
        if (drop_parent->HasAncestor(node))
          return false;
      }
      return true;
    }
    // From another profile, always accept.
    return true;
  }
  
  #if defined(TOOLKIT_VIEWS)
  // TODO(bsep): vectorize the Windows versions: crbug.com/564112
  gfx::ImageSkia GetBookmarkFolderIcon(SkColor text_color) {
  #if defined(OS_WIN)
    return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
        IDR_BOOKMARK_BAR_FOLDER);
  #else
    return GetFolderIcon(gfx::VectorIconId::FOLDER, text_color);
  #endif
  }
  
  gfx::ImageSkia GetBookmarkSupervisedFolderIcon(SkColor text_color) {
  #if defined(OS_WIN)
    return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
        IDR_BOOKMARK_BAR_FOLDER_SUPERVISED);
  #else
    return GetFolderIcon(gfx::VectorIconId::FOLDER_SUPERVISED, text_color);
  #endif
  }
  
  gfx::ImageSkia GetBookmarkManagedFolderIcon(SkColor text_color) {
  #if defined(OS_WIN)
    return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
        IDR_BOOKMARK_BAR_FOLDER_MANAGED);
  #else
    return GetFolderIcon(gfx::VectorIconId::FOLDER_MANAGED, text_color);
  #endif
  }
  #endif
  
  }  // namespace chrome
  