910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/api/notifications/notifications_api.h"

#include <stddef.h>
#include <string.h>

#include <utility>

#include "base/functional/callback.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/api/notifications/extension_notification_display_helper.h"
#include "chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h"
#include "chrome/browser/extensions/api/notifications/extension_notification_handler.h"
#include "chrome/browser/notifications/notification_common.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/browser/notifications/notifier_state_tracker.h"
#include "chrome/browser/notifications/notifier_state_tracker_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/notifications/notification_style.h"
#include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
#include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_system_provider.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#include "url/gurl.h"

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

using message_center::NotifierId;

namespace extensions {

namespace notifications = api::notifications;

namespace {

// The maximum length of a notification ID, in number of characters. Some
// platforms have limitattions on the length of the ID.
constexpr int kNotificationIdLengthLimit = 500;

const char kMissingRequiredPropertiesForCreateNotification[] =
    "Some of the required properties are missing: type, iconUrl, title and "
    "message.";
const char kUnableToDecodeIconError[] =
    "Unable to successfully use the provided image.";
const char kUnexpectedProgressValueForNonProgressType[] =
    "The progress value should not be specified for non-progress notification";
const char kInvalidProgressValue[] =
    "The progress value should range from 0 to 100";
const char kExtraListItemsProvided[] =
    "List items provided for notification type != list";
const char kExtraImageProvided[] =
    "Image resource provided for notification type != image";
const char kNotificationIdTooLong[] =
    "The notification's ID should be %d characters or less";

#if !BUILDFLAG(IS_CHROMEOS)
const char kLowPriorityDeprecatedOnPlatform[] =
    "Low-priority notifications are deprecated on this platform.";
#endif

// Given an extension id and another id, returns an id that is unique
// relative to other extensions.
std::string CreateScopedIdentifier(const ExtensionId& extension_id,
                                   const std::string& id) {
  return extension_id + "-" + id;
}

// Removes the unique internal identifier to send the ID as the
// extension expects it.
std::string StripScopeFromIdentifier(const ExtensionId& extension_id,
                                     const std::string& scoped_id) {
  size_t index_of_separator = extension_id.length() + 1;
  DCHECK_LT(index_of_separator, scoped_id.length());

  return scoped_id.substr(index_of_separator);
}

// Converts the |notification_bitmap| (in RGBA format) to the |*return_image|
// (which is in ARGB format).
bool NotificationBitmapToGfxImage(
    float max_scale,
    const gfx::Size& target_size_dips,
    const notifications::NotificationBitmap& notification_bitmap,
    gfx::Image* return_image) {
  const int max_device_pixel_width = target_size_dips.width() * max_scale;
  const int max_device_pixel_height = target_size_dips.height() * max_scale;

  const int kBytesPerPixel = 4;

  const int width = notification_bitmap.width;
  const int height = notification_bitmap.height;

  if (width < 0 || height < 0 || width > max_device_pixel_width ||
      height > max_device_pixel_height)
    return false;

  // Ensure we have rgba data.
  if (!notification_bitmap.data) {
    return false;
  }

  const std::vector<uint8_t>& rgba_data = *notification_bitmap.data;

  const size_t rgba_data_length = rgba_data.size();
  const size_t rgba_area = width * height;

  if (rgba_data_length != rgba_area * kBytesPerPixel)
    return false;

  SkBitmap bitmap;
  // Allocate the actual backing store with the sanitized dimensions.
  std::vector<uint32_t> pixels(rgba_area);
  SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
  bitmap.setInfo(info, width * kBytesPerPixel);
  bitmap.setPixels(&pixels[0]);

  // Ensure that our bitmap and our data now refer to the same number of pixels.
  if (rgba_data_length != bitmap.computeByteSize())
    return false;

  for (size_t t = 0; t < rgba_area; ++t) {
    // `rgba_data` is RGBA, pixels is ARGB.
    size_t rgba_index = t * kBytesPerPixel;
    pixels[t] = SkPreMultiplyColor(((rgba_data[rgba_index + 3] & 0xFF) << 24) |
                                   ((rgba_data[rgba_index + 0] & 0xFF) << 16) |
                                   ((rgba_data[rgba_index + 1] & 0xFF) << 8) |
                                   ((rgba_data[rgba_index + 2] & 0xFF) << 0));
  }

  // Make a copy, since the current bitmap is using a local std::vector for
  // storage.
  SkBitmap copy;
  if (!copy.tryAllocPixels(bitmap.info())) {
    return false;
  }

  // Copy the bitmap.
  bitmap.readPixels(copy.pixmap());

  // TODO(dewittj): Handle HiDPI images with more than one scale factor
  // representation.
  gfx::ImageSkia skia = gfx::ImageSkia::CreateFromBitmap(copy, 1.0f);
  *return_image = gfx::Image(skia);
  return true;
}

// Returns true if a notification with the given origin should show over the
// currently fullscreen app window. If there is no fullscreen app window,
// returns false.
bool ShouldShowOverCurrentFullscreenWindow(Profile* profile,
                                           const GURL& origin) {
#if BUILDFLAG(ENABLE_PLATFORM_APPS)
  DCHECK(profile);
  ExtensionId extension_id =
      ExtensionNotificationHandler::GetExtensionId(origin);
  DCHECK(!extension_id.empty());
  AppWindowRegistry::AppWindowList windows =
      AppWindowRegistry::Get(profile)->GetAppWindowsForApp(extension_id);
  for (AppWindow* window : windows) {
    if (window->IsFullscreen() && window->GetBaseWindow()->IsActive())
      return true;
  }
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)
  return false;
}

}  // namespace

bool NotificationsApiFunction::IsNotificationsApiAvailable() {
  // We need to check this explicitly rather than letting
  // _permission_features.json enforce it, because we're sharing the
  // chrome.notifications permissions namespace with WebKit notifications.
  return extension()->is_platform_app() || extension()->is_extension();
}

NotificationsApiFunction::NotificationsApiFunction() = default;

NotificationsApiFunction::~NotificationsApiFunction() = default;

bool NotificationsApiFunction::CreateNotification(
    const std::string& id,
    api::notifications::NotificationOptions* options,
    std::string* error) {
  // First, make sure the required fields exist: type, title, message, icon.
  // These fields are defined as optional in IDL such that they can be used as
  // optional for notification updates. But for notification creations, they
  // should be present.
  if (options->type == api::notifications::TemplateType::kNone ||
      !options->icon_url || !options->title || !options->message) {
    *error = kMissingRequiredPropertiesForCreateNotification;
    return false;
  }

#if !BUILDFLAG(IS_CHROMEOS)
  if (options->priority &&
      *options->priority < message_center::DEFAULT_PRIORITY) {
    *error = kLowPriorityDeprecatedOnPlatform;
    return false;
  }
#endif

  NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();

  const float image_scale = ui::GetScaleForMaxSupportedResourceScaleFactor();

  // Extract required fields: type, title, message, and icon.
  message_center::NotificationType type =
      MapApiTemplateTypeToType(options->type);

  const std::u16string title(base::UTF8ToUTF16(*options->title));
  const std::u16string message(base::UTF8ToUTF16(*options->message));
  gfx::Image icon;

  if (!options->icon_bitmap ||
      !NotificationBitmapToGfxImage(image_scale, bitmap_sizes.icon_size,
                                    *options->icon_bitmap, &icon)) {
    *error = kUnableToDecodeIconError;
    return false;
  }

  // Then, handle any optional data that's been provided.
  message_center::RichNotificationData optional_fields;
  if (options->app_icon_mask_url) {
    gfx::Image small_icon_mask;
    if (!NotificationBitmapToGfxImage(
            image_scale, bitmap_sizes.app_icon_mask_size,
            *options->app_icon_mask_bitmap, &small_icon_mask)) {
      *error = kUnableToDecodeIconError;
      return false;
    }
    optional_fields.small_image = small_icon_mask;
    optional_fields.small_image_needs_additional_masking = true;
  }

  if (options->priority)
    optional_fields.priority = *options->priority;

  if (options->event_time)
    optional_fields.timestamp =
        base::Time::FromMillisecondsSinceUnixEpoch(*options->event_time);

  if (options->silent)
    optional_fields.silent = *options->silent;

  if (options->buttons) {
    // Currently we allow up to 2 buttons.
    size_t number_of_buttons = options->buttons->size();

    number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons;

    for (size_t i = 0; i < number_of_buttons; i++) {
      message_center::ButtonInfo info(
          base::UTF8ToUTF16((*options->buttons)[i].title));
      const auto& icon_bitmap = (*options->buttons)[i].icon_bitmap;
      if (icon_bitmap) {
        NotificationBitmapToGfxImage(image_scale, bitmap_sizes.button_icon_size,
                                     *icon_bitmap, &info.icon);
      }
      optional_fields.buttons.push_back(info);
    }
  }

  if (options->context_message) {
    optional_fields.context_message =
        base::UTF8ToUTF16(*options->context_message);
  }

  bool has_image = options->image_bitmap &&
                   NotificationBitmapToGfxImage(
                       image_scale, bitmap_sizes.image_size,
                       *options->image_bitmap, &optional_fields.image);

  // We should have an image if and only if the type is an image type.
  if (has_image != (type == message_center::NOTIFICATION_TYPE_IMAGE)) {
    *error = kExtraImageProvided;
    return false;
  }

  // We should have list items if and only if the type is a multiple type.
  bool has_list_items = options->items && !options->items->empty();
  if (has_list_items != (type == message_center::NOTIFICATION_TYPE_MULTIPLE)) {
    *error = kExtraListItemsProvided;
    return false;
  }

  if (options->progress) {
    // We should have progress if and only if the type is a progress type.
    if (type != message_center::NOTIFICATION_TYPE_PROGRESS) {
      *error = kUnexpectedProgressValueForNonProgressType;
      return false;
    }
    optional_fields.progress = *options->progress;
    // Progress value should range from 0 to 100.
    if (optional_fields.progress < 0 || optional_fields.progress > 100) {
      *error = kInvalidProgressValue;
      return false;
    }
  }

  if (has_list_items) {
    using api::notifications::NotificationItem;
    for (const NotificationItem& api_item : *options->items) {
      optional_fields.items.push_back({base::UTF8ToUTF16(api_item.title),
                                       base::UTF8ToUTF16(api_item.message)});
    }
  }

  optional_fields.settings_button_handler =
      message_center::SettingsButtonHandler::INLINE;

  // TODO(crbug.com/41348342): Remove the manual limitation in favor of an IDL
  // annotation once supported.
  if (id.size() > kNotificationIdLengthLimit) {
    *error =
        base::StringPrintf(kNotificationIdTooLong, kNotificationIdLengthLimit);
    return false;
  }

  std::string notification_id = CreateScopedIdentifier(extension_->id(), id);
  message_center::Notification notification(
      type, notification_id, title, message, ui::ImageModel::FromImage(icon),
      base::UTF8ToUTF16(extension_->name()), extension_->url(),
      message_center::NotifierId(message_center::NotifierType::APPLICATION,
                                 extension_->id()),
      optional_fields, nullptr /* delegate */);

  // Apply the "requireInteraction" flag. The value defaults to false.
  notification.set_never_timeout(options->require_interaction &&
                                 *options->require_interaction);

  // For a progress notification the message parameter won't be displayed in the
  // notification. Therefore, its value is passed to progress_status which will
  // be displayed.
  if (type == message_center::NOTIFICATION_TYPE_PROGRESS) {
    notification.set_progress_status(message);
  }

  if (ShouldShowOverCurrentFullscreenWindow(GetProfile(),
                                            notification.origin_url())) {
    notification.set_fullscreen_visibility(
        message_center::FullscreenVisibility::OVER_USER);
  }

  GetDisplayHelper()->Display(notification);
  return true;
}

bool NotificationsApiFunction::UpdateNotification(
    const std::string& id,
    api::notifications::NotificationOptions* options,
    message_center::Notification* notification,
    std::string* error) {
#if !BUILDFLAG(IS_CHROMEOS)
  if (options->priority &&
      *options->priority < message_center::DEFAULT_PRIORITY) {
    *error = kLowPriorityDeprecatedOnPlatform;
    return false;
  }
#endif

  NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
  const float image_scale = ui::GetScaleForMaxSupportedResourceScaleFactor();

  // Update optional fields if provided.
  if (options->type != api::notifications::TemplateType::kNone) {
    notification->set_type(MapApiTemplateTypeToType(options->type));
  }
  if (options->title)
    notification->set_title(base::UTF8ToUTF16(*options->title));
  if (options->message)
    notification->set_message(base::UTF8ToUTF16(*options->message));

  if (options->icon_bitmap) {
    gfx::Image icon;
    if (!NotificationBitmapToGfxImage(
            image_scale, bitmap_sizes.icon_size, *options->icon_bitmap,
            &icon)) {
      *error = kUnableToDecodeIconError;
      return false;
    }
    notification->set_icon(ui::ImageModel::FromImage(icon));
  }

  if (options->app_icon_mask_bitmap) {
    gfx::Image app_icon_mask;
    if (!NotificationBitmapToGfxImage(
            image_scale, bitmap_sizes.app_icon_mask_size,
            *options->app_icon_mask_bitmap, &app_icon_mask)) {
      *error = kUnableToDecodeIconError;
      return false;
    }
    notification->SetSmallImage(app_icon_mask);
    notification->set_small_image_needs_additional_masking(true);
  }

  if (options->priority)
    notification->set_priority(*options->priority);

  if (options->event_time)
    notification->set_timestamp(
        base::Time::FromMillisecondsSinceUnixEpoch(*options->event_time));

  if (options->silent)
    notification->set_silent(*options->silent);

  if (options->buttons) {
    // Currently we allow up to 2 buttons.
    size_t number_of_buttons = options->buttons->size();
    number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons;

    std::vector<message_center::ButtonInfo> buttons;
    for (size_t i = 0; i < number_of_buttons; i++) {
      message_center::ButtonInfo button(
          base::UTF8ToUTF16((*options->buttons)[i].title));
      const auto& icon_bitmap = (*options->buttons)[i].icon_bitmap;
      if (icon_bitmap) {
        NotificationBitmapToGfxImage(image_scale, bitmap_sizes.button_icon_size,
                                     *icon_bitmap, &button.icon);
      }
      buttons.push_back(button);
    }
    notification->set_buttons(buttons);
  }

  if (options->context_message) {
    notification->set_context_message(
        base::UTF8ToUTF16(*options->context_message));
  }

  gfx::Image image;
  bool has_image =
      options->image_bitmap &&
      NotificationBitmapToGfxImage(image_scale, bitmap_sizes.image_size,
                                   *options->image_bitmap, &image);

  if (has_image) {
    // We should have an image if and only if the type is an image type.
    if (notification->type() != message_center::NOTIFICATION_TYPE_IMAGE) {
      *error = kExtraImageProvided;
      return false;
    }
    notification->SetImage(image);
  }

  if (options->progress) {
    // We should have progress if and only if the type is a progress type.
    if (notification->type() != message_center::NOTIFICATION_TYPE_PROGRESS) {
      *error = kUnexpectedProgressValueForNonProgressType;
      return false;
    }
    int progress = *options->progress;
    // Progress value should range from 0 to 100.
    if (progress < 0 || progress > 100) {
      *error = kInvalidProgressValue;
      return false;
    }
    notification->set_progress(progress);
  }

  // For a progress notification the message parameter won't be displayed in the
  // notification. Therefore, its value is passed to progress_status which will
  // be displayed.
  if (options->message &&
      notification->type() == message_center::NOTIFICATION_TYPE_PROGRESS) {
    notification->set_progress_status(base::UTF8ToUTF16(*options->message));
  }

  if (options->items && !options->items->empty()) {
    // We should have list items if and only if the type is a multiple type.
    if (notification->type() != message_center::NOTIFICATION_TYPE_MULTIPLE) {
      *error = kExtraListItemsProvided;
      return false;
    }

    std::vector<message_center::NotificationItem> items;
    using api::notifications::NotificationItem;
    for (const NotificationItem& api_item : *options->items) {
      items.push_back({base::UTF8ToUTF16(api_item.title),
                       base::UTF8ToUTF16(api_item.message)});
    }
    notification->set_items(items);
  }

  // It's safe to follow the regular path for adding a new notification as it's
  // already been verified that there is a notification that can be updated.
  GetDisplayHelper()->Display(*notification);

  return true;
}

bool NotificationsApiFunction::AreExtensionNotificationsAllowed() const {
  NotifierStateTracker* notifier_state_tracker =
      NotifierStateTrackerFactory::GetForProfile(GetProfile());

  return notifier_state_tracker->IsNotifierEnabled(message_center::NotifierId(
      message_center::NotifierType::APPLICATION, extension_->id()));
}

bool NotificationsApiFunction::IsNotificationsApiEnabled() const {
  return CanRunWhileDisabled() || AreExtensionNotificationsAllowed();
}

bool NotificationsApiFunction::CanRunWhileDisabled() const {
  return false;
}

ExtensionNotificationDisplayHelper* NotificationsApiFunction::GetDisplayHelper()
    const {
  return ExtensionNotificationDisplayHelperFactory::GetForProfile(GetProfile());
}

Profile* NotificationsApiFunction::GetProfile() const {
  return Profile::FromBrowserContext(browser_context());
}

ExtensionFunction::ResponseAction NotificationsApiFunction::Run() {
  if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
    return RunNotificationsApi();
  } else {
    return RespondNow(Error(""));
  }
}

message_center::NotificationType
NotificationsApiFunction::MapApiTemplateTypeToType(
    api::notifications::TemplateType type) {
  switch (type) {
    case api::notifications::TemplateType::kNone:
    case api::notifications::TemplateType::kBasic:
      return message_center::NOTIFICATION_TYPE_SIMPLE;
    case api::notifications::TemplateType::kImage:
      return message_center::NOTIFICATION_TYPE_IMAGE;
    case api::notifications::TemplateType::kList:
      return message_center::NOTIFICATION_TYPE_MULTIPLE;
    case api::notifications::TemplateType::kProgress:
      return message_center::NOTIFICATION_TYPE_PROGRESS;
    default:
      // Gracefully handle newer application code that is running on an older
      // runtime that doesn't recognize the requested template.
      return message_center::NOTIFICATION_TYPE_SIMPLE;
  }
}

NotificationsCreateFunction::NotificationsCreateFunction() = default;

NotificationsCreateFunction::~NotificationsCreateFunction() = default;

ExtensionFunction::ResponseAction
NotificationsCreateFunction::RunNotificationsApi() {
  params_ = api::notifications::Create::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params_);

  std::string notification_id;
  if (params_->notification_id && !params_->notification_id->empty()) {
    // If the caller provided a notificationId, use that.
    notification_id = *params_->notification_id;
  } else {
    // Otherwise, use a randomly created GUID. In case that
    // Uuid::GenerateRandomV4().AsLowercaseString returns the empty string,
    // simply generate a random string.
    notification_id = base::Uuid::GenerateRandomV4().AsLowercaseString();
    if (notification_id.empty())
      notification_id = base::RandBytesAsString(16);
  }

  // TODO(dewittj): Add more human-readable error strings if this fails.
  std::string error;
  if (!CreateNotification(notification_id, &params_->options, &error)) {
    return RespondNow(ErrorWithArgumentsDoNotUse(
        api::notifications::Create::Results::Create(notification_id), error));
  }

  return RespondNow(WithArguments(notification_id));
}

NotificationsUpdateFunction::NotificationsUpdateFunction() = default;

NotificationsUpdateFunction::~NotificationsUpdateFunction() = default;

ExtensionFunction::ResponseAction
NotificationsUpdateFunction::RunNotificationsApi() {
  params_ = api::notifications::Update::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params_);

  // We are in update.  If the ID doesn't exist, succeed but call the callback
  // with "false".
  const message_center::Notification* matched_notification =
      GetDisplayHelper()->GetByNotificationId(
          CreateScopedIdentifier(extension_->id(), params_->notification_id));

  if (!matched_notification) {
    return RespondNow(WithArguments(false));
  }

  // Copy the existing notification to get a writable version of it.
  message_center::Notification notification = *matched_notification;

  // If we have trouble updating the notification (could be improper use of API
  // or some other reason), mark the function as failed, calling the callback
  // with false.
  // TODO(dewittj): Add more human-readable error strings if this fails.
  std::string error;
  bool could_update_notification = UpdateNotification(
      params_->notification_id, &params_->options, &notification, &error);
  if (!could_update_notification) {
    return RespondNow(ErrorWithArgumentsDoNotUse(
        api::notifications::Update::Results::Create(false), error));
  }

  // No trouble, created the notification, send true to the callback and
  // succeed.
  return RespondNow(WithArguments(true));
}

NotificationsClearFunction::NotificationsClearFunction() = default;

NotificationsClearFunction::~NotificationsClearFunction() = default;

ExtensionFunction::ResponseAction
NotificationsClearFunction::RunNotificationsApi() {
  params_ = api::notifications::Clear::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params_);

  bool cancel_result = GetDisplayHelper()->Close(
      CreateScopedIdentifier(extension_->id(), params_->notification_id));

  return RespondNow(WithArguments(cancel_result));
}

NotificationsGetAllFunction::NotificationsGetAllFunction() = default;

NotificationsGetAllFunction::~NotificationsGetAllFunction() = default;

ExtensionFunction::ResponseAction
NotificationsGetAllFunction::RunNotificationsApi() {
  std::set<std::string> notification_ids =
      GetDisplayHelper()->GetNotificationIdsForExtension(extension_->url());

  base::Value::Dict result;

  for (const auto& entry : notification_ids) {
    result.Set(StripScopeFromIdentifier(extension_->id(), entry), true);
  }

  return RespondNow(WithArguments(std::move(result)));
}

NotificationsGetPermissionLevelFunction::
    NotificationsGetPermissionLevelFunction() = default;

NotificationsGetPermissionLevelFunction::
    ~NotificationsGetPermissionLevelFunction() = default;

bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
  return true;
}

ExtensionFunction::ResponseAction
NotificationsGetPermissionLevelFunction::RunNotificationsApi() {
  api::notifications::PermissionLevel result =
      AreExtensionNotificationsAllowed()
          ? api::notifications::PermissionLevel::kGranted
          : api::notifications::PermissionLevel::kDenied;

  return RespondNow(WithArguments(api::notifications::ToString(result)));
}

}  // namespace extensions