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

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "xfa/fxfa/cxfa_ffdocview.h"

#include <set>
#include <utility>

#include "core/fxcrt/fx_extension.h"
#include "core/fxcrt/stl_util.h"
#include "core/fxcrt/xml/cfx_xmlparser.h"
#include "fxjs/gc/container_trace.h"
#include "fxjs/xfa/cfxjse_engine.h"
#include "fxjs/xfa/cjx_object.h"
#include "third_party/base/check_op.h"
#include "third_party/base/containers/contains.h"
#include "xfa/fxfa/cxfa_ffapp.h"
#include "xfa/fxfa/cxfa_ffbarcode.h"
#include "xfa/fxfa/cxfa_ffcheckbutton.h"
#include "xfa/fxfa/cxfa_ffdoc.h"
#include "xfa/fxfa/cxfa_ffexclgroup.h"
#include "xfa/fxfa/cxfa_fffield.h"
#include "xfa/fxfa/cxfa_ffimage.h"
#include "xfa/fxfa/cxfa_ffimageedit.h"
#include "xfa/fxfa/cxfa_ffpageview.h"
#include "xfa/fxfa/cxfa_ffpushbutton.h"
#include "xfa/fxfa/cxfa_ffsignature.h"
#include "xfa/fxfa/cxfa_fftext.h"
#include "xfa/fxfa/cxfa_ffwidget.h"
#include "xfa/fxfa/cxfa_ffwidgethandler.h"
#include "xfa/fxfa/cxfa_fwladapterwidgetmgr.h"
#include "xfa/fxfa/cxfa_readynodeiterator.h"
#include "xfa/fxfa/cxfa_textprovider.h"
#include "xfa/fxfa/layout/cxfa_layoutprocessor.h"
#include "xfa/fxfa/parser/cxfa_acrobat.h"
#include "xfa/fxfa/parser/cxfa_binditems.h"
#include "xfa/fxfa/parser/cxfa_calculate.h"
#include "xfa/fxfa/parser/cxfa_pageset.h"
#include "xfa/fxfa/parser/cxfa_present.h"
#include "xfa/fxfa/parser/cxfa_subform.h"
#include "xfa/fxfa/parser/cxfa_validate.h"
#include "xfa/fxfa/parser/xfa_utils.h"

namespace {

bool IsValidXMLNameString(const WideString& str) {
  bool first = true;
  for (const auto ch : str) {
    if (!CFX_XMLParser::IsXMLNameChar(ch, first)) {
      return false;
    }
    first = false;
  }
  return true;
}

}  // namespace

const XFA_AttributeValue kXFAEventActivity[] = {
    XFA_AttributeValue::Click,      XFA_AttributeValue::Change,
    XFA_AttributeValue::DocClose,   XFA_AttributeValue::DocReady,
    XFA_AttributeValue::Enter,      XFA_AttributeValue::Exit,
    XFA_AttributeValue::Full,       XFA_AttributeValue::IndexChange,
    XFA_AttributeValue::Initialize, XFA_AttributeValue::MouseDown,
    XFA_AttributeValue::MouseEnter, XFA_AttributeValue::MouseExit,
    XFA_AttributeValue::MouseUp,    XFA_AttributeValue::PostExecute,
    XFA_AttributeValue::PostOpen,   XFA_AttributeValue::PostPrint,
    XFA_AttributeValue::PostSave,   XFA_AttributeValue::PostSign,
    XFA_AttributeValue::PostSubmit, XFA_AttributeValue::PreExecute,
    XFA_AttributeValue::PreOpen,    XFA_AttributeValue::PrePrint,
    XFA_AttributeValue::PreSave,    XFA_AttributeValue::PreSign,
    XFA_AttributeValue::PreSubmit,  XFA_AttributeValue::Ready,
    XFA_AttributeValue::Unknown,
};

CXFA_FFDocView::UpdateScope::UpdateScope(CXFA_FFDocView* pDocView)
    : m_pDocView(pDocView) {
  m_pDocView->LockUpdate();
}

CXFA_FFDocView::UpdateScope::~UpdateScope() {
  m_pDocView->UnlockUpdate();
  m_pDocView->UpdateDocView();
}

CXFA_FFDocView::CXFA_FFDocView(CXFA_FFDoc* pDoc) : m_pDoc(pDoc) {}

CXFA_FFDocView::~CXFA_FFDocView() = default;

void CXFA_FFDocView::Trace(cppgc::Visitor* visitor) const {
  visitor->Trace(m_pDoc);
  visitor->Trace(m_pWidgetHandler);
  visitor->Trace(m_pFocusNode);
  visitor->Trace(m_pFocusWidget);
  ContainerTrace(visitor, m_ValidateNodes);
  ContainerTrace(visitor, m_CalculateNodes);
  ContainerTrace(visitor, m_NewAddedNodes);
  ContainerTrace(visitor, m_BindItems);
  ContainerTrace(visitor, m_IndexChangedSubforms);
}

void CXFA_FFDocView::InitLayout(CXFA_Node* pNode) {
  RunBindItems();
  ExecEventActivityByDeepFirst(pNode, XFA_EVENT_Initialize, false, true);
  ExecEventActivityByDeepFirst(pNode, XFA_EVENT_IndexChange, false, true);
}

int32_t CXFA_FFDocView::StartLayout() {
  m_iStatus = LayoutStatus::kStart;
  m_pDoc->GetXFADoc()->DoProtoMerge();
  m_pDoc->GetXFADoc()->DoDataMerge();

  int32_t iStatus = GetLayoutProcessor()->StartLayout();
  if (iStatus < 0)
    return iStatus;

  CXFA_Node* pRootItem =
      ToNode(m_pDoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Form));
  if (!pRootItem)
    return iStatus;

  InitLayout(pRootItem);
  InitCalculate(pRootItem);
  InitValidate(pRootItem);

  ExecEventActivityByDeepFirst(pRootItem, XFA_EVENT_Ready, true, true);
  m_iStatus = LayoutStatus::kStart;
  return iStatus;
}

int32_t CXFA_FFDocView::DoLayout() {
  int32_t iStatus = GetLayoutProcessor()->DoLayout();
  if (iStatus != 100)
    return iStatus;

  m_iStatus = LayoutStatus::kDoing;
  return iStatus;
}

void CXFA_FFDocView::StopLayout() {
  CXFA_Node* pRootItem =
      ToNode(m_pDoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Form));
  if (!pRootItem)
    return;

  CXFA_Subform* pSubformNode =
      pRootItem->GetChild<CXFA_Subform>(0, XFA_Element::Subform, false);
  if (!pSubformNode)
    return;

  CXFA_PageSet* pPageSetNode =
      pSubformNode->GetFirstChildByClass<CXFA_PageSet>(XFA_Element::PageSet);
  if (!pPageSetNode)
    return;

  RunCalculateWidgets();
  RunValidate();

  InitLayout(pPageSetNode);
  InitCalculate(pPageSetNode);
  InitValidate(pPageSetNode);

  ExecEventActivityByDeepFirst(pPageSetNode, XFA_EVENT_Ready, true, true);
  ExecEventActivityByDeepFirst(pRootItem, XFA_EVENT_Ready, false, true);
  ExecEventActivityByDeepFirst(pRootItem, XFA_EVENT_DocReady, false, true);

  RunCalculateWidgets();
  RunValidate();

  if (RunLayout())
    ExecEventActivityByDeepFirst(pRootItem, XFA_EVENT_Ready, false, true);

  m_CalculateNodes.clear();
  if (m_pFocusNode && !m_pFocusWidget)
    SetFocusNode(m_pFocusNode);

  m_iStatus = LayoutStatus::kEnd;
}

void CXFA_FFDocView::AddNullTestMsg(const WideString& msg) {
  m_NullTestMsgArray.push_back(msg);
}

void CXFA_FFDocView::ShowNullTestMsg() {
  int32_t iCount = fxcrt::CollectionSize<int32_t>(m_NullTestMsgArray);
  CXFA_FFApp* pApp = m_pDoc->GetApp();
  CXFA_FFApp::CallbackIface* pAppProvider = pApp->GetAppProvider();
  if (pAppProvider && iCount) {
    int32_t iRemain = iCount > 7 ? iCount - 7 : 0;
    iCount -= iRemain;
    WideString wsMsg;
    for (int32_t i = 0; i < iCount; i++)
      wsMsg += m_NullTestMsgArray[i] + L"\n";

    if (iRemain > 0) {
      wsMsg += L"\n" + WideString::Format(
                           L"Message limit exceeded. Remaining %d "
                           L"validation errors not reported.",
                           iRemain);
    }
    pAppProvider->MsgBox(wsMsg, pAppProvider->GetAppTitle(),
                         static_cast<uint32_t>(AlertIcon::kStatus),
                         static_cast<uint32_t>(AlertButton::kOK));
  }
  m_NullTestMsgArray.clear();
}

void CXFA_FFDocView::UpdateDocView() {
  if (IsUpdateLocked())
    return;

  LockUpdate();
  while (!m_NewAddedNodes.empty()) {
    CXFA_Node* pNode = m_NewAddedNodes.front();
    m_NewAddedNodes.pop_front();
    InitCalculate(pNode);
    InitValidate(pNode);
    ExecEventActivityByDeepFirst(pNode, XFA_EVENT_Ready, true, true);
  }

  RunSubformIndexChange();
  RunCalculateWidgets();
  RunValidate();

  ShowNullTestMsg();

  if (RunLayout() && m_bLayoutEvent)
    RunEventLayoutReady();

  m_bLayoutEvent = false;
  m_CalculateNodes.clear();
  UnlockUpdate();
}

void CXFA_FFDocView::UpdateUIDisplay(CXFA_Node* pNode, CXFA_FFWidget* pExcept) {
  CXFA_FFWidget* pWidget = GetWidgetForNode(pNode);
  CXFA_FFWidget* pNext = nullptr;
  for (; pWidget; pWidget = pNext) {
    pNext = pWidget->GetNextFFWidget();
    if (pWidget == pExcept || !pWidget->IsLoaded() ||
        (pNode->GetFFWidgetType() != XFA_FFWidgetType::kCheckButton &&
         pWidget->IsFocused())) {
      continue;
    }
    pWidget->UpdateFWLData();
    pWidget->InvalidateRect();
  }
}

int32_t CXFA_FFDocView::CountPageViews() const {
  CXFA_LayoutProcessor* pProcessor = GetLayoutProcessor();
  return pProcessor ? pProcessor->CountPages() : 0;
}

CXFA_FFPageView* CXFA_FFDocView::GetPageView(int32_t nIndex) const {
  CXFA_LayoutProcessor* pProcessor = GetLayoutProcessor();
  if (!pProcessor)
    return nullptr;

  auto* pPage = pProcessor->GetPage(nIndex);
  return pPage ? pPage->GetPageView() : nullptr;
}

CXFA_LayoutProcessor* CXFA_FFDocView::GetLayoutProcessor() const {
  return CXFA_LayoutProcessor::FromDocument(m_pDoc->GetXFADoc());
}

bool CXFA_FFDocView::ResetSingleNodeData(CXFA_Node* pNode) {
  XFA_Element eType = pNode->GetElementType();
  if (eType != XFA_Element::Field && eType != XFA_Element::ExclGroup)
    return false;

  pNode->ResetData();
  UpdateUIDisplay(pNode, nullptr);
  CXFA_Validate* validate = pNode->GetValidateIfExists();
  if (!validate)
    return true;

  AddValidateNode(pNode);
  validate->SetFlag(XFA_NodeFlag::kNeedsInitApp);
  return true;
}

void CXFA_FFDocView::ResetNode(CXFA_Node* pNode) {
  m_bLayoutEvent = true;
  bool bChanged = false;
  CXFA_Node* pFormNode = nullptr;
  if (pNode) {
    bChanged = ResetSingleNodeData(pNode);
    pFormNode = pNode;
  } else {
    pFormNode = GetRootSubform();
  }
  if (!pFormNode)
    return;

  if (pFormNode->GetElementType() != XFA_Element::Field &&
      pFormNode->GetElementType() != XFA_Element::ExclGroup) {
    CXFA_ReadyNodeIterator it(pFormNode);
    while (CXFA_Node* next_node = it.MoveToNext()) {
      bChanged |= ResetSingleNodeData(next_node);
      if (next_node->GetElementType() == XFA_Element::ExclGroup)
        it.SkipTree();
    }
  }
  if (bChanged)
    m_pDoc->SetChangeMark();
}

CXFA_FFWidget* CXFA_FFDocView::GetWidgetForNode(CXFA_Node* node) {
  return GetFFWidget(
      ToContentLayoutItem(GetLayoutProcessor()->GetLayoutItem(node)));
}

CXFA_FFWidgetHandler* CXFA_FFDocView::GetWidgetHandler() {
  if (!m_pWidgetHandler) {
    m_pWidgetHandler = cppgc::MakeGarbageCollected<CXFA_FFWidgetHandler>(
        m_pDoc->GetHeap()->GetAllocationHandle(), this);
  }
  return m_pWidgetHandler;
}

bool CXFA_FFDocView::SetFocus(CXFA_FFWidget* pNewFocus) {
  if (pNewFocus == m_pFocusWidget)
    return false;

  if (m_pFocusWidget) {
    CXFA_ContentLayoutItem* pItem = m_pFocusWidget->GetLayoutItem();
    if (pItem->TestStatusBits(XFA_WidgetStatus::kVisible) &&
        !pItem->TestStatusBits(XFA_WidgetStatus::kFocused)) {
      if (!m_pFocusWidget->IsLoaded())
        m_pFocusWidget->LoadWidget();
      if (!m_pFocusWidget->OnSetFocus(m_pFocusWidget))
        m_pFocusWidget.Clear();
    }
  }
  if (m_pFocusWidget) {
    if (!m_pFocusWidget->OnKillFocus(pNewFocus))
      return false;
  }

  if (pNewFocus) {
    if (pNewFocus->GetLayoutItem()->TestStatusBits(
            XFA_WidgetStatus::kVisible)) {
      if (!pNewFocus->IsLoaded())
        pNewFocus->LoadWidget();
      if (!pNewFocus->OnSetFocus(m_pFocusWidget))
        pNewFocus = nullptr;
    }
  }
  if (pNewFocus) {
    CXFA_Node* node = pNewFocus->GetNode();
    m_pFocusNode = node->IsWidgetReady() ? node : nullptr;
    m_pFocusWidget = pNewFocus;
  } else {
    m_pFocusNode.Clear();
    m_pFocusWidget.Clear();
  }
  return true;
}

void CXFA_FFDocView::SetFocusNode(CXFA_Node* node) {
  CXFA_FFWidget* pNewFocus = node ? GetWidgetForNode(node) : nullptr;
  if (!SetFocus(pNewFocus))
    return;

  m_pFocusNode = node;
  if (m_iStatus != LayoutStatus::kEnd)
    return;

  m_pDoc->SetFocusWidget(m_pFocusWidget);
}

void CXFA_FFDocView::DeleteLayoutItem(CXFA_FFWidget* pWidget) {
  if (m_pFocusNode != pWidget->GetNode())
    return;

  m_pFocusNode.Clear();
  m_pFocusWidget.Clear();
}

static XFA_EventError XFA_ProcessEvent(CXFA_FFDocView* pDocView,
                                       CXFA_Node* pNode,
                                       CXFA_EventParam* pParam) {
  if (!pParam || pParam->m_eType == XFA_EVENT_Unknown)
    return XFA_EventError::kNotExist;
  if (pNode && pNode->GetElementType() == XFA_Element::Draw)
    return XFA_EventError::kNotExist;

  switch (pParam->m_eType) {
    case XFA_EVENT_Calculate:
      return pNode->ProcessCalculate(pDocView);
    case XFA_EVENT_Validate:
      if (pDocView->GetDoc()->IsValidationsEnabled())
        return pNode->ProcessValidate(pDocView, 0x01);
      return XFA_EventError::kDisabled;
    case XFA_EVENT_InitCalculate: {
      CXFA_Calculate* calc = pNode->GetCalculateIfExists();
      if (!calc)
        return XFA_EventError::kNotExist;
      if (pNode->IsUserInteractive())
        return XFA_EventError::kDisabled;
      return pNode->ExecuteScript(pDocView, calc->GetScriptIfExists(), pParam);
    }
    default:
      return pNode->ProcessEvent(pDocView, kXFAEventActivity[pParam->m_eType],
                                 pParam);
  }
}

XFA_EventError CXFA_FFDocView::ExecEventActivityByDeepFirst(
    CXFA_Node* pFormNode,
    XFA_EVENTTYPE eEventType,
    bool bIsFormReady,
    bool bRecursive) {
  if (!pFormNode)
    return XFA_EventError::kNotExist;

  XFA_Element elementType = pFormNode->GetElementType();
  if (elementType == XFA_Element::Field) {
    if (eEventType == XFA_EVENT_IndexChange)
      return XFA_EventError::kNotExist;

    if (!pFormNode->IsWidgetReady())
      return XFA_EventError::kNotExist;

    CXFA_EventParam eParam;
    eParam.m_eType = eEventType;
    eParam.m_bIsFormReady = bIsFormReady;
    return XFA_ProcessEvent(this, pFormNode, &eParam);
  }

  XFA_EventError iRet = XFA_EventError::kNotExist;
  if (bRecursive) {
    for (CXFA_Node* pNode = pFormNode->GetFirstContainerChild(); pNode;
         pNode = pNode->GetNextContainerSibling()) {
      elementType = pNode->GetElementType();
      if (elementType != XFA_Element::Variables &&
          elementType != XFA_Element::Draw) {
        XFA_EventErrorAccumulate(
            &iRet, ExecEventActivityByDeepFirst(pNode, eEventType, bIsFormReady,
                                                bRecursive));
      }
    }
  }
  if (!pFormNode->IsWidgetReady())
    return iRet;

  CXFA_EventParam eParam;
  eParam.m_eType = eEventType;
  eParam.m_bIsFormReady = bIsFormReady;

  XFA_EventErrorAccumulate(&iRet, XFA_ProcessEvent(this, pFormNode, &eParam));
  return iRet;
}

CXFA_FFWidget* CXFA_FFDocView::GetWidgetByName(const WideString& wsName,
                                               CXFA_FFWidget* pRefWidget) {
  if (!IsValidXMLNameString(wsName)) {
    return nullptr;
  }
  CFXJSE_Engine* pScriptContext = m_pDoc->GetXFADoc()->GetScriptContext();
  CXFA_Node* pRefNode = nullptr;
  if (pRefWidget) {
    CXFA_Node* node = pRefWidget->GetNode();
    pRefNode = node->IsWidgetReady() ? node : nullptr;
  }
  WideString wsExpression = (!pRefNode ? L"$form." : L"") + wsName;
  absl::optional<CFXJSE_Engine::ResolveResult> maybeResult =
      pScriptContext->ResolveObjects(
          pRefNode, wsExpression.AsStringView(),
          Mask<XFA_ResolveFlag>{
              XFA_ResolveFlag::kChildren, XFA_ResolveFlag::kProperties,
              XFA_ResolveFlag::kSiblings, XFA_ResolveFlag::kParent});
  if (!maybeResult.has_value())
    return nullptr;

  if (maybeResult.value().type == CFXJSE_Engine::ResolveResult::Type::kNodes) {
    CXFA_Node* pNode = maybeResult.value().objects.front()->AsNode();
    if (pNode && pNode->IsWidgetReady())
      return GetWidgetForNode(pNode);
  }
  return nullptr;
}

void CXFA_FFDocView::OnPageViewEvent(CXFA_ViewLayoutItem* pSender,
                                     CXFA_FFDoc::PageViewEvent eEvent) {
  CXFA_FFPageView* pFFPageView = pSender ? pSender->GetPageView() : nullptr;
  m_pDoc->OnPageViewEvent(pFFPageView, eEvent);
}

void CXFA_FFDocView::InvalidateRect(CXFA_FFPageView* pPageView,
                                    const CFX_RectF& rtInvalidate) {
  m_pDoc->InvalidateRect(pPageView, rtInvalidate);
}

bool CXFA_FFDocView::RunLayout() {
  LockUpdate();
  m_bInLayoutStatus = true;

  CXFA_LayoutProcessor* pProcessor = GetLayoutProcessor();
  if (!pProcessor->IncrementLayout() && pProcessor->StartLayout() < 100) {
    pProcessor->DoLayout();
    UnlockUpdate();
    m_bInLayoutStatus = false;
    m_pDoc->OnPageViewEvent(nullptr, CXFA_FFDoc::PageViewEvent::kStopLayout);
    return true;
  }

  m_bInLayoutStatus = false;
  m_pDoc->OnPageViewEvent(nullptr, CXFA_FFDoc::PageViewEvent::kStopLayout);
  UnlockUpdate();
  return false;
}

void CXFA_FFDocView::RunSubformIndexChange() {
  std::set<CXFA_Node*> seen;
  while (!m_IndexChangedSubforms.empty()) {
    CXFA_Node* pSubformNode = m_IndexChangedSubforms.front();
    m_IndexChangedSubforms.pop_front();
    bool bInserted = seen.insert(pSubformNode).second;
    if (!bInserted || !pSubformNode->IsWidgetReady())
      continue;

    CXFA_EventParam eParam;
    eParam.m_eType = XFA_EVENT_IndexChange;
    pSubformNode->ProcessEvent(this, XFA_AttributeValue::IndexChange, &eParam);
  }
}

void CXFA_FFDocView::AddNewFormNode(CXFA_Node* pNode) {
  m_NewAddedNodes.push_back(pNode);
  InitLayout(pNode);
}

void CXFA_FFDocView::AddIndexChangedSubform(CXFA_Subform* pNode) {
  if (!pdfium::Contains(m_IndexChangedSubforms, pNode))
    m_IndexChangedSubforms.push_back(pNode);
}

void CXFA_FFDocView::RunDocClose() {
  CXFA_Node* pRootItem =
      ToNode(m_pDoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Form));
  if (!pRootItem)
    return;

  ExecEventActivityByDeepFirst(pRootItem, XFA_EVENT_DocClose, false, true);
}

void CXFA_FFDocView::AddCalculateNode(CXFA_Node* node) {
  CXFA_Node* pCurrentNode =
      !m_CalculateNodes.empty() ? m_CalculateNodes.back() : nullptr;
  if (pCurrentNode != node)
    m_CalculateNodes.push_back(node);
}

void CXFA_FFDocView::AddCalculateNodeNotify(CXFA_Node* pNodeChange) {
  CJX_Object::CalcData* pGlobalData = pNodeChange->JSObject()->GetCalcData();
  if (!pGlobalData)
    return;

  for (auto& pResult : pGlobalData->m_Globals) {
    if (!pResult->HasRemovedChildren() && pResult->IsWidgetReady())
      AddCalculateNode(pResult);
  }
}

size_t CXFA_FFDocView::RunCalculateRecursive(size_t index) {
  while (index < m_CalculateNodes.size()) {
    CXFA_Node* node = m_CalculateNodes[index];

    AddCalculateNodeNotify(node);
    size_t recurse = node->JSObject()->GetCalcRecursionCount() + 1;
    node->JSObject()->SetCalcRecursionCount(recurse);
    if (recurse > 11)
      break;
    if (node->ProcessCalculate(this) == XFA_EventError::kSuccess &&
        node->IsWidgetReady()) {
      AddValidateNode(node);
    }

    index = RunCalculateRecursive(++index);
  }
  return index;
}

XFA_EventError CXFA_FFDocView::RunCalculateWidgets() {
  if (!m_pDoc->IsCalculationsEnabled())
    return XFA_EventError::kDisabled;

  if (!m_CalculateNodes.empty())
    RunCalculateRecursive(0);

  for (CXFA_Node* node : m_CalculateNodes)
    node->JSObject()->SetCalcRecursionCount(0);

  m_CalculateNodes.clear();
  return XFA_EventError::kSuccess;
}

void CXFA_FFDocView::AddValidateNode(CXFA_Node* node) {
  if (!pdfium::Contains(m_ValidateNodes, node))
    m_ValidateNodes.push_back(node);
}

void CXFA_FFDocView::InitCalculate(CXFA_Node* pNode) {
  ExecEventActivityByDeepFirst(pNode, XFA_EVENT_InitCalculate, false, true);
}

void CXFA_FFDocView::ProcessValueChanged(CXFA_Node* node) {
  AddValidateNode(node);
  AddCalculateNode(node);
  RunCalculateWidgets();
  RunValidate();
}

bool CXFA_FFDocView::InitValidate(CXFA_Node* pNode) {
  if (!m_pDoc->IsValidationsEnabled())
    return false;

  ExecEventActivityByDeepFirst(pNode, XFA_EVENT_Validate, false, true);
  m_ValidateNodes.clear();
  return true;
}

bool CXFA_FFDocView::RunValidate() {
  if (!m_pDoc->IsValidationsEnabled())
    return false;

  while (!m_ValidateNodes.empty()) {
    CXFA_Node* node = m_ValidateNodes.front();
    m_ValidateNodes.pop_front();
    if (!node->HasRemovedChildren())
      node->ProcessValidate(this, 0);
  }
  return true;
}

bool CXFA_FFDocView::RunEventLayoutReady() {
  CXFA_Node* pRootItem =
      ToNode(m_pDoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Form));
  if (!pRootItem)
    return false;

  ExecEventActivityByDeepFirst(pRootItem, XFA_EVENT_Ready, false, true);
  RunLayout();
  return true;
}

void CXFA_FFDocView::RunBindItems() {
  while (!m_BindItems.empty()) {
    CXFA_BindItems* item = m_BindItems.front();
    m_BindItems.pop_front();
    if (item->HasRemovedChildren())
      continue;

    CXFA_Node* pWidgetNode = item->GetParent();
    if (!pWidgetNode || !pWidgetNode->IsWidgetReady())
      continue;

    CFXJSE_Engine* pScriptContext =
        pWidgetNode->GetDocument()->GetScriptContext();
    WideString wsRef = item->GetRef();
    absl::optional<CFXJSE_Engine::ResolveResult> maybeRS =
        pScriptContext->ResolveObjects(
            pWidgetNode, wsRef.AsStringView(),
            Mask<XFA_ResolveFlag>{
                XFA_ResolveFlag::kChildren, XFA_ResolveFlag::kProperties,
                XFA_ResolveFlag::kSiblings, XFA_ResolveFlag::kParent,
                XFA_ResolveFlag::kALL});
    pWidgetNode->DeleteItem(-1, false, false);
    if (!maybeRS.has_value() ||
        maybeRS.value().type != CFXJSE_Engine::ResolveResult::Type::kNodes ||
        maybeRS.value().objects.empty()) {
      continue;
    }
    WideString wsValueRef = item->GetValueRef();
    WideString wsLabelRef = item->GetLabelRef();
    const bool bUseValue = wsLabelRef.IsEmpty() || wsLabelRef == wsValueRef;
    const bool bLabelUseContent =
        wsLabelRef.IsEmpty() || wsLabelRef.EqualsASCII("$");
    const bool bValueUseContent =
        wsValueRef.IsEmpty() || wsValueRef.EqualsASCII("$");
    WideString wsValue;
    WideString wsLabel;
    uint32_t uValueHash = FX_HashCode_GetW(wsValueRef.AsStringView());
    for (auto& refObject : maybeRS.value().objects) {
      CXFA_Node* refNode = refObject->AsNode();
      if (!refNode)
        continue;

      if (bValueUseContent) {
        wsValue = refNode->JSObject()->GetContent(false);
      } else {
        CXFA_Node* nodeValue = refNode->GetFirstChildByName(uValueHash);
        wsValue = nodeValue ? nodeValue->JSObject()->GetContent(false)
                            : refNode->JSObject()->GetContent(false);
      }

      if (!bUseValue) {
        if (bLabelUseContent) {
          wsLabel = refNode->JSObject()->GetContent(false);
        } else {
          CXFA_Node* nodeLabel =
              refNode->GetFirstChildByName(wsLabelRef.AsStringView());
          if (nodeLabel)
            wsLabel = nodeLabel->JSObject()->GetContent(false);
        }
      } else {
        wsLabel = wsValue;
      }
      pWidgetNode->InsertItem(wsLabel, wsValue, false);
    }
  }
}

void CXFA_FFDocView::SetChangeMark() {
  if (m_iStatus != LayoutStatus::kEnd)
    return;

  m_pDoc->SetChangeMark();
}

CXFA_Node* CXFA_FFDocView::GetRootSubform() {
  CXFA_Node* pFormPacketNode =
      ToNode(m_pDoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Form));
  if (!pFormPacketNode)
    return nullptr;

  return pFormPacketNode->GetFirstChildByClass<CXFA_Subform>(
      XFA_Element::Subform);
}
