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 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
6 
7 #include <stddef.h>
8 
9 #include "base/check.h"
10 #include "base/notreached.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "build/build_config.h"
13 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/search/search.h"
16 #include "chrome/common/pref_names.h"
17 #include "chrome/common/url_constants.h"
18 #include "components/bookmarks/browser/bookmark_model.h"
19 #include "components/bookmarks/browser/bookmark_node_data.h"
20 #include "components/bookmarks/common/bookmark_pref_names.h"
21 #include "components/dom_distiller/core/url_constants.h"
22 #include "components/dom_distiller/core/url_utils.h"
23 #include "components/prefs/pref_service.h"
24 #include "components/search/search.h"
25 #include "components/url_formatter/url_formatter.h"
26 #include "components/user_prefs/user_prefs.h"
27 #include "components/vector_icons/vector_icons.h"
28 #include "content/public/browser/web_contents.h"
29 #include "ui/base/dragdrop/drag_drop_types.h"
30 #include "ui/base/dragdrop/drop_target_event.h"
31 #include "ui/base/pointer/touch_ui_controller.h"
32 
33 #if defined(TOOLKIT_VIEWS)
34 #include "ui/gfx/canvas.h"
35 #include "ui/gfx/color_utils.h"
36 #include "ui/gfx/image/image_skia_source.h"
37 #include "ui/gfx/paint_vector_icon.h"
38 #include "ui/gfx/scoped_canvas.h"
39 #endif
40 
41 #if defined(OS_WIN) || defined(OS_MAC)
42 #include "chrome/grit/theme_resources.h"
43 #include "ui/base/resource/resource_bundle.h"
44 #include "ui/resources/grit/ui_resources.h"
45 #endif
46 
47 using bookmarks::BookmarkModel;
48 using bookmarks::BookmarkNode;
49 
50 namespace chrome {
51 
52 namespace {
53 
54 #if defined(TOOLKIT_VIEWS)
55 // Image source that flips the supplied source image in RTL.
56 class RTLFlipSource : public gfx::ImageSkiaSource {
57  public:
RTLFlipSource(gfx::ImageSkia source)58   explicit RTLFlipSource(gfx::ImageSkia source) : source_(std::move(source)) {}
59   ~RTLFlipSource() override = default;
60 
61   // gfx::ImageSkiaSource:
GetImageForScale(float scale)62   gfx::ImageSkiaRep GetImageForScale(float scale) override {
63     gfx::Canvas canvas(source_.size(), scale, false);
64     gfx::ScopedCanvas scoped_canvas(&canvas);
65     scoped_canvas.FlipIfRTL(source_.width());
66     canvas.DrawImageInt(source_, 0, 0);
67     return gfx::ImageSkiaRep(canvas.GetBitmap(), scale);
68   }
69 
70  private:
71   const gfx::ImageSkia source_;
72 };
73 
74 #if !defined(OS_WIN) && !defined(OS_MAC)
GetFolderIcon(const gfx::VectorIcon & icon,SkColor text_color)75 gfx::ImageSkia GetFolderIcon(const gfx::VectorIcon& icon, SkColor text_color) {
76   return gfx::CreateVectorIcon(icon,
77                                color_utils::DeriveDefaultIconColor(text_color));
78 }
79 #endif  // !defined(OS_WIN) && !defined(OS_MAC)
80 #endif  // defined(TOOLKIT_VIEWS)
81 
82 }  // namespace
83 
GetURLToBookmark(content::WebContents * web_contents)84 GURL GetURLToBookmark(content::WebContents* web_contents) {
85   DCHECK(web_contents);
86   if (search::IsInstantNTP(web_contents))
87     return GURL(kChromeUINewTabURL);
88   // Users cannot bookmark Reader Mode pages directly, so the bookmark
89   // interaction is as if it were with the original page.
90   if (dom_distiller::url_utils::IsDistilledPage(web_contents->GetURL())) {
91     return dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(
92         web_contents->GetURL());
93   }
94   return web_contents->GetURL();
95 }
96 
GetURLAndTitleToBookmark(content::WebContents * web_contents,GURL * url,base::string16 * title)97 void GetURLAndTitleToBookmark(content::WebContents* web_contents,
98                               GURL* url,
99                               base::string16* title) {
100   *url = GetURLToBookmark(web_contents);
101   if (dom_distiller::url_utils::IsDistilledPage(web_contents->GetURL())) {
102     // Users cannot bookmark Reader Mode pages directly. Instead, a bookmark
103     // is added for the original page and original title.
104     *title =
105         base::UTF8ToUTF16(dom_distiller::url_utils::GetTitleFromDistillerUrl(
106             web_contents->GetURL()));
107   } else {
108     *title = web_contents->GetTitle();
109   }
110 }
111 
ToggleBookmarkBarWhenVisible(content::BrowserContext * browser_context)112 void ToggleBookmarkBarWhenVisible(content::BrowserContext* browser_context) {
113   PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
114   const bool always_show =
115       !prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar);
116 
117   // The user changed when the bookmark bar is shown, update the preferences.
118   prefs->SetBoolean(bookmarks::prefs::kShowBookmarkBar, always_show);
119 }
120 
FormatBookmarkURLForDisplay(const GURL & url)121 base::string16 FormatBookmarkURLForDisplay(const GURL& url) {
122   // Because this gets re-parsed by FixupURL(), it's safe to omit the scheme
123   // and trailing slash, and unescape most characters. However, it's
124   // important not to drop any username/password, or unescape anything that
125   // changes the URL's meaning.
126   url_formatter::FormatUrlTypes format_types =
127       url_formatter::kFormatUrlOmitDefaults &
128       ~url_formatter::kFormatUrlOmitUsernamePassword;
129 
130   // If username is present, we must not omit the scheme because FixupURL() will
131   // subsequently interpret the username as a scheme. crbug.com/639126
132   if (url.has_username())
133     format_types &= ~url_formatter::kFormatUrlOmitHTTP;
134 
135   return url_formatter::FormatUrl(url, format_types, net::UnescapeRule::SPACES,
136                                   nullptr, nullptr, nullptr);
137 }
138 
IsAppsShortcutEnabled(Profile * profile)139 bool IsAppsShortcutEnabled(Profile* profile) {
140   // Legacy supervised users can not have apps installed currently so there's no
141   // need to show the apps shortcut.
142   if (profile->IsLegacySupervised())
143     return false;
144 
145 #if defined(OS_CHROMEOS)
146   // Chrome OS uses the app list / app launcher.
147   return false;
148 #else
149   return search::IsInstantExtendedAPIEnabled() && !profile->IsOffTheRecord();
150 #endif
151 }
152 
ShouldShowAppsShortcutInBookmarkBar(Profile * profile)153 bool ShouldShowAppsShortcutInBookmarkBar(Profile* profile) {
154   return IsAppsShortcutEnabled(profile) &&
155          profile->GetPrefs()->GetBoolean(
156              bookmarks::prefs::kShowAppsShortcutInBookmarkBar);
157 }
158 
GetBookmarkDragOperation(content::BrowserContext * browser_context,const BookmarkNode * node)159 int GetBookmarkDragOperation(content::BrowserContext* browser_context,
160                              const BookmarkNode* node) {
161   PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
162   BookmarkModel* model =
163       BookmarkModelFactory::GetForBrowserContext(browser_context);
164 
165   int move = ui::DragDropTypes::DRAG_MOVE;
166   if (!prefs->GetBoolean(bookmarks::prefs::kEditBookmarksEnabled) ||
167       !model->client()->CanBeEditedByUser(node)) {
168     move = ui::DragDropTypes::DRAG_NONE;
169   }
170   if (node->is_url())
171     return ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK | move;
172   return ui::DragDropTypes::DRAG_COPY | move;
173 }
174 
GetPreferredBookmarkDropOperation(int source_operations,int operations)175 int GetPreferredBookmarkDropOperation(int source_operations, int operations) {
176   int common_ops = (source_operations & operations);
177   if (!common_ops)
178     return ui::DragDropTypes::DRAG_NONE;
179   if (ui::DragDropTypes::DRAG_COPY & common_ops)
180     return ui::DragDropTypes::DRAG_COPY;
181   if (ui::DragDropTypes::DRAG_LINK & common_ops)
182     return ui::DragDropTypes::DRAG_LINK;
183   if (ui::DragDropTypes::DRAG_MOVE & common_ops)
184     return ui::DragDropTypes::DRAG_MOVE;
185   return ui::DragDropTypes::DRAG_NONE;
186 }
187 
GetBookmarkDropOperation(Profile * profile,const ui::DropTargetEvent & event,const bookmarks::BookmarkNodeData & data,const BookmarkNode * parent,size_t index)188 int GetBookmarkDropOperation(Profile* profile,
189                              const ui::DropTargetEvent& event,
190                              const bookmarks::BookmarkNodeData& data,
191                              const BookmarkNode* parent,
192                              size_t index) {
193   const base::FilePath& profile_path = profile->GetPath();
194 
195   if (data.IsFromProfilePath(profile_path) && data.size() > 1)
196     // Currently only accept one dragged node at a time.
197     return ui::DragDropTypes::DRAG_NONE;
198 
199   if (!IsValidBookmarkDropLocation(profile, data, parent, index))
200     return ui::DragDropTypes::DRAG_NONE;
201 
202   BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
203   if (!model->client()->CanBeEditedByUser(parent))
204     return ui::DragDropTypes::DRAG_NONE;
205 
206   const BookmarkNode* dragged_node =
207       data.GetFirstNode(model, profile->GetPath());
208   if (dragged_node) {
209     // User is dragging from this profile.
210     if (!model->client()->CanBeEditedByUser(dragged_node)) {
211       // Do a copy instead of a move when dragging bookmarks that the user can't
212       // modify.
213       return ui::DragDropTypes::DRAG_COPY;
214     }
215     return ui::DragDropTypes::DRAG_MOVE;
216   }
217 
218   // User is dragging from another app, copy.
219   return GetPreferredBookmarkDropOperation(event.source_operations(),
220       ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK);
221 }
222 
IsValidBookmarkDropLocation(Profile * profile,const bookmarks::BookmarkNodeData & data,const BookmarkNode * drop_parent,size_t index)223 bool IsValidBookmarkDropLocation(Profile* profile,
224                                  const bookmarks::BookmarkNodeData& data,
225                                  const BookmarkNode* drop_parent,
226                                  size_t index) {
227   if (!drop_parent->is_folder()) {
228     NOTREACHED();
229     return false;
230   }
231 
232   if (!data.is_valid())
233     return false;
234 
235   BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
236   if (!model->client()->CanBeEditedByUser(drop_parent))
237     return false;
238 
239   const base::FilePath& profile_path = profile->GetPath();
240   if (data.IsFromProfilePath(profile_path)) {
241     std::vector<const BookmarkNode*> nodes = data.GetNodes(model, profile_path);
242     for (size_t i = 0; i < nodes.size(); ++i) {
243       // Don't allow the drop if the user is attempting to drop on one of the
244       // nodes being dragged.
245       const BookmarkNode* node = nodes[i];
246       int node_index = (drop_parent == node->parent()) ?
247           drop_parent->GetIndexOf(nodes[i]) : -1;
248       if (node_index != -1 &&
249           (index == size_t{node_index} || index == size_t{node_index} + 1))
250         return false;
251 
252       // drop_parent can't accept a child that is an ancestor.
253       if (drop_parent->HasAncestor(node))
254         return false;
255     }
256     return true;
257   }
258   // From another profile, always accept.
259   return true;
260 }
261 
262 #if defined(TOOLKIT_VIEWS)
263 // TODO(bsep): vectorize the Windows versions: crbug.com/564112
GetBookmarkFolderIcon(SkColor text_color)264 ui::ImageModel GetBookmarkFolderIcon(SkColor text_color) {
265   gfx::ImageSkia folder;
266 #if defined(OS_WIN)
267   folder = *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
268       IDR_FOLDER_CLOSED);
269 #elif defined(OS_MAC)
270   int resource_id = color_utils::IsDark(text_color) ? IDR_FOLDER_CLOSED
271                                                     : IDR_FOLDER_CLOSED_WHITE;
272   folder = *ui::ResourceBundle::GetSharedInstance()
273                 .GetNativeImageNamed(resource_id)
274                 .ToImageSkia();
275 #else
276   folder = GetFolderIcon(ui::TouchUiController::Get()->touch_ui()
277                              ? vector_icons::kFolderTouchIcon
278                              : vector_icons::kFolderIcon,
279                          text_color);
280 #endif
281   // TODO(crbug.com/1119823): Return the unflipped image here
282   // (as a vector if possible); callers should have the responsibility to flip
283   // when painting as necessary.
284   return ui::ImageModel::FromImageSkia(
285       gfx::ImageSkia(std::make_unique<RTLFlipSource>(folder), folder.size()));
286 }
287 
GetBookmarkManagedFolderIcon(SkColor text_color)288 ui::ImageModel GetBookmarkManagedFolderIcon(SkColor text_color) {
289   gfx::ImageSkia folder;
290 #if defined(OS_WIN)
291   folder = *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
292       IDR_BOOKMARK_BAR_FOLDER_MANAGED);
293 #elif defined(OS_MAC)
294   int resource_id = color_utils::IsDark(text_color)
295                         ? IDR_BOOKMARK_BAR_FOLDER_MANAGED
296                         : IDR_BOOKMARK_BAR_FOLDER_MANAGED_WHITE;
297   folder = *ui::ResourceBundle::GetSharedInstance()
298                 .GetNativeImageNamed(resource_id)
299                 .ToImageSkia();
300 #else
301   folder = GetFolderIcon(ui::TouchUiController::Get()->touch_ui()
302                              ? vector_icons::kFolderManagedTouchIcon
303                              : vector_icons::kFolderManagedIcon,
304                          text_color);
305 #endif
306   // TODO(crbug.com/1119823): Return the unflipped image here
307   // (as a vector if possible); callers should have the responsibility to flip
308   // when painting as necessary.
309   return ui::ImageModel::FromImageSkia(
310       gfx::ImageSkia(std::make_unique<RTLFlipSource>(folder), folder.size()));
311 }
312 #endif
313 
314 }  // namespace chrome
315