summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/push_messaging/push_messaging_notification_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/push_messaging/push_messaging_notification_manager.cc')
-rw-r--r--chromium/chrome/browser/push_messaging/push_messaging_notification_manager.cc353
1 files changed, 353 insertions, 0 deletions
diff --git a/chromium/chrome/browser/push_messaging/push_messaging_notification_manager.cc b/chromium/chrome/browser/push_messaging/push_messaging_notification_manager.cc
new file mode 100644
index 00000000000..e2a8f2adc42
--- /dev/null
+++ b/chromium/chrome/browser/push_messaging/push_messaging_notification_manager.cc
@@ -0,0 +1,353 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/push_messaging/push_messaging_notification_manager.h"
+
+#include <stddef.h>
+
+#include <bitset>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/feature_list.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "chrome/browser/notifications/platform_notification_service_factory.h"
+#include "chrome/browser/notifications/platform_notification_service_impl.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/push_messaging/push_messaging_constants.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/site_engagement/content/site_engagement_service.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/platform_notification_context.h"
+#include "content/public/browser/push_messaging_service.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/page_visibility_state.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
+#include "third_party/blink/public/mojom/notifications/notification.mojom-shared.h"
+#include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+#if defined(OS_ANDROID)
+#include "chrome/browser/ui/android/tab_model/tab_model.h"
+#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
+#else
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ash/android_sms/android_sms_service_factory.h"
+#include "chrome/browser/ash/android_sms/android_sms_urls.h"
+#include "chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h"
+#endif
+
+using content::BrowserThread;
+using content::NotificationDatabaseData;
+using content::PlatformNotificationContext;
+using content::PushMessagingService;
+using content::ServiceWorkerContext;
+using content::WebContents;
+
+namespace {
+void RecordUserVisibleStatus(blink::mojom::PushUserVisibleStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PushMessaging.UserVisibleStatus", status);
+}
+
+content::StoragePartition* GetStoragePartition(Profile* profile,
+ const GURL& origin) {
+ return profile->GetStoragePartitionForUrl(origin);
+}
+
+NotificationDatabaseData CreateDatabaseData(
+ const GURL& origin,
+ int64_t service_worker_registration_id) {
+ blink::PlatformNotificationData notification_data;
+ notification_data.title = url_formatter::FormatUrlForSecurityDisplay(
+ origin, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
+ notification_data.direction =
+ blink::mojom::NotificationDirection::LEFT_TO_RIGHT;
+ notification_data.body =
+ l10n_util::GetStringUTF16(IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_BODY);
+ notification_data.tag = kPushMessagingForcedNotificationTag;
+ notification_data.icon = GURL();
+ notification_data.timestamp = base::Time::Now();
+ notification_data.silent = true;
+
+ NotificationDatabaseData database_data;
+ database_data.origin = origin;
+ database_data.service_worker_registration_id = service_worker_registration_id;
+ database_data.notification_data = notification_data;
+
+ // Make sure we don't expose this notification to the site.
+ database_data.is_shown_by_browser = true;
+
+ return database_data;
+}
+
+} // namespace
+
+PushMessagingNotificationManager::PushMessagingNotificationManager(
+ Profile* profile)
+ : profile_(profile), budget_database_(profile) {}
+
+PushMessagingNotificationManager::~PushMessagingNotificationManager() = default;
+
+void PushMessagingNotificationManager::EnforceUserVisibleOnlyRequirements(
+ const GURL& origin,
+ int64_t service_worker_registration_id,
+ EnforceRequirementsCallback message_handled_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ if (ShouldSkipUserVisibleOnlyRequirements(origin)) {
+ std::move(message_handled_callback)
+ .Run(/* did_show_generic_notification= */ false);
+ return;
+ }
+#endif
+
+ // TODO(johnme): Relax this heuristic slightly.
+ scoped_refptr<PlatformNotificationContext> notification_context =
+ GetStoragePartition(profile_, origin)->GetPlatformNotificationContext();
+
+ notification_context->CountVisibleNotificationsForServiceWorkerRegistration(
+ origin, service_worker_registration_id,
+ base::BindOnce(
+ &PushMessagingNotificationManager::DidCountVisibleNotifications,
+ weak_factory_.GetWeakPtr(), origin, service_worker_registration_id,
+ std::move(message_handled_callback)));
+}
+
+void PushMessagingNotificationManager::DidCountVisibleNotifications(
+ const GURL& origin,
+ int64_t service_worker_registration_id,
+ EnforceRequirementsCallback message_handled_callback,
+ bool success,
+ int notification_count) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // TODO(johnme): Hiding an existing notification should also count as a useful
+ // user-visible action done in response to a push message - but make sure that
+ // sending two messages in rapid succession which show then hide a
+ // notification doesn't count.
+ // TODO(crbug.com/891339): Scheduling a notification should count as a
+ // user-visible action, if it is not immediately cancelled or the |origin|
+ // schedules too many notifications too far in the future.
+ bool notification_shown = notification_count > 0;
+ bool notification_needed = true;
+
+ base::UmaHistogramCounts100("PushMessaging.VisibleNotificationCount",
+ notification_count);
+
+ // Sites with a currently visible tab don't need to show notifications.
+#if defined(OS_ANDROID)
+ for (const TabModel* model : TabModelList::models()) {
+ Profile* profile = model->GetProfile();
+ WebContents* active_web_contents = model->GetActiveWebContents();
+#else
+ for (auto* browser : *BrowserList::GetInstance()) {
+ Profile* profile = browser->profile();
+ WebContents* active_web_contents =
+ browser->tab_strip_model()->GetActiveWebContents();
+#endif
+ if (IsTabVisible(profile, active_web_contents, origin)) {
+ notification_needed = false;
+ break;
+ }
+ }
+
+ // If more than one notification is showing for this Service Worker, close
+ // the default notification if it happens to be part of this group.
+ if (notification_count >= 2) {
+ scoped_refptr<PlatformNotificationContext> notification_context =
+ GetStoragePartition(profile_, origin)->GetPlatformNotificationContext();
+ notification_context->DeleteAllNotificationDataWithTag(
+ kPushMessagingForcedNotificationTag, /*is_shown_by_browser=*/true,
+ origin, base::DoNothing());
+ }
+
+ if (notification_needed && !notification_shown) {
+ // If the worker needed to show a notification and didn't, see if a silent
+ // push was allowed.
+ budget_database_.SpendBudget(
+ url::Origin::Create(origin),
+ base::BindOnce(&PushMessagingNotificationManager::ProcessSilentPush,
+ weak_factory_.GetWeakPtr(), origin,
+ service_worker_registration_id,
+ std::move(message_handled_callback)));
+ return;
+ }
+
+ if (notification_needed && notification_shown) {
+ RecordUserVisibleStatus(
+ blink::mojom::PushUserVisibleStatus::REQUIRED_AND_SHOWN);
+ } else if (!notification_needed && !notification_shown) {
+ RecordUserVisibleStatus(
+ blink::mojom::PushUserVisibleStatus::NOT_REQUIRED_AND_NOT_SHOWN);
+ } else {
+ RecordUserVisibleStatus(
+ blink::mojom::PushUserVisibleStatus::NOT_REQUIRED_BUT_SHOWN);
+ }
+
+ std::move(message_handled_callback)
+ .Run(/* did_show_generic_notification= */ false);
+}
+
+bool PushMessagingNotificationManager::IsTabVisible(
+ Profile* profile,
+ WebContents* active_web_contents,
+ const GURL& origin) {
+ if (!active_web_contents || !active_web_contents->GetMainFrame())
+ return false;
+
+ // Don't leak information from other profiles.
+ if (profile != profile_)
+ return false;
+
+ // Ignore minimized windows.
+ switch (active_web_contents->GetMainFrame()->GetVisibilityState()) {
+ case content::PageVisibilityState::kHidden:
+ case content::PageVisibilityState::kHiddenButPainting:
+ return false;
+ case content::PageVisibilityState::kVisible:
+ break;
+ }
+
+ // Use the visible URL since that's the one the user is aware of (and it
+ // doesn't matter whether the page loaded successfully).
+ GURL visible_url = active_web_contents->GetVisibleURL();
+
+ // view-source: pages are considered to be controlled Service Worker clients
+ // and thus should be considered when checking the visible URL. However, the
+ // prefix has to be removed before the origins can be compared.
+ if (visible_url.SchemeIs(content::kViewSourceScheme))
+ visible_url = GURL(visible_url.GetContent());
+
+ return visible_url.DeprecatedGetOriginAsURL() == origin;
+}
+
+void PushMessagingNotificationManager::ProcessSilentPush(
+ const GURL& origin,
+ int64_t service_worker_registration_id,
+ EnforceRequirementsCallback message_handled_callback,
+ bool silent_push_allowed) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ // If the origin was allowed to issue a silent push, just return.
+ if (silent_push_allowed) {
+ RecordUserVisibleStatus(
+ blink::mojom::PushUserVisibleStatus::REQUIRED_BUT_NOT_SHOWN_USED_GRACE);
+ std::move(message_handled_callback)
+ .Run(/* did_show_generic_notification= */ false);
+ return;
+ }
+
+ RecordUserVisibleStatus(blink::mojom::PushUserVisibleStatus::
+ REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED);
+
+ // The site failed to show a notification when one was needed, and they don't
+ // have enough budget to cover the cost of suppressing, so we will show a
+ // generic notification.
+ NotificationDatabaseData database_data =
+ CreateDatabaseData(origin, service_worker_registration_id);
+ scoped_refptr<PlatformNotificationContext> notification_context =
+ GetStoragePartition(profile_, origin)->GetPlatformNotificationContext();
+ int64_t next_persistent_notification_id =
+ PlatformNotificationServiceFactory::GetForProfile(profile_)
+ ->ReadNextPersistentNotificationId();
+
+ notification_context->WriteNotificationData(
+ next_persistent_notification_id, service_worker_registration_id, origin,
+ database_data,
+ base::BindOnce(
+ &PushMessagingNotificationManager::DidWriteNotificationData,
+ weak_factory_.GetWeakPtr(), std::move(message_handled_callback)));
+}
+
+void PushMessagingNotificationManager::DidWriteNotificationData(
+ EnforceRequirementsCallback message_handled_callback,
+ bool success,
+ const std::string& notification_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!success)
+ DLOG(ERROR) << "Writing forced notification to database should not fail";
+
+ std::move(message_handled_callback)
+ .Run(/* did_show_generic_notification= */ true);
+}
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+bool PushMessagingNotificationManager::ShouldSkipUserVisibleOnlyRequirements(
+ const GURL& origin) {
+ // This is a short-term exception to user visible only enforcement added
+ // to support for "Messages for Web" integration on ChromeOS.
+
+ chromeos::multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client;
+ if (test_multidevice_setup_client_) {
+ multidevice_setup_client = test_multidevice_setup_client_;
+ } else {
+ multidevice_setup_client =
+ ash::multidevice_setup::MultiDeviceSetupClientFactory::GetForProfile(
+ profile_);
+ }
+
+ if (!multidevice_setup_client)
+ return false;
+
+ // Check if messages feature is enabled
+ if (multidevice_setup_client->GetFeatureState(
+ chromeos::multidevice_setup::mojom::Feature::kMessages) !=
+ chromeos::multidevice_setup::mojom::FeatureState::kEnabledByUser) {
+ return false;
+ }
+
+ ash::android_sms::AndroidSmsAppManager* android_sms_app_manager;
+ if (test_android_sms_app_manager_) {
+ android_sms_app_manager = test_android_sms_app_manager_;
+ } else {
+ auto* android_sms_service =
+ ash::android_sms::AndroidSmsServiceFactory::GetForBrowserContext(
+ profile_);
+ if (!android_sms_service)
+ return false;
+ android_sms_app_manager = android_sms_service->android_sms_app_manager();
+ }
+
+ // Check if origin matches current messages url
+ absl::optional<GURL> app_url = android_sms_app_manager->GetCurrentAppUrl();
+ if (!app_url)
+ app_url = ash::android_sms::GetAndroidMessagesURL();
+
+ if (!origin.EqualsIgnoringRef(app_url->DeprecatedGetOriginAsURL()))
+ return false;
+
+ return true;
+}
+
+void PushMessagingNotificationManager::SetTestMultiDeviceSetupClient(
+ chromeos::multidevice_setup::MultiDeviceSetupClient*
+ multidevice_setup_client) {
+ test_multidevice_setup_client_ = multidevice_setup_client;
+}
+
+void PushMessagingNotificationManager::SetTestAndroidSmsAppManager(
+ ash::android_sms::AndroidSmsAppManager* android_sms_app_manager) {
+ test_android_sms_app_manager_ = android_sms_app_manager;
+}
+#endif