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 "base/basictypes.h"
8#include "base/logging.h"
9#include "base/prefs/pref_service.h"
10#include "base/strings/string_number_conversions.h"
11#include "chrome/browser/bookmarks/bookmark_model_factory.h"
12#include "chrome/browser/extensions/api/commands/command_service.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/search/search.h"
15#include "chrome/browser/ui/app_list/app_list_util.h"
16#include "chrome/browser/ui/bookmarks/bookmark_editor.h"
17#include "chrome/browser/ui/browser.h"
18#include "chrome/browser/ui/browser_navigator.h"
19#include "chrome/browser/ui/browser_window.h"
20#include "chrome/browser/ui/simple_message_box.h"
21#include "chrome/browser/ui/tabs/tab_strip_model.h"
22#include "chrome/common/pref_names.h"
23#include "chrome/common/url_constants.h"
24#include "chrome/grit/chromium_strings.h"
25#include "chrome/grit/generated_resources.h"
26#include "components/bookmarks/browser/bookmark_model.h"
27#include "components/search/search.h"
28#include "components/user_prefs/user_prefs.h"
29#include "content/public/browser/web_contents.h"
30#include "net/base/net_util.h"
31#include "ui/base/l10n/l10n_util.h"
32
33#if defined(ENABLE_EXTENSIONS)
34#include "extensions/browser/extension_registry.h"
35#include "extensions/common/extension_set.h"
36#endif
37
38namespace chrome {
39
40int num_bookmark_urls_before_prompting = 15;
41
42namespace {
43
44// The ways in which extensions may customize the bookmark shortcut.
45enum BookmarkShortcutDisposition {
46  BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED,
47  BOOKMARK_SHORTCUT_DISPOSITION_REMOVED,
48  BOOKMARK_SHORTCUT_DISPOSITION_OVERRIDDEN
49};
50
51// Iterator that iterates through a set of BookmarkNodes returning the URLs
52// for nodes that are urls, or the URLs for the children of non-url urls.
53// This does not recurse through all descendants, only immediate children.
54// The following illustrates
55// typical usage:
56// OpenURLIterator iterator(nodes);
57// while (iterator.has_next()) {
58//   const GURL* url = iterator.NextURL();
59//   // do something with |urll|.
60// }
61class OpenURLIterator {
62 public:
63  explicit OpenURLIterator(const std::vector<const BookmarkNode*>& nodes)
64      : child_index_(0),
65        next_(NULL),
66        parent_(nodes.begin()),
67        end_(nodes.end()) {
68    FindNext();
69  }
70
71  bool has_next() { return next_ != NULL;}
72
73  const GURL* NextURL() {
74    if (!has_next()) {
75      NOTREACHED();
76      return NULL;
77    }
78
79    const GURL* next = next_;
80    FindNext();
81    return next;
82  }
83
84 private:
85  // Seach next node which has URL.
86  void FindNext() {
87    for (; parent_ < end_; ++parent_, child_index_ = 0) {
88      if ((*parent_)->is_url()) {
89        next_ = &(*parent_)->url();
90        ++parent_;
91        child_index_ = 0;
92        return;
93      } else {
94        for (; child_index_ < (*parent_)->child_count(); ++child_index_) {
95          const BookmarkNode* child = (*parent_)->GetChild(child_index_);
96          if (child->is_url()) {
97            next_ = &child->url();
98            ++child_index_;
99            return;
100          }
101        }
102      }
103    }
104    next_ = NULL;
105  }
106
107  int child_index_;
108  const GURL* next_;
109  std::vector<const BookmarkNode*>::const_iterator parent_;
110  const std::vector<const BookmarkNode*>::const_iterator end_;
111
112  DISALLOW_COPY_AND_ASSIGN(OpenURLIterator);
113};
114
115bool ShouldOpenAll(gfx::NativeWindow parent,
116                   const std::vector<const BookmarkNode*>& nodes) {
117  int child_count = 0;
118  OpenURLIterator iterator(nodes);
119  while (iterator.has_next()) {
120    iterator.NextURL();
121    child_count++;
122  }
123
124  if (child_count < num_bookmark_urls_before_prompting)
125    return true;
126
127  return ShowMessageBox(parent,
128      l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
129      l10n_util::GetStringFUTF16(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL,
130                                 base::IntToString16(child_count)),
131      MESSAGE_BOX_TYPE_QUESTION) == MESSAGE_BOX_RESULT_YES;
132}
133
134// Returns the total number of descendants nodes.
135int ChildURLCountTotal(const BookmarkNode* node) {
136  int result = 0;
137  for (int i = 0; i < node->child_count(); ++i) {
138    const BookmarkNode* child = node->GetChild(i);
139    result++;
140    if (child->is_folder())
141      result += ChildURLCountTotal(child);
142  }
143  return result;
144}
145
146// Returns in |urls|, the url and title pairs for each open tab in browser.
147void GetURLsForOpenTabs(Browser* browser,
148                        std::vector<std::pair<GURL, base::string16> >* urls) {
149  for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
150    std::pair<GURL, base::string16> entry;
151    GetURLAndTitleToBookmark(browser->tab_strip_model()->GetWebContentsAt(i),
152                             &(entry.first), &(entry.second));
153    urls->push_back(entry);
154  }
155}
156
157// Indicates how the bookmark shortcut has been changed by extensions associated
158// with |profile|, if at all.
159BookmarkShortcutDisposition GetBookmarkShortcutDisposition(Profile* profile) {
160#if defined(ENABLE_EXTENSIONS)
161  extensions::CommandService* command_service =
162      extensions::CommandService::Get(profile);
163
164  extensions::ExtensionRegistry* registry =
165      extensions::ExtensionRegistry::Get(profile);
166  if (!registry)
167    return BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED;
168
169  const extensions::ExtensionSet& extension_set =
170      registry->enabled_extensions();
171
172  // This flag tracks whether any extension wants the disposition to be
173  // removed.
174  bool removed = false;
175  for (extensions::ExtensionSet::const_iterator i = extension_set.begin();
176       i != extension_set.end();
177       ++i) {
178    // Use the overridden disposition if any extension wants it.
179    if (command_service->OverridesBookmarkShortcut(i->get()))
180      return BOOKMARK_SHORTCUT_DISPOSITION_OVERRIDDEN;
181
182    if (!removed &&
183        extensions::CommandService::RemovesBookmarkShortcut(i->get())) {
184      removed = true;
185    }
186  }
187
188  if (removed)
189    return BOOKMARK_SHORTCUT_DISPOSITION_REMOVED;
190#endif
191  return BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED;
192}
193
194}  // namespace
195
196void OpenAll(gfx::NativeWindow parent,
197             content::PageNavigator* navigator,
198             const std::vector<const BookmarkNode*>& nodes,
199             WindowOpenDisposition initial_disposition,
200             content::BrowserContext* browser_context) {
201  if (!ShouldOpenAll(parent, nodes))
202    return;
203
204  // Opens all |nodes| of type URL and any children of |nodes| that are of type
205  // URL. |navigator| is the PageNavigator used to open URLs. After the first
206  // url is opened |opened_first_url| is set to true and |navigator| is set to
207  // the PageNavigator of the last active tab. This is done to handle a window
208  // disposition of new window, in which case we want subsequent tabs to open in
209  // that window.
210  bool opened_first_url = false;
211  WindowOpenDisposition disposition = initial_disposition;
212  OpenURLIterator iterator(nodes);
213  while (iterator.has_next()) {
214    const GURL* url = iterator.NextURL();
215    // When |initial_disposition| is OFF_THE_RECORD, a node which can't be
216    // opened in incognito window, it is detected using |browser_context|, is
217    // not opened.
218    if (initial_disposition == OFF_THE_RECORD &&
219        !IsURLAllowedInIncognito(*url, browser_context))
220      continue;
221
222    content::WebContents* opened_tab = navigator->OpenURL(
223        content::OpenURLParams(*url, content::Referrer(), disposition,
224                               ui::PAGE_TRANSITION_AUTO_BOOKMARK, false));
225
226    if (!opened_first_url) {
227      opened_first_url = true;
228      disposition = NEW_BACKGROUND_TAB;
229      // We opened the first URL which may have opened a new window or clobbered
230      // the current page, reset the navigator just to be sure. |opened_tab| may
231      // be NULL in tests.
232      if (opened_tab)
233        navigator = opened_tab;
234    }
235  }
236}
237
238void OpenAll(gfx::NativeWindow parent,
239             content::PageNavigator* navigator,
240             const BookmarkNode* node,
241             WindowOpenDisposition initial_disposition,
242             content::BrowserContext* browser_context) {
243  std::vector<const BookmarkNode*> nodes;
244  nodes.push_back(node);
245  OpenAll(parent, navigator, nodes, initial_disposition, browser_context);
246}
247
248bool ConfirmDeleteBookmarkNode(const BookmarkNode* node,
249                               gfx::NativeWindow window) {
250  DCHECK(node && node->is_folder() && !node->empty());
251  return ShowMessageBox(window,
252      l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
253      l10n_util::GetStringFUTF16Int(IDS_BOOKMARK_EDITOR_CONFIRM_DELETE,
254                                    ChildURLCountTotal(node)),
255      MESSAGE_BOX_TYPE_QUESTION) == MESSAGE_BOX_RESULT_YES;
256}
257
258void ShowBookmarkAllTabsDialog(Browser* browser) {
259  Profile* profile = browser->profile();
260  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile);
261  DCHECK(model && model->loaded());
262
263  const BookmarkNode* parent = model->GetParentForNewNodes();
264  BookmarkEditor::EditDetails details =
265      BookmarkEditor::EditDetails::AddFolder(parent, parent->child_count());
266  GetURLsForOpenTabs(browser, &(details.urls));
267  DCHECK(!details.urls.empty());
268
269  BookmarkEditor::Show(browser->window()->GetNativeWindow(), profile, details,
270                       BookmarkEditor::SHOW_TREE);
271}
272
273bool HasBookmarkURLs(const std::vector<const BookmarkNode*>& selection) {
274  OpenURLIterator iterator(selection);
275  return iterator.has_next();
276}
277
278bool HasBookmarkURLsAllowedInIncognitoMode(
279    const std::vector<const BookmarkNode*>& selection,
280    content::BrowserContext* browser_context) {
281  OpenURLIterator iterator(selection);
282  while (iterator.has_next()) {
283    const GURL* url = iterator.NextURL();
284    if (IsURLAllowedInIncognito(*url, browser_context))
285      return true;
286  }
287  return false;
288}
289
290GURL GetURLToBookmark(content::WebContents* web_contents) {
291  DCHECK(web_contents);
292  return IsInstantNTP(web_contents) ?
293      GURL(kChromeUINewTabURL) : web_contents->GetURL();
294}
295
296void GetURLAndTitleToBookmark(content::WebContents* web_contents,
297                              GURL* url,
298                              base::string16* title) {
299  *url = GetURLToBookmark(web_contents);
300  *title = web_contents->GetTitle();
301}
302
303void ToggleBookmarkBarWhenVisible(content::BrowserContext* browser_context) {
304  PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
305  const bool always_show =
306      !prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar);
307
308  // The user changed when the bookmark bar is shown, update the preferences.
309  prefs->SetBoolean(bookmarks::prefs::kShowBookmarkBar, always_show);
310}
311
312base::string16 FormatBookmarkURLForDisplay(const GURL& url,
313                                           const PrefService* prefs) {
314  std::string languages;
315  if (prefs)
316    languages = prefs->GetString(prefs::kAcceptLanguages);
317
318  // Because this gets re-parsed by FixupURL(), it's safe to omit the scheme
319  // and trailing slash, and unescape most characters.  However, it's
320  // important not to drop any username/password, or unescape anything that
321  // changes the URL's meaning.
322  return net::FormatUrl(
323      url, languages,
324      net::kFormatUrlOmitAll & ~net::kFormatUrlOmitUsernamePassword,
325      net::UnescapeRule::SPACES, NULL, NULL, NULL);
326}
327
328bool IsAppsShortcutEnabled(Profile* profile,
329                           chrome::HostDesktopType host_desktop_type) {
330  // Supervised users can not have apps installed currently so there's no need
331  // to show the apps shortcut.
332  if (profile->IsSupervised())
333    return false;
334
335  // Don't show the apps shortcut in ash since the app launcher is enabled.
336  if (host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH)
337    return false;
338
339  return chrome::IsInstantExtendedAPIEnabled() && !profile->IsOffTheRecord();
340}
341
342bool ShouldShowAppsShortcutInBookmarkBar(
343  Profile* profile,
344  chrome::HostDesktopType host_desktop_type) {
345  return IsAppsShortcutEnabled(profile, host_desktop_type) &&
346         profile->GetPrefs()->GetBoolean(
347             bookmarks::prefs::kShowAppsShortcutInBookmarkBar);
348}
349
350bool ShouldRemoveBookmarkThisPageUI(Profile* profile) {
351  return GetBookmarkShortcutDisposition(profile) ==
352         BOOKMARK_SHORTCUT_DISPOSITION_REMOVED;
353}
354
355bool ShouldRemoveBookmarkOpenPagesUI(Profile* profile) {
356#if defined(ENABLE_EXTENSIONS)
357  extensions::ExtensionRegistry* registry =
358      extensions::ExtensionRegistry::Get(profile);
359  if (!registry)
360    return false;
361
362  const extensions::ExtensionSet& extension_set =
363      registry->enabled_extensions();
364
365  for (extensions::ExtensionSet::const_iterator i = extension_set.begin();
366       i != extension_set.end();
367       ++i) {
368    if (extensions::CommandService::RemovesBookmarkOpenPagesShortcut(i->get()))
369      return true;
370  }
371#endif
372
373  return false;
374}
375
376}  // namespace chrome
377