chromium/ash/assistant/model/ui/assistant_card_element.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/assistant/model/ui/assistant_card_element.h"

#include <utility>

#include "ash/assistant/ui/assistant_ui_constants.h"
#include "ash/assistant/ui/assistant_view_ids.h"
#include "ash/public/cpp/ash_web_view.h"
#include "ash/public/cpp/ash_web_view_factory.h"
#include "base/base64.h"
#include "base/memory/raw_ptr.h"

namespace ash {

// AssistantCardElement::Processor ---------------------------------------------

class AssistantCardElement::Processor : public AshWebView::Observer {
 public:
  Processor(AssistantCardElement* card_element, ProcessingCallback callback)
      : card_element_(card_element), callback_(std::move(callback)) {}

  Processor(const Processor& copy) = delete;
  Processor& operator=(const Processor& assign) = delete;

  ~Processor() override {
    if (contents_view_)
      contents_view_->RemoveObserver(this);

    if (callback_)
      std::move(callback_).Run();
  }

  void Process() {
    const int width_dip =
        card_element_->viewport_width() - 2 * assistant::ui::kHorizontalMargin;

    // Configure parameters for the card. We want to configure the size as:
    // - width: It should be width_dip.
    // - height: It should be calculated from the content.
    AshWebView::InitParams contents_params;
    contents_params.enable_auto_resize = true;
    contents_params.min_size = gfx::Size(width_dip, 1);
    contents_params.max_size = gfx::Size(width_dip, INT_MAX);
    contents_params.suppress_navigation = true;
    contents_params.fix_zoom_level_to_one = true;

    // Create |contents_view_| and retain ownership until it is added to the
    // view hierarchy. If that never happens, it will be still be cleaned up.
    contents_view_ = AshWebViewFactory::Get()->Create(contents_params);
    contents_view_->SetID(AssistantViewID::kAshWebView);

    // Observe |contents_view_| so that we are notified when loading is
    // complete.
    contents_view_->AddObserver(this);

    // Encode the html string to be URL-safe.
    std::string encoded_html = base::Base64Encode(card_element_->html());

    // Navigate to the data URL which represents the card.
    constexpr char kDataUriPrefix[] = "data:text/html;base64,";
    contents_view_->Navigate(GURL(kDataUriPrefix + encoded_html));
  }

 private:
  // AshWebView::Observer:
  void DidStopLoading() override {
    contents_view_->RemoveObserver(this);

    // Pass ownership of |contents_view_| to the card element that was being
    // processed and notify our |callback_| of the completion.
    card_element_->set_contents_view(std::move(contents_view_));
    std::move(callback_).Run();
  }

  // |card_element_| should outlive the Processor.
  const raw_ptr<AssistantCardElement> card_element_;
  ProcessingCallback callback_;

  std::unique_ptr<AshWebView> contents_view_;
};

// AssistantCardElement --------------------------------------------------------

AssistantCardElement::AssistantCardElement(const std::string& html,
                                           const std::string& fallback,
                                           int viewport_width)
    : AssistantUiElement(AssistantUiElementType::kCard),
      html_(html),
      fallback_(fallback),
      viewport_width_(viewport_width) {}

AssistantCardElement::~AssistantCardElement() {
  // |processor_| should be destroyed before |this| has been deleted.
  processor_.reset();
}

void AssistantCardElement::Process(ProcessingCallback callback) {
  processor_ = std::make_unique<Processor>(this, std::move(callback));
  processor_->Process();
}

bool AssistantCardElement::has_contents_view() const {
  return !!contents_view_;
}

bool AssistantCardElement::Compare(const AssistantUiElement& other) const {
  return other.type() == AssistantUiElementType::kCard &&
         static_cast<const AssistantCardElement&>(other).html() == html_;
}

}  // namespace ash