/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsColorControlFrame.h"

#include "nsContentCreatorFunctions.h"
#include "nsContentUtils.h"
#include "nsCSSPseudoElements.h"
#include "nsGkAtoms.h"
#include "nsIFormControl.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/Document.h"

using namespace mozilla;
using mozilla::dom::CallerType;
using mozilla::dom::Document;
using mozilla::dom::Element;
using mozilla::dom::HTMLInputElement;

nsColorControlFrame::nsColorControlFrame(ComputedStyle* aStyle,
                                         nsPresContext* aPresContext)
    : nsHTMLButtonControlFrame(aStyle, aPresContext, kClassID) {}

nsIFrame* NS_NewColorControlFrame(PresShell* aPresShell,
                                  ComputedStyle* aStyle) {
  return new (aPresShell)
      nsColorControlFrame(aStyle, aPresShell->GetPresContext());
}

NS_IMPL_FRAMEARENA_HELPERS(nsColorControlFrame)

NS_QUERYFRAME_HEAD(nsColorControlFrame)
  NS_QUERYFRAME_ENTRY(nsColorControlFrame)
  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
NS_QUERYFRAME_TAIL_INHERITING(nsHTMLButtonControlFrame)

void nsColorControlFrame::Destroy(DestroyContext& aContext) {
  aContext.AddAnonymousContent(mColorContent.forget());
  nsHTMLButtonControlFrame::Destroy(aContext);
}

#ifdef DEBUG_FRAME_DUMP
nsresult nsColorControlFrame::GetFrameName(nsAString& aResult) const {
  return MakeFrameName(u"ColorControl"_ns, aResult);
}
#endif

// Create the color area for the button.
// The frame will be generated by the frame constructor.
nsresult nsColorControlFrame::CreateAnonymousContent(
    nsTArray<ContentInfo>& aElements) {
  RefPtr<Document> doc = mContent->GetComposedDoc();
  mColorContent = doc->CreateHTMLElement(nsGkAtoms::div);
  mColorContent->SetPseudoElementType(PseudoStyleType::mozColorSwatch);

  // Mark the element to be native anonymous before setting any attributes.
  mColorContent->SetIsNativeAnonymousRoot();

  nsresult rv = UpdateColor();
  NS_ENSURE_SUCCESS(rv, rv);

  // XXX(Bug 1631371) Check if this should use a fallible operation as it
  // pretended earlier.
  aElements.AppendElement(mColorContent);

  return NS_OK;
}

void nsColorControlFrame::AppendAnonymousContentTo(
    nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
  if (mColorContent) {
    aElements.AppendElement(mColorContent);
  }
}

nsresult nsColorControlFrame::UpdateColor() {
  // Get the color from the "value" property of our content; it will return the
  // default color (through the sanitization algorithm) if the value is empty.
  nsAutoString color;
  HTMLInputElement* elt = HTMLInputElement::FromNode(mContent);
  elt->GetValue(color, CallerType::System);

  if (color.IsEmpty()) {
    // OK, there is one case the color string might be empty -- if our content
    // is still being created, i.e. if it has mDoneCreating==false.  In that
    // case, we simply do nothing, because we'll be called again with a complete
    // content node before we ever reflow or paint. Specifically: we can expect
    // that HTMLInputElement::DoneCreatingElement() will set mDoneCreating to
    // true (which enables sanitization) and then it'll call SetValueInternal(),
    // which produces a nonempty color (via sanitization), and then it'll call
    // this function here, and we'll get the nonempty default color.
    MOZ_ASSERT(HasAnyStateBits(NS_FRAME_FIRST_REFLOW),
               "Content node's GetValue() should return a valid color string "
               "by the time we've been reflowed (the default color, in case "
               "no valid color is set)");
    return NS_OK;
  }

  // Set the background-color CSS property of the swatch element to this color.
  return mColorContent->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
                                u"background-color:"_ns + color,
                                /* aNotify */ true);
}

nsresult nsColorControlFrame::AttributeChanged(int32_t aNameSpaceID,
                                               nsAtom* aAttribute,
                                               int32_t aModType) {
  NS_ASSERTION(mColorContent, "The color div must exist");

  // If the value attribute is set, update the color box, but only if we're
  // still a color control, which might not be the case if the type attribute
  // was removed/changed.
  nsCOMPtr<nsIFormControl> fctrl = do_QueryInterface(GetContent());
  if (fctrl->ControlType() == FormControlType::InputColor &&
      aNameSpaceID == kNameSpaceID_None && nsGkAtoms::value == aAttribute) {
    UpdateColor();
  }
  return nsHTMLButtonControlFrame::AttributeChanged(aNameSpaceID, aAttribute,
                                                    aModType);
}

nsContainerFrame* nsColorControlFrame::GetContentInsertionFrame() {
  return this;
}
