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/notifications/desktop_notification_service.h"
6
7#include "base/bind.h"
8#include "base/metrics/histogram.h"
9#include "base/prefs/scoped_user_pref_update.h"
10#include "base/strings/utf_string_conversions.h"
11#include "base/threading/thread.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/browser/notifications/desktop_notification_profile_util.h"
15#include "chrome/browser/notifications/desktop_notification_service_factory.h"
16#include "chrome/browser/notifications/notification.h"
17#include "chrome/browser/notifications/notification_object_proxy.h"
18#include "chrome/browser/notifications/notification_ui_manager.h"
19#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
20#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/common/pref_names.h"
24#include "chrome/common/url_constants.h"
25#include "components/pref_registry/pref_registry_syncable.h"
26#include "content/public/browser/browser_thread.h"
27#include "content/public/browser/desktop_notification_delegate.h"
28#include "content/public/browser/notification_service.h"
29#include "content/public/browser/render_frame_host.h"
30#include "content/public/browser/render_process_host.h"
31#include "content/public/browser/render_view_host.h"
32#include "content/public/browser/web_contents.h"
33#include "content/public/common/show_desktop_notification_params.h"
34#include "ui/base/webui/web_ui_util.h"
35#include "ui/message_center/notifier_settings.h"
36
37#if defined(ENABLE_EXTENSIONS)
38#include "chrome/browser/extensions/api/notifications/notifications_api.h"
39#include "chrome/browser/extensions/extension_service.h"
40#include "extensions/browser/event_router.h"
41#include "extensions/browser/extension_registry.h"
42#include "extensions/browser/extension_system.h"
43#include "extensions/browser/extension_util.h"
44#include "extensions/browser/info_map.h"
45#include "extensions/common/constants.h"
46#include "extensions/common/extension.h"
47#include "extensions/common/extension_set.h"
48#endif
49
50using blink::WebTextDirection;
51using content::BrowserThread;
52using content::RenderViewHost;
53using content::WebContents;
54using message_center::NotifierId;
55
56namespace {
57
58void CancelNotification(const std::string& id) {
59  g_browser_process->notification_ui_manager()->CancelById(id);
60}
61
62}  // namespace
63
64// DesktopNotificationService -------------------------------------------------
65
66// static
67void DesktopNotificationService::RegisterProfilePrefs(
68    user_prefs::PrefRegistrySyncable* registry) {
69  registry->RegisterListPref(
70      prefs::kMessageCenterDisabledExtensionIds,
71      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
72  registry->RegisterListPref(
73      prefs::kMessageCenterDisabledSystemComponentIds,
74      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
75}
76
77// static
78std::string DesktopNotificationService::AddIconNotification(
79    const GURL& origin_url,
80    const base::string16& title,
81    const base::string16& message,
82    const gfx::Image& icon,
83    const base::string16& replace_id,
84    NotificationDelegate* delegate,
85    Profile* profile) {
86  Notification notification(message_center::NOTIFICATION_TYPE_SIMPLE,
87                            origin_url,
88                            title,
89                            message,
90                            icon,
91                            blink::WebTextDirectionDefault,
92                            message_center::NotifierId(origin_url),
93                            base::string16(),
94                            replace_id,
95                            message_center::RichNotificationData(),
96                            delegate);
97  g_browser_process->notification_ui_manager()->Add(notification, profile);
98  return notification.delegate_id();
99}
100
101DesktopNotificationService::DesktopNotificationService(Profile* profile)
102    : PermissionContextBase(profile, CONTENT_SETTINGS_TYPE_NOTIFICATIONS),
103      profile_(profile),
104#if defined(ENABLE_EXTENSIONS)
105      extension_registry_observer_(this),
106#endif
107      weak_factory_(this) {
108  OnStringListPrefChanged(
109      prefs::kMessageCenterDisabledExtensionIds, &disabled_extension_ids_);
110  OnStringListPrefChanged(
111      prefs::kMessageCenterDisabledSystemComponentIds,
112      &disabled_system_component_ids_);
113  disabled_extension_id_pref_.Init(
114      prefs::kMessageCenterDisabledExtensionIds,
115      profile_->GetPrefs(),
116      base::Bind(
117          &DesktopNotificationService::OnStringListPrefChanged,
118          base::Unretained(this),
119          base::Unretained(prefs::kMessageCenterDisabledExtensionIds),
120          base::Unretained(&disabled_extension_ids_)));
121  disabled_system_component_id_pref_.Init(
122      prefs::kMessageCenterDisabledSystemComponentIds,
123      profile_->GetPrefs(),
124      base::Bind(
125          &DesktopNotificationService::OnStringListPrefChanged,
126          base::Unretained(this),
127          base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds),
128          base::Unretained(&disabled_system_component_ids_)));
129#if defined(ENABLE_EXTENSIONS)
130  extension_registry_observer_.Add(
131      extensions::ExtensionRegistry::Get(profile_));
132#endif
133}
134
135DesktopNotificationService::~DesktopNotificationService() {
136}
137
138void DesktopNotificationService::RequestNotificationPermission(
139    content::WebContents* web_contents,
140    const PermissionRequestID& request_id,
141    const GURL& requesting_frame,
142    bool user_gesture,
143    const NotificationPermissionCallback& callback) {
144  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
145  RequestPermission(
146      web_contents,
147      request_id,
148      requesting_frame,
149      user_gesture,
150      base::Bind(&DesktopNotificationService::OnNotificationPermissionRequested,
151                 weak_factory_.GetWeakPtr(),
152                 callback));
153}
154
155void DesktopNotificationService::ShowDesktopNotification(
156    const content::ShowDesktopNotificationHostMsgParams& params,
157    content::RenderFrameHost* render_frame_host,
158    scoped_ptr<content::DesktopNotificationDelegate> delegate,
159    base::Closure* cancel_callback) {
160  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
161  const GURL& origin = params.origin;
162  NotificationObjectProxy* proxy =
163      new NotificationObjectProxy(render_frame_host, delegate.Pass());
164
165  base::string16 display_source = DisplayNameForOriginInProcessId(
166      origin, render_frame_host->GetProcess()->GetID());
167  Notification notification(origin, params.icon_url, params.title,
168      params.body, params.direction, display_source, params.replace_id,
169      proxy);
170
171  // The webkit notification doesn't timeout.
172  notification.set_never_timeout(true);
173
174  g_browser_process->notification_ui_manager()->Add(notification, profile_);
175  if (cancel_callback)
176    *cancel_callback = base::Bind(&CancelNotification, proxy->id());
177
178  DesktopNotificationProfileUtil::UsePermission(profile_, origin);
179}
180
181base::string16 DesktopNotificationService::DisplayNameForOriginInProcessId(
182    const GURL& origin, int process_id) {
183#if defined(ENABLE_EXTENSIONS)
184  // If the source is an extension, lookup the display name.
185  if (origin.SchemeIs(extensions::kExtensionScheme)) {
186    extensions::InfoMap* extension_info_map =
187        extensions::ExtensionSystem::Get(profile_)->info_map();
188    if (extension_info_map) {
189      extensions::ExtensionSet extensions;
190      extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
191          origin,
192          process_id,
193          extensions::APIPermission::kNotifications,
194          &extensions);
195      for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
196           iter != extensions.end(); ++iter) {
197        NotifierId notifier_id(NotifierId::APPLICATION, (*iter)->id());
198        if (IsNotifierEnabled(notifier_id))
199          return base::UTF8ToUTF16((*iter)->name());
200      }
201    }
202  }
203#endif
204
205  return base::UTF8ToUTF16(origin.host());
206}
207
208bool DesktopNotificationService::IsNotifierEnabled(
209    const NotifierId& notifier_id) {
210  switch (notifier_id.type) {
211    case NotifierId::APPLICATION:
212      return disabled_extension_ids_.find(notifier_id.id) ==
213          disabled_extension_ids_.end();
214    case NotifierId::WEB_PAGE:
215      return DesktopNotificationProfileUtil::GetContentSetting(
216          profile_, notifier_id.url) == CONTENT_SETTING_ALLOW;
217    case NotifierId::SYSTEM_COMPONENT:
218#if defined(OS_CHROMEOS)
219      return disabled_system_component_ids_.find(notifier_id.id) ==
220          disabled_system_component_ids_.end();
221#else
222      // We do not disable system component notifications.
223      return true;
224#endif
225  }
226
227  NOTREACHED();
228  return false;
229}
230
231void DesktopNotificationService::SetNotifierEnabled(
232    const NotifierId& notifier_id,
233    bool enabled) {
234  DCHECK_NE(NotifierId::WEB_PAGE, notifier_id.type);
235
236  bool add_new_item = false;
237  const char* pref_name = NULL;
238  scoped_ptr<base::StringValue> id;
239  switch (notifier_id.type) {
240    case NotifierId::APPLICATION:
241      pref_name = prefs::kMessageCenterDisabledExtensionIds;
242      add_new_item = !enabled;
243      id.reset(new base::StringValue(notifier_id.id));
244      FirePermissionLevelChangedEvent(notifier_id, enabled);
245      break;
246    case NotifierId::SYSTEM_COMPONENT:
247#if defined(OS_CHROMEOS)
248      pref_name = prefs::kMessageCenterDisabledSystemComponentIds;
249      add_new_item = !enabled;
250      id.reset(new base::StringValue(notifier_id.id));
251#else
252      return;
253#endif
254      break;
255    default:
256      NOTREACHED();
257  }
258  DCHECK(pref_name != NULL);
259
260  ListPrefUpdate update(profile_->GetPrefs(), pref_name);
261  base::ListValue* const list = update.Get();
262  if (add_new_item) {
263    // AppendIfNotPresent will delete |adding_value| when the same value
264    // already exists.
265    list->AppendIfNotPresent(id.release());
266  } else {
267    list->Remove(*id, NULL);
268  }
269}
270
271void DesktopNotificationService::OnStringListPrefChanged(
272    const char* pref_name, std::set<std::string>* ids_field) {
273  ids_field->clear();
274  // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
275  const PrefService* pref_service = profile_->GetPrefs();
276  CHECK(pref_service);
277  const base::ListValue* pref_list = pref_service->GetList(pref_name);
278  for (size_t i = 0; i < pref_list->GetSize(); ++i) {
279    std::string element;
280    if (pref_list->GetString(i, &element) && !element.empty())
281      ids_field->insert(element);
282    else
283      LOG(WARNING) << i << "-th element is not a string for " << pref_name;
284  }
285}
286
287#if defined(ENABLE_EXTENSIONS)
288void DesktopNotificationService::OnExtensionUninstalled(
289    content::BrowserContext* browser_context,
290    const extensions::Extension* extension,
291    extensions::UninstallReason reason) {
292  NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
293  if (IsNotifierEnabled(notifier_id))
294    return;
295
296  // The settings for ephemeral apps will be persisted across cache evictions.
297  if (extensions::util::IsEphemeralApp(extension->id(), profile_))
298    return;
299
300  SetNotifierEnabled(notifier_id, true);
301}
302#endif
303
304// Unlike other permission types, granting a notification for a given origin
305// will not take into account the |embedder_origin|, it will only be based
306// on the requesting iframe origin.
307// TODO(mukai) Consider why notifications behave differently than
308// other permissions. crbug.com/416894
309void DesktopNotificationService::UpdateContentSetting(
310    const GURL& requesting_origin,
311    const GURL& embedder_origin,
312    bool allowed) {
313  if (allowed) {
314    DesktopNotificationProfileUtil::GrantPermission(
315        profile_, requesting_origin);
316  } else {
317    DesktopNotificationProfileUtil::DenyPermission(profile_, requesting_origin);
318  }
319}
320
321void DesktopNotificationService::OnNotificationPermissionRequested(
322    const NotificationPermissionCallback& callback, bool allowed) {
323  blink::WebNotificationPermission permission = allowed ?
324      blink::WebNotificationPermissionAllowed :
325      blink::WebNotificationPermissionDenied;
326
327  callback.Run(permission);
328}
329
330void DesktopNotificationService::FirePermissionLevelChangedEvent(
331    const NotifierId& notifier_id, bool enabled) {
332#if defined(ENABLE_EXTENSIONS)
333  DCHECK_EQ(NotifierId::APPLICATION, notifier_id.type);
334  extensions::api::notifications::PermissionLevel permission =
335      enabled ? extensions::api::notifications::PERMISSION_LEVEL_GRANTED
336              : extensions::api::notifications::PERMISSION_LEVEL_DENIED;
337  scoped_ptr<base::ListValue> args(new base::ListValue());
338  args->Append(new base::StringValue(
339      extensions::api::notifications::ToString(permission)));
340  scoped_ptr<extensions::Event> event(new extensions::Event(
341      extensions::api::notifications::OnPermissionLevelChanged::kEventName,
342      args.Pass()));
343  extensions::EventRouter::Get(profile_)
344      ->DispatchEventToExtension(notifier_id.id, event.Pass());
345
346  // Tell the IO thread that this extension's permission for notifications
347  // has changed.
348  extensions::InfoMap* extension_info_map =
349      extensions::ExtensionSystem::Get(profile_)->info_map();
350  BrowserThread::PostTask(
351      BrowserThread::IO, FROM_HERE,
352      base::Bind(&extensions::InfoMap::SetNotificationsDisabled,
353                 extension_info_map, notifier_id.id, !enabled));
354#endif
355}
356