text: // Copyright 2016 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 "third_party/blink/renderer/modules/notifications/notification_resources_loader.h"
  
  #include <cmath>
  
  #include "base/time/time.h"
  #include "third_party/abseil-cpp/absl/types/optional.h"
  #include "third_party/blink/public/common/notifications/notification_constants.h"
  #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
  #include "third_party/blink/public/mojom/notifications/notification.mojom-blink.h"
  #include "third_party/blink/renderer/platform/heap/persistent.h"
  #include "third_party/blink/renderer/platform/weborigin/kurl.h"
  #include "third_party/blink/renderer/platform/wtf/threading.h"
  
  namespace blink {
  
  namespace {
  
  // 99.9% of all images were fetched successfully in 90 seconds.
  constexpr base::TimeDelta kImageFetchTimeout = base::TimeDelta::FromSeconds(90);
  
  enum class NotificationIconType { kImage, kIcon, kBadge, kActionIcon };
  
  gfx::Size GetIconDimensions(NotificationIconType type) {
    switch (type) {
      case NotificationIconType::kImage:
        return {kNotificationMaxImageWidthPx, kNotificationMaxImageHeightPx};
      case NotificationIconType::kIcon:
        return {kNotificationMaxIconSizePx, kNotificationMaxIconSizePx};
      case NotificationIconType::kBadge:
        return {kNotificationMaxBadgeSizePx, kNotificationMaxBadgeSizePx};
      case NotificationIconType::kActionIcon:
        return {kNotificationMaxActionIconSizePx,
                kNotificationMaxActionIconSizePx};
    }
  }
  
  }  // namespace
  
  NotificationResourcesLoader::NotificationResourcesLoader(
      CompletionCallback completion_callback)
      : started_(false),
        completion_callback_(std::move(completion_callback)),
        pending_request_count_(0) {
    DCHECK(completion_callback_);
  }
  
  NotificationResourcesLoader::~NotificationResourcesLoader() = default;
  
  void NotificationResourcesLoader::Start(
      ExecutionContext* context,
      const mojom::blink::NotificationData& notification_data) {
    DCHECK(!started_);
    started_ = true;
  
    wtf_size_t num_actions = notification_data.actions.has_value()
                                 ? notification_data.actions->size()
                                 : 0;
    pending_request_count_ = 3 /* image, icon, badge */ + num_actions;
  
    // TODO(johnme): ensure image is not loaded when it will not be used.
    // TODO(mvanouwerkerk): ensure no badge is loaded when it will not be used.
    LoadIcon(context, notification_data.image,
             GetIconDimensions(NotificationIconType::kImage),
             WTF::Bind(&NotificationResourcesLoader::DidLoadIcon,
                       WrapWeakPersistent(this), WTF::Unretained(&image_)));
    LoadIcon(context, notification_data.icon,
             GetIconDimensions(NotificationIconType::kIcon),
             WTF::Bind(&NotificationResourcesLoader::DidLoadIcon,
                       WrapWeakPersistent(this), WTF::Unretained(&icon_)));
    LoadIcon(context, notification_data.badge,
             GetIconDimensions(NotificationIconType::kBadge),
             WTF::Bind(&NotificationResourcesLoader::DidLoadIcon,
                       WrapWeakPersistent(this), WTF::Unretained(&badge_)));
  
    action_icons_.Grow(num_actions);
    for (wtf_size_t i = 0; i < num_actions; i++) {
      LoadIcon(context, notification_data.actions.value()[i]->icon,
               GetIconDimensions(NotificationIconType::kActionIcon),
               WTF::Bind(&NotificationResourcesLoader::DidLoadIcon,
                         WrapWeakPersistent(this),
                         WTF::Unretained(&action_icons_[i])));
    }
  }
  
  mojom::blink::NotificationResourcesPtr
  NotificationResourcesLoader::GetResources() const {
    auto resources = mojom::blink::NotificationResources::New();
    resources->image = image_;
    resources->icon = icon_;
    resources->badge = badge_;
    resources->action_icons = action_icons_;
    return resources;
  }
  
  void NotificationResourcesLoader::Stop() {
    for (const auto& icon_loader : icon_loaders_)
      icon_loader->Stop();
  }
  
  void NotificationResourcesLoader::Trace(Visitor* visitor) const {
    visitor->Trace(icon_loaders_);
  }
  
  void NotificationResourcesLoader::LoadIcon(
      ExecutionContext* context,
      const KURL& url,
      const gfx::Size& resize_dimensions,
      ThreadedIconLoader::IconCallback icon_callback) {
    if (url.IsNull() || url.IsEmpty() || !url.IsValid()) {
      std::move(icon_callback).Run(SkBitmap(), -1.0);
      return;
    }
  
    ResourceRequest resource_request(url);
    resource_request.SetRequestContext(mojom::blink::RequestContextType::IMAGE);
    resource_request.SetRequestDestination(
        network::mojom::RequestDestination::kImage);
    resource_request.SetPriority(ResourceLoadPriority::kMedium);
    resource_request.SetTimeoutInterval(kImageFetchTimeout);
  
    auto* icon_loader = MakeGarbageCollected<ThreadedIconLoader>();
    icon_loaders_.push_back(icon_loader);
    icon_loader->Start(context, resource_request, resize_dimensions,
                       std::move(icon_callback));
  }
  
  void NotificationResourcesLoader::DidLoadIcon(SkBitmap* out_icon,
                                                SkBitmap icon,
                                                double resize_scale) {
    *out_icon = std::move(icon);
    DidFinishRequest();
  }
  
  void NotificationResourcesLoader::DidFinishRequest() {
    DCHECK_GT(pending_request_count_, 0);
    pending_request_count_--;
    if (!pending_request_count_) {
      Stop();
      std::move(completion_callback_).Run(this);
      // The |this| pointer may have been deleted now.
    }
  }
  
  }  // namespace blink
  