Upstream version 10.38.220.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / notifications / desktop_notification_service.cc
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/content_settings/content_settings_details.h"
15 #include "chrome/browser/content_settings/content_settings_provider.h"
16 #include "chrome/browser/notifications/desktop_notification_profile_util.h"
17 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
18 #include "chrome/browser/notifications/notification.h"
19 #include "chrome/browser/notifications/notification_object_proxy.h"
20 #include "chrome/browser/notifications/notification_ui_manager.h"
21 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
22 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/url_constants.h"
27 #include "components/pref_registry/pref_registry_syncable.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/desktop_notification_delegate.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/render_frame_host.h"
32 #include "content/public/browser/render_process_host.h"
33 #include "content/public/browser/render_view_host.h"
34 #include "content/public/browser/web_contents.h"
35 #include "content/public/common/show_desktop_notification_params.h"
36 #include "grit/browser_resources.h"
37 #include "grit/chromium_strings.h"
38 #include "grit/generated_resources.h"
39 #include "grit/theme_resources.h"
40 #include "net/base/escape.h"
41 #include "ui/base/resource/resource_bundle.h"
42 #include "ui/base/webui/web_ui_util.h"
43 #include "ui/message_center/notifier_settings.h"
44
45 #if defined(ENABLE_EXTENSIONS)
46 #include "chrome/browser/extensions/api/notifications/notifications_api.h"
47 #include "chrome/browser/extensions/extension_service.h"
48 #include "extensions/browser/event_router.h"
49 #include "extensions/browser/extension_registry.h"
50 #include "extensions/browser/extension_system.h"
51 #include "extensions/browser/extension_util.h"
52 #include "extensions/browser/info_map.h"
53 #include "extensions/common/constants.h"
54 #include "extensions/common/extension.h"
55 #include "extensions/common/extension_set.h"
56 #endif
57
58 using blink::WebTextDirection;
59 using content::BrowserThread;
60 using content::RenderViewHost;
61 using content::WebContents;
62 using message_center::NotifierId;
63
64 namespace {
65
66 const char kChromeNowExtensionID[] = "pafkbggdmjlpgkdkcbjmhmfcdpncadgh";
67
68 void CancelNotification(const std::string& id) {
69   g_browser_process->notification_ui_manager()->CancelById(id);
70 }
71
72 }  // namespace
73
74
75 // DesktopNotificationService -------------------------------------------------
76
77 // static
78 void DesktopNotificationService::RegisterProfilePrefs(
79     user_prefs::PrefRegistrySyncable* registry) {
80   registry->RegisterListPref(
81       prefs::kMessageCenterDisabledExtensionIds,
82       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
83   registry->RegisterListPref(
84       prefs::kMessageCenterDisabledSystemComponentIds,
85       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
86   ExtensionWelcomeNotification::RegisterProfilePrefs(registry);
87 }
88
89 // static
90 base::string16 DesktopNotificationService::CreateDataUrl(
91     const GURL& icon_url,
92     const base::string16& title,
93     const base::string16& body,
94     WebTextDirection dir) {
95   int resource;
96   std::vector<std::string> subst;
97   if (icon_url.is_valid()) {
98     resource = IDR_NOTIFICATION_ICON_HTML;
99     subst.push_back(icon_url.spec());
100     subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title)));
101     subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body)));
102     // icon float position
103     subst.push_back(dir == blink::WebTextDirectionRightToLeft ?
104                     "right" : "left");
105   } else if (title.empty() || body.empty()) {
106     resource = IDR_NOTIFICATION_1LINE_HTML;
107     base::string16 line = title.empty() ? body : title;
108     // Strings are div names in the template file.
109     base::string16 line_name =
110         title.empty() ? base::ASCIIToUTF16("description")
111                       : base::ASCIIToUTF16("title");
112     subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line_name)));
113     subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line)));
114   } else {
115     resource = IDR_NOTIFICATION_2LINE_HTML;
116     subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title)));
117     subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body)));
118   }
119   // body text direction
120   subst.push_back(dir == blink::WebTextDirectionRightToLeft ?
121                   "rtl" : "ltr");
122
123   return CreateDataUrl(resource, subst);
124 }
125
126 // static
127 base::string16 DesktopNotificationService::CreateDataUrl(
128     int resource, const std::vector<std::string>& subst) {
129   const base::StringPiece template_html(
130       ResourceBundle::GetSharedInstance().GetRawDataResource(
131           resource));
132
133   if (template_html.empty()) {
134     NOTREACHED() << "unable to load template. ID: " << resource;
135     return base::string16();
136   }
137
138   std::string data = ReplaceStringPlaceholders(template_html, subst, NULL);
139   return base::UTF8ToUTF16("data:text/html;charset=utf-8," +
140                                net::EscapeQueryParamValue(data, false));
141 }
142
143 // static
144 std::string DesktopNotificationService::AddIconNotification(
145     const GURL& origin_url,
146     const base::string16& title,
147     const base::string16& message,
148     const gfx::Image& icon,
149     const base::string16& replace_id,
150     NotificationDelegate* delegate,
151     Profile* profile) {
152   Notification notification(origin_url, icon, title, message,
153                             blink::WebTextDirectionDefault,
154                             base::string16(), replace_id, delegate);
155   g_browser_process->notification_ui_manager()->Add(notification, profile);
156   return notification.delegate_id();
157 }
158
159 DesktopNotificationService::DesktopNotificationService(
160     Profile* profile,
161     NotificationUIManager* ui_manager)
162     : PermissionContextBase(profile, CONTENT_SETTINGS_TYPE_NOTIFICATIONS),
163       profile_(profile),
164       ui_manager_(ui_manager),
165       extension_registry_observer_(this),
166       weak_factory_(this) {
167   OnStringListPrefChanged(
168       prefs::kMessageCenterDisabledExtensionIds, &disabled_extension_ids_);
169   OnStringListPrefChanged(
170       prefs::kMessageCenterDisabledSystemComponentIds,
171       &disabled_system_component_ids_);
172   disabled_extension_id_pref_.Init(
173       prefs::kMessageCenterDisabledExtensionIds,
174       profile_->GetPrefs(),
175       base::Bind(
176           &DesktopNotificationService::OnStringListPrefChanged,
177           base::Unretained(this),
178           base::Unretained(prefs::kMessageCenterDisabledExtensionIds),
179           base::Unretained(&disabled_extension_ids_)));
180   disabled_system_component_id_pref_.Init(
181       prefs::kMessageCenterDisabledSystemComponentIds,
182       profile_->GetPrefs(),
183       base::Bind(
184           &DesktopNotificationService::OnStringListPrefChanged,
185           base::Unretained(this),
186           base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds),
187           base::Unretained(&disabled_system_component_ids_)));
188   extension_registry_observer_.Add(
189       extensions::ExtensionRegistry::Get(profile_));
190 }
191
192 DesktopNotificationService::~DesktopNotificationService() {
193 }
194
195 void DesktopNotificationService::RequestNotificationPermission(
196     content::WebContents* web_contents,
197     const PermissionRequestID& request_id,
198     const GURL& requesting_frame,
199     bool user_gesture,
200     const NotificationPermissionCallback& callback) {
201   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
202   RequestPermission(
203       web_contents,
204       request_id,
205       requesting_frame,
206       user_gesture,
207       base::Bind(&DesktopNotificationService::OnNotificationPermissionRequested,
208                  weak_factory_.GetWeakPtr(),
209                  callback));
210 }
211
212 void DesktopNotificationService::ShowDesktopNotification(
213     const content::ShowDesktopNotificationHostMsgParams& params,
214     content::RenderFrameHost* render_frame_host,
215     scoped_ptr<content::DesktopNotificationDelegate> delegate,
216     base::Closure* cancel_callback) {
217   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
218   const GURL& origin = params.origin;
219   NotificationObjectProxy* proxy =
220       new NotificationObjectProxy(render_frame_host, delegate.Pass());
221
222   base::string16 display_source = DisplayNameForOriginInProcessId(
223       origin, render_frame_host->GetProcess()->GetID());
224   Notification notification(origin, params.icon_url, params.title,
225       params.body, params.direction, display_source, params.replace_id,
226       proxy);
227
228   // The webkit notification doesn't timeout.
229   notification.set_never_timeout(true);
230
231   GetUIManager()->Add(notification, profile_);
232   if (cancel_callback)
233     *cancel_callback = base::Bind(&CancelNotification, proxy->id());
234
235   DesktopNotificationProfileUtil::UsePermission(profile_, origin);
236 }
237
238 base::string16 DesktopNotificationService::DisplayNameForOriginInProcessId(
239     const GURL& origin, int process_id) {
240 #if defined(ENABLE_EXTENSIONS)
241   // If the source is an extension, lookup the display name.
242   if (origin.SchemeIs(extensions::kExtensionScheme)) {
243     extensions::InfoMap* extension_info_map =
244         extensions::ExtensionSystem::Get(profile_)->info_map();
245     if (extension_info_map) {
246       extensions::ExtensionSet extensions;
247       extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
248           origin,
249           process_id,
250           extensions::APIPermission::kNotifications,
251           &extensions);
252       for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
253            iter != extensions.end(); ++iter) {
254         NotifierId notifier_id(NotifierId::APPLICATION, (*iter)->id());
255         if (IsNotifierEnabled(notifier_id))
256           return base::UTF8ToUTF16((*iter)->name());
257       }
258     }
259   }
260 #endif
261
262   return base::UTF8ToUTF16(origin.host());
263 }
264
265 NotificationUIManager* DesktopNotificationService::GetUIManager() {
266   // We defer setting ui_manager_ to the global singleton until we need it
267   // in order to avoid UI dependent construction during startup.
268   if (!ui_manager_)
269     ui_manager_ = g_browser_process->notification_ui_manager();
270   return ui_manager_;
271 }
272
273 bool DesktopNotificationService::IsNotifierEnabled(
274     const NotifierId& notifier_id) {
275   switch (notifier_id.type) {
276     case NotifierId::APPLICATION:
277       return disabled_extension_ids_.find(notifier_id.id) ==
278           disabled_extension_ids_.end();
279     case NotifierId::WEB_PAGE:
280       return DesktopNotificationProfileUtil::GetContentSetting(
281           profile_, notifier_id.url) == CONTENT_SETTING_ALLOW;
282     case NotifierId::SYSTEM_COMPONENT:
283 #if defined(OS_CHROMEOS)
284       return disabled_system_component_ids_.find(notifier_id.id) ==
285           disabled_system_component_ids_.end();
286 #else
287       // We do not disable system component notifications.
288       return true;
289 #endif
290   }
291
292   NOTREACHED();
293   return false;
294 }
295
296 void DesktopNotificationService::SetNotifierEnabled(
297     const NotifierId& notifier_id,
298     bool enabled) {
299   DCHECK_NE(NotifierId::WEB_PAGE, notifier_id.type);
300
301   bool add_new_item = false;
302   const char* pref_name = NULL;
303   scoped_ptr<base::StringValue> id;
304   switch (notifier_id.type) {
305     case NotifierId::APPLICATION:
306       pref_name = prefs::kMessageCenterDisabledExtensionIds;
307       add_new_item = !enabled;
308       id.reset(new base::StringValue(notifier_id.id));
309       FirePermissionLevelChangedEvent(notifier_id, enabled);
310       break;
311     case NotifierId::SYSTEM_COMPONENT:
312 #if defined(OS_CHROMEOS)
313       pref_name = prefs::kMessageCenterDisabledSystemComponentIds;
314       add_new_item = !enabled;
315       id.reset(new base::StringValue(notifier_id.id));
316 #else
317       return;
318 #endif
319       break;
320     default:
321       NOTREACHED();
322   }
323   DCHECK(pref_name != NULL);
324
325   ListPrefUpdate update(profile_->GetPrefs(), pref_name);
326   base::ListValue* const list = update.Get();
327   if (add_new_item) {
328     // AppendIfNotPresent will delete |adding_value| when the same value
329     // already exists.
330     list->AppendIfNotPresent(id.release());
331   } else {
332     list->Remove(*id, NULL);
333   }
334 }
335
336 void DesktopNotificationService::ShowWelcomeNotificationIfNecessary(
337     const Notification& notification) {
338   if (!chrome_now_welcome_notification_) {
339     chrome_now_welcome_notification_ =
340         ExtensionWelcomeNotification::Create(kChromeNowExtensionID, profile_);
341   }
342
343   if (chrome_now_welcome_notification_) {
344     chrome_now_welcome_notification_->ShowWelcomeNotificationIfNecessary(
345         notification);
346   }
347 }
348
349 void DesktopNotificationService::OnStringListPrefChanged(
350     const char* pref_name, std::set<std::string>* ids_field) {
351   ids_field->clear();
352   // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
353   const PrefService* pref_service = profile_->GetPrefs();
354   CHECK(pref_service);
355   const base::ListValue* pref_list = pref_service->GetList(pref_name);
356   for (size_t i = 0; i < pref_list->GetSize(); ++i) {
357     std::string element;
358     if (pref_list->GetString(i, &element) && !element.empty())
359       ids_field->insert(element);
360     else
361       LOG(WARNING) << i << "-th element is not a string for " << pref_name;
362   }
363 }
364
365 void DesktopNotificationService::OnExtensionUninstalled(
366     content::BrowserContext* browser_context,
367     const extensions::Extension* extension,
368     extensions::UninstallReason reason) {
369 #if defined(ENABLE_EXTENSIONS)
370   NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
371   if (IsNotifierEnabled(notifier_id))
372     return;
373
374   // The settings for ephemeral apps will be persisted across cache evictions.
375   if (extensions::util::IsEphemeralApp(extension->id(), profile_))
376     return;
377
378   SetNotifierEnabled(notifier_id, true);
379 #endif
380 }
381
382 // Unlike other permission types, granting a notification for a given origin
383 // will not take into account the |embedder_origin|, it will only be based
384 // on the requesting iframe origin.
385 // TODO(mukai) Consider why notifications behave differently than
386 // other permissions. crbug.com/416894
387 void DesktopNotificationService::UpdateContentSetting(
388     const GURL& requesting_origin,
389     const GURL& embedder_origin,
390     bool allowed) {
391   if (allowed) {
392     DesktopNotificationProfileUtil::GrantPermission(
393         profile_, requesting_origin);
394   } else {
395     DesktopNotificationProfileUtil::DenyPermission(profile_, requesting_origin);
396   }
397 }
398
399 void DesktopNotificationService::OnNotificationPermissionRequested(
400     const NotificationPermissionCallback& callback, bool allowed) {
401   blink::WebNotificationPermission permission = allowed ?
402       blink::WebNotificationPermissionAllowed :
403       blink::WebNotificationPermissionDenied;
404
405   callback.Run(permission);
406 }
407
408 void DesktopNotificationService::FirePermissionLevelChangedEvent(
409     const NotifierId& notifier_id, bool enabled) {
410 #if defined(ENABLE_EXTENSIONS)
411   DCHECK_EQ(NotifierId::APPLICATION, notifier_id.type);
412   extensions::api::notifications::PermissionLevel permission =
413       enabled ? extensions::api::notifications::PERMISSION_LEVEL_GRANTED
414               : extensions::api::notifications::PERMISSION_LEVEL_DENIED;
415   scoped_ptr<base::ListValue> args(new base::ListValue());
416   args->Append(new base::StringValue(
417       extensions::api::notifications::ToString(permission)));
418   scoped_ptr<extensions::Event> event(new extensions::Event(
419       extensions::api::notifications::OnPermissionLevelChanged::kEventName,
420       args.Pass()));
421   extensions::EventRouter::Get(profile_)
422       ->DispatchEventToExtension(notifier_id.id, event.Pass());
423
424   // Tell the IO thread that this extension's permission for notifications
425   // has changed.
426   extensions::InfoMap* extension_info_map =
427       extensions::ExtensionSystem::Get(profile_)->info_map();
428   BrowserThread::PostTask(
429       BrowserThread::IO, FROM_HERE,
430       base::Bind(&extensions::InfoMap::SetNotificationsDisabled,
431                  extension_info_map, notifier_id.id, !enabled));
432 #endif
433 }