content_setting_bubble_model.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 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/content_setting_bubble_model.h"
6
7#include "app/l10n_util.h"
8#include "base/command_line.h"
9#include "base/utf_string_conversions.h"
10#include "chrome/browser/blocked_popup_container.h"
11#include "chrome/browser/geolocation/geolocation_content_settings_map.h"
12#include "chrome/browser/host_content_settings_map.h"
13#include "chrome/browser/metrics/user_metrics.h"
14#include "chrome/browser/prefs/pref_service.h"
15#include "chrome/browser/profile.h"
16#include "chrome/browser/renderer_host/render_view_host.h"
17#include "chrome/browser/tab_contents/tab_contents.h"
18#include "chrome/browser/tab_contents/tab_contents_delegate.h"
19#include "chrome/browser/tab_contents/tab_specific_content_settings.h"
20#include "chrome/common/chrome_switches.h"
21#include "chrome/common/notification_service.h"
22#include "chrome/common/pref_names.h"
23#include "grit/generated_resources.h"
24#include "net/base/net_util.h"
25
26class ContentSettingTitleAndLinkModel : public ContentSettingBubbleModel {
27 public:
28  ContentSettingTitleAndLinkModel(TabContents* tab_contents, Profile* profile,
29      ContentSettingsType content_type)
30      : ContentSettingBubbleModel(tab_contents, profile, content_type) {
31     // Notifications do not have a bubble.
32     DCHECK_NE(content_type, CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
33     SetBlockedResources();
34     SetTitle();
35     SetManageLink();
36  }
37
38 private:
39  void SetBlockedResources() {
40    TabSpecificContentSettings* settings =
41        tab_contents()->GetTabSpecificContentSettings();
42    const std::set<std::string>& resources = settings->BlockedResourcesForType(
43        content_type());
44    for (std::set<std::string>::const_iterator it = resources.begin();
45        it != resources.end(); ++it) {
46      AddBlockedResource(*it);
47    }
48  }
49
50  void SetTitle() {
51    static const int kBlockedTitleIDs[] = {
52      IDS_BLOCKED_COOKIES_TITLE,
53      IDS_BLOCKED_IMAGES_TITLE,
54      IDS_BLOCKED_JAVASCRIPT_TITLE,
55      IDS_BLOCKED_PLUGINS_MESSAGE,
56      IDS_BLOCKED_POPUPS_TITLE,
57      0,  // Geolocation does not have an overall title.
58      0,  // Notifications do not have a bubble.
59    };
60    // Fields as for kBlockedTitleIDs, above.
61    static const int kResourceSpecificBlockedTitleIDs[] = {
62      0,
63      0,
64      0,
65      IDS_BLOCKED_PLUGINS_TITLE,
66      0,
67      0,
68      0,
69    };
70    static const int kAccessedTitleIDs[] = {
71      IDS_ACCESSED_COOKIES_TITLE,
72      0,
73      0,
74      0,
75      0,
76      0,
77      0,
78    };
79    COMPILE_ASSERT(arraysize(kAccessedTitleIDs) == CONTENT_SETTINGS_NUM_TYPES,
80                   Need_a_setting_for_every_content_settings_type);
81    COMPILE_ASSERT(arraysize(kBlockedTitleIDs) == CONTENT_SETTINGS_NUM_TYPES,
82                   Need_a_setting_for_every_content_settings_type);
83    COMPILE_ASSERT(arraysize(kResourceSpecificBlockedTitleIDs) ==
84        CONTENT_SETTINGS_NUM_TYPES,
85        Need_a_setting_for_every_content_settings_type);
86    const int *title_ids = kBlockedTitleIDs;
87    if (tab_contents() &&
88        tab_contents()->GetTabSpecificContentSettings()->IsContentAccessed(
89            content_type()) &&
90        !tab_contents()->GetTabSpecificContentSettings()->IsContentBlocked(
91            content_type())) {
92      title_ids = kAccessedTitleIDs;
93    } else if (!bubble_content().resource_identifiers.empty()) {
94      title_ids = kResourceSpecificBlockedTitleIDs;
95    }
96    if (title_ids[content_type()])
97      set_title(l10n_util::GetStringUTF8(title_ids[content_type()]));
98  }
99
100  void SetManageLink() {
101    static const int kLinkIDs[] = {
102      IDS_BLOCKED_COOKIES_LINK,
103      IDS_BLOCKED_IMAGES_LINK,
104      IDS_BLOCKED_JAVASCRIPT_LINK,
105      IDS_BLOCKED_PLUGINS_LINK,
106      IDS_BLOCKED_POPUPS_LINK,
107      IDS_GEOLOCATION_BUBBLE_MANAGE_LINK,
108      0,  // Notifications do not have a bubble.
109    };
110    COMPILE_ASSERT(arraysize(kLinkIDs) == CONTENT_SETTINGS_NUM_TYPES,
111                   Need_a_setting_for_every_content_settings_type);
112    set_manage_link(l10n_util::GetStringUTF8(kLinkIDs[content_type()]));
113  }
114
115  virtual void OnManageLinkClicked() {
116    if (tab_contents())
117      tab_contents()->delegate()->ShowContentSettingsWindow(content_type());
118  }
119};
120
121class ContentSettingTitleLinkAndInfoModel
122    : public ContentSettingTitleAndLinkModel {
123 public:
124  ContentSettingTitleLinkAndInfoModel(TabContents* tab_contents,
125                                      Profile* profile,
126                                      ContentSettingsType content_type)
127      : ContentSettingTitleAndLinkModel(tab_contents, profile, content_type) {
128    SetInfoLink();
129  }
130
131 private:
132  void SetInfoLink() {
133    static const int kInfoIDs[] = {
134      IDS_BLOCKED_COOKIES_INFO,
135      0,  // Images do not have an info link.
136      0,  // Javascript doesn't have an info link.
137      0,  // Plugins do not have an info link.
138      0,  // Popups do not have an info link.
139      0,  // Geolocation does not have an info link.
140      0,  // Notifications do not have a bubble.
141    };
142    COMPILE_ASSERT(arraysize(kInfoIDs) == CONTENT_SETTINGS_NUM_TYPES,
143                   Need_a_setting_for_every_content_settings_type);
144    if (kInfoIDs[content_type()])
145      set_info_link(l10n_util::GetStringUTF8(kInfoIDs[content_type()]));
146  }
147
148  virtual void OnInfoLinkClicked() {
149    DCHECK(content_type() == CONTENT_SETTINGS_TYPE_COOKIES);
150    if (tab_contents()) {
151      NotificationService::current()->Notify(
152          NotificationType::COLLECTED_COOKIES_SHOWN,
153          Source<TabSpecificContentSettings>(
154              tab_contents()->GetTabSpecificContentSettings()),
155          NotificationService::NoDetails());
156      tab_contents()->delegate()->ShowCollectedCookiesDialog(tab_contents());
157    }
158  }
159};
160
161
162class ContentSettingSingleRadioGroup : public ContentSettingTitleAndLinkModel {
163 public:
164  ContentSettingSingleRadioGroup(TabContents* tab_contents, Profile* profile,
165      ContentSettingsType content_type)
166      : ContentSettingTitleAndLinkModel(tab_contents, profile, content_type) {
167    SetRadioGroup();
168  }
169
170 private:
171  void SetRadioGroup() {
172    GURL url = tab_contents()->GetURL();
173    std::wstring display_host_wide;
174    net::AppendFormattedHost(url,
175        UTF8ToWide(profile()->GetPrefs()->GetString(prefs::kAcceptLanguages)),
176        &display_host_wide, NULL, NULL);
177    std::string display_host(WideToUTF8(display_host_wide));
178
179    const std::set<std::string>& resources =
180        bubble_content().resource_identifiers;
181
182    RadioGroup radio_group;
183    radio_group.url = url;
184
185    static const int kAllowIDs[] = {
186      0,  // We don't manage cookies here.
187      IDS_BLOCKED_IMAGES_UNBLOCK,
188      IDS_BLOCKED_JAVASCRIPT_UNBLOCK,
189      IDS_BLOCKED_PLUGINS_UNBLOCK_ALL,
190      IDS_BLOCKED_POPUPS_UNBLOCK,
191      0,  // We don't manage geolocation here.
192      0,  // Notifications do not have a bubble.
193    };
194    COMPILE_ASSERT(arraysize(kAllowIDs) == CONTENT_SETTINGS_NUM_TYPES,
195                   Need_a_setting_for_every_content_settings_type);
196     // Fields as for kAllowIDs, above.
197    static const int kResourceSpecificAllowIDs[] = {
198      0,
199      0,
200      0,
201      IDS_BLOCKED_PLUGINS_UNBLOCK,
202      0,
203      0,
204      0,
205    };
206    COMPILE_ASSERT(
207        arraysize(kResourceSpecificAllowIDs) == CONTENT_SETTINGS_NUM_TYPES,
208        Need_a_setting_for_every_content_settings_type);
209    std::string radio_allow_label;
210    const int* allowIDs = resources.empty() ?
211        kAllowIDs : kResourceSpecificAllowIDs;
212    radio_allow_label = l10n_util::GetStringFUTF8(
213        allowIDs[content_type()], UTF8ToUTF16(display_host));
214
215    static const int kBlockIDs[] = {
216      0,  // We don't manage cookies here.
217      IDS_BLOCKED_IMAGES_NO_ACTION,
218      IDS_BLOCKED_JAVASCRIPT_NO_ACTION,
219      IDS_BLOCKED_PLUGINS_NO_ACTION,
220      IDS_BLOCKED_POPUPS_NO_ACTION,
221      0,  // We don't manage geolocation here.
222      0,  // Notifications do not have a bubble.
223    };
224    COMPILE_ASSERT(arraysize(kBlockIDs) == CONTENT_SETTINGS_NUM_TYPES,
225                   Need_a_setting_for_every_content_settings_type);
226    std::string radio_block_label;
227    radio_block_label = l10n_util::GetStringFUTF8(
228        kBlockIDs[content_type()], UTF8ToUTF16(display_host));
229
230    radio_group.radio_items.push_back(radio_allow_label);
231    radio_group.radio_items.push_back(radio_block_label);
232    HostContentSettingsMap* map = profile()->GetHostContentSettingsMap();
233    if (resources.empty()) {
234      ContentSetting setting = map->GetContentSetting(url, content_type(),
235                                                      std::string());
236      radio_group.default_item = (setting == CONTENT_SETTING_ALLOW) ? 0 : 1;
237    } else {
238      // The default item is "block" if at least one of the resources
239      // is blocked.
240      radio_group.default_item = 0;
241      for (std::set<std::string>::const_iterator it = resources.begin();
242           it != resources.end(); ++it) {
243        ContentSetting setting = map->GetContentSetting(
244            url, content_type(), *it);
245        if (setting == CONTENT_SETTING_BLOCK) {
246          radio_group.default_item = 1;
247          break;
248        }
249      }
250    }
251    set_radio_group(radio_group);
252  }
253
254  void AddException(ContentSetting setting,
255                    const std::string& resource_identifier) {
256    profile()->GetHostContentSettingsMap()->AddExceptionForURL(
257        bubble_content().radio_group.url, content_type(), resource_identifier,
258        setting);
259  }
260
261  virtual void OnRadioClicked(int radio_index) {
262    ContentSetting setting =
263        radio_index == 0 ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
264    const std::set<std::string>& resources =
265        bubble_content().resource_identifiers;
266    if (resources.empty()) {
267      AddException(setting, std::string());
268    } else {
269      for (std::set<std::string>::const_iterator it = resources.begin();
270           it != resources.end(); ++it) {
271        AddException(setting, *it);
272      }
273    }
274  }
275};
276
277class ContentSettingPluginBubbleModel : public ContentSettingSingleRadioGroup {
278 public:
279  ContentSettingPluginBubbleModel(TabContents* tab_contents, Profile* profile,
280                                  ContentSettingsType content_type)
281      : ContentSettingSingleRadioGroup(tab_contents, profile, content_type) {
282    DCHECK_EQ(content_type, CONTENT_SETTINGS_TYPE_PLUGINS);
283    SetLoadPluginsLinkTitle();
284  }
285
286 private:
287  void SetLoadPluginsLinkTitle() {
288    if (!CommandLine::ForCurrentProcess()->HasSwitch(
289        switches::kDisableClickToPlay)) {
290      set_load_plugins_link_title(
291          l10n_util::GetStringUTF8(IDS_BLOCKED_PLUGINS_LOAD_ALL));
292     }
293   }
294
295  virtual void OnLoadPluginsLinkClicked() {
296    DCHECK(!CommandLine::ForCurrentProcess()->HasSwitch(
297           switches::kDisableClickToPlay));
298    UserMetrics::RecordAction(UserMetricsAction("ClickToPlay_LoadAll_Bubble"));
299    if (tab_contents()) {
300      tab_contents()->render_view_host()->LoadBlockedPlugins();
301    }
302    set_load_plugins_link_enabled(false);
303    TabSpecificContentSettings* settings =
304        tab_contents()->GetTabSpecificContentSettings();
305    settings->set_load_plugins_link_enabled(false);
306  }
307};
308
309class ContentSettingPopupBubbleModel : public ContentSettingSingleRadioGroup {
310 public:
311  ContentSettingPopupBubbleModel(TabContents* tab_contents, Profile* profile,
312      ContentSettingsType content_type)
313      : ContentSettingSingleRadioGroup(tab_contents, profile, content_type) {
314    SetPopups();
315  }
316
317 private:
318  void SetPopups() {
319    // check for crbug.com/53176
320    if (!tab_contents()->blocked_popup_container())
321      return;
322    BlockedPopupContainer::BlockedContents blocked_contents;
323    tab_contents()->blocked_popup_container()->GetBlockedContents(
324        &blocked_contents);
325    for (BlockedPopupContainer::BlockedContents::const_iterator
326         i(blocked_contents.begin()); i != blocked_contents.end(); ++i) {
327      std::string title(UTF16ToUTF8((*i)->GetTitle()));
328      // The popup may not have committed a load yet, in which case it won't
329      // have a URL or title.
330      if (title.empty())
331        title = l10n_util::GetStringUTF8(IDS_TAB_LOADING_TITLE);
332      PopupItem popup_item;
333      popup_item.title = title;
334      popup_item.bitmap = (*i)->GetFavIcon();
335      popup_item.tab_contents = (*i);
336      add_popup(popup_item);
337    }
338  }
339
340  virtual void OnPopupClicked(int index) {
341    if (tab_contents() && tab_contents()->blocked_popup_container()) {
342      tab_contents()->blocked_popup_container()->LaunchPopupForContents(
343          bubble_content().popup_items[index].tab_contents);
344    }
345  }
346};
347
348class ContentSettingDomainListBubbleModel
349    : public ContentSettingTitleAndLinkModel {
350 public:
351  ContentSettingDomainListBubbleModel(TabContents* tab_contents,
352                                      Profile* profile,
353                                      ContentSettingsType content_type)
354      : ContentSettingTitleAndLinkModel(tab_contents, profile, content_type) {
355    DCHECK_EQ(CONTENT_SETTINGS_TYPE_GEOLOCATION, content_type) <<
356        "SetDomains currently only supports geolocation content type";
357    SetDomainsAndClearLink();
358  }
359
360 private:
361  void MaybeAddDomainList(const std::set<std::string>& hosts, int title_id) {
362    if (!hosts.empty()) {
363      DomainList domain_list;
364      domain_list.title = l10n_util::GetStringUTF8(title_id);
365      domain_list.hosts = hosts;
366      add_domain_list(domain_list);
367    }
368  }
369  void SetDomainsAndClearLink() {
370    TabSpecificContentSettings* content_settings =
371        tab_contents()->GetTabSpecificContentSettings();
372    const GeolocationSettingsState& settings =
373        content_settings->geolocation_settings_state();
374    GeolocationSettingsState::FormattedHostsPerState formatted_hosts_per_state;
375    unsigned int tab_state_flags = 0;
376    settings.GetDetailedInfo(&formatted_hosts_per_state, &tab_state_flags);
377    // Divide the tab's current geolocation users into sets according to their
378    // permission state.
379    MaybeAddDomainList(formatted_hosts_per_state[CONTENT_SETTING_ALLOW],
380                       IDS_GEOLOCATION_BUBBLE_SECTION_ALLOWED);
381
382    MaybeAddDomainList(formatted_hosts_per_state[CONTENT_SETTING_BLOCK],
383                       IDS_GEOLOCATION_BUBBLE_SECTION_DENIED);
384
385    if (tab_state_flags & GeolocationSettingsState::TABSTATE_HAS_EXCEPTION) {
386      set_clear_link(
387          l10n_util::GetStringUTF8(IDS_GEOLOCATION_BUBBLE_CLEAR_LINK));
388    } else if (tab_state_flags &
389               GeolocationSettingsState::TABSTATE_HAS_CHANGED) {
390      // It is a slight abuse of the domain list field to use it for the reload
391      // hint, but works fine for now. TODO(joth): If we need to style it
392      // differently, consider adding an explicit field, or generalize the
393      // domain list to be a flat list of style formatted lines.
394      DomainList reload_section;
395      reload_section.title = l10n_util::GetStringUTF8(
396          IDS_GEOLOCATION_BUBBLE_REQUIRE_RELOAD_TO_CLEAR);
397      add_domain_list(reload_section);
398    }
399  }
400  virtual void OnClearLinkClicked() {
401    if (!tab_contents())
402      return;
403    // Reset this embedder's entry to default for each of the requesting
404    // origins currently on the page.
405    const GURL& embedder_url = tab_contents()->GetURL();
406    TabSpecificContentSettings* content_settings =
407        tab_contents()->GetTabSpecificContentSettings();
408    const GeolocationSettingsState::StateMap& state_map =
409        content_settings->geolocation_settings_state().state_map();
410    GeolocationContentSettingsMap* settings_map =
411        profile()->GetGeolocationContentSettingsMap();
412    for (GeolocationSettingsState::StateMap::const_iterator it =
413         state_map.begin(); it != state_map.end(); ++it) {
414      settings_map->SetContentSetting(it->first, embedder_url,
415                                      CONTENT_SETTING_DEFAULT);
416    }
417  }
418};
419
420// static
421ContentSettingBubbleModel*
422    ContentSettingBubbleModel::CreateContentSettingBubbleModel(
423        TabContents* tab_contents,
424        Profile* profile,
425        ContentSettingsType content_type) {
426  if (content_type == CONTENT_SETTINGS_TYPE_COOKIES) {
427    return new ContentSettingTitleLinkAndInfoModel(tab_contents, profile,
428                                                   content_type);
429  }
430  if (content_type == CONTENT_SETTINGS_TYPE_POPUPS) {
431    return new ContentSettingPopupBubbleModel(tab_contents, profile,
432                                              content_type);
433  }
434  if (content_type == CONTENT_SETTINGS_TYPE_GEOLOCATION) {
435    return new ContentSettingDomainListBubbleModel(tab_contents, profile,
436                                                   content_type);
437  }
438  if (content_type == CONTENT_SETTINGS_TYPE_PLUGINS) {
439    return new ContentSettingPluginBubbleModel(tab_contents, profile,
440                                               content_type);
441  }
442  return new ContentSettingSingleRadioGroup(tab_contents, profile,
443                                            content_type);
444}
445
446ContentSettingBubbleModel::ContentSettingBubbleModel(
447    TabContents* tab_contents, Profile* profile,
448    ContentSettingsType content_type)
449    : tab_contents_(tab_contents), profile_(profile),
450      content_type_(content_type) {
451  if (tab_contents) {
452    TabSpecificContentSettings* settings =
453        tab_contents->GetTabSpecificContentSettings();
454    set_load_plugins_link_enabled(settings->load_plugins_link_enabled());
455  } else {
456    set_load_plugins_link_enabled(true);
457  }
458  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
459                 Source<TabContents>(tab_contents));
460}
461
462ContentSettingBubbleModel::~ContentSettingBubbleModel() {
463}
464
465void ContentSettingBubbleModel::AddBlockedResource(
466    const std::string& resource_identifier) {
467  bubble_content_.resource_identifiers.insert(resource_identifier);
468}
469
470void ContentSettingBubbleModel::Observe(NotificationType type,
471                                        const NotificationSource& source,
472                                        const NotificationDetails& details) {
473  DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
474  DCHECK(source == Source<TabContents>(tab_contents_));
475  tab_contents_ = NULL;
476}
477