* Copyright (c) 2021-2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "frameworks/bridge/js_frontend/js_command.h"
#include "core/accessibility/accessibility_manager.h"
#include "base/log/event_report.h"
#include "compatible/components/search/dom_search.h"
#include "compatible/components/text_field/dom_textarea.h"
#include "core/common/dynamic_module_helper.h"
#include "frameworks/bridge/common/dom/dom_xcomponent.h"
#include "frameworks/bridge/js_frontend/engine/common/js_engine_loader.h"
#include "frameworks/bridge/js_frontend/js_ace_page.h"
#include "compatible/components/chart/chart_modifier_compatible.h"
namespace OHOS::Ace::Framework {
namespace {
inline RefPtr<DOMNode> GetNodeFromPage(const RefPtr<JsAcePage>& page, NodeId nodeId)
{
auto domDocument = page ? page->GetDomDocument() : nullptr;
if (domDocument) {
return domDocument->GetDOMNodeById(nodeId);
}
LOGE("Failed to get DOM document");
EventReport::SendJsException(JsExcepType::GET_NODE_ERR);
return nullptr;
}
inline RefPtr<AccessibilityManager> GetAccessibilityManager(const RefPtr<JsAcePage>& page)
{
if (!page) {
return nullptr;
}
auto pipelineContext = page->GetPipelineContext().Upgrade();
if (!pipelineContext) {
return nullptr;
}
return pipelineContext->GetAccessibilityManager();
}
inline void TrySaveTargetAndIdNode(const std::string& id, const std::string& target,
const RefPtr<DOMDocument>& domDocument, const RefPtr<DOMNode>& node)
{
if (!id.empty()) {
domDocument->AddNodeWithId(id, node);
}
if (!target.empty()) {
domDocument->AddNodeWithTarget(target, node);
}
}
std::vector<std::string> g_declarationNodes = {
DOM_NODE_TAG_BADGE,
DOM_NODE_TAG_BUTTON,
DOM_NODE_TAG_LABEL,
DOM_NODE_TAG_PIECE,
DOM_NODE_TAG_QRCODE,
DOM_NODE_TAG_SPAN,
DOM_NODE_TAG_SWIPER,
DOM_NODE_TAG_TEXT,
DOM_NODE_TAG_WEB,
DOM_NODE_TAG_CLOCK,
DOM_NODE_TAG_XCOMPONENT
};
}
static const ArkUIChartModifierCompatible* GetCachedModifier()
{
static const auto* modifier = []() {
auto loader = DynamicModuleHelper::GetInstance().GetLoaderByName("chart");
CHECK_NULL_RETURN(loader, static_cast<const ArkUIChartModifierCompatible*>(nullptr));
return reinterpret_cast<const ArkUIChartModifierCompatible*>(loader->GetCustomModifier());
}();
return modifier;
}
void JsCommandDomElementOperator::UpdateForChart(const RefPtr<DOMNode>& node) const
{
if (chartDatasets_ || chartOptions_ || segments_) {
auto modifier = GetCachedModifier();
CHECK_NULL_VOID(modifier);
if (chartDatasets_) {
modifier->setChartDatasets(node, chartDatasets_.get());
}
if (chartOptions_) {
modifier->setChartOptions(node, chartOptions_.get());
}
if (segments_) {
modifier->setChartSegments(node, segments_.get());
}
}
}
void JsCommandDomElementOperator::UpdateForImageAnimator(const RefPtr<DOMNode>& node) const
{
if (images_) {
auto loader = DynamicModuleHelper::GetInstance().GetLoaderByName("image-animator");
if (loader) {
loader->UpdateDomConfig(node, images_.get());
}
}
}
void JsCommandDomElementOperator::UpdateForClock(const RefPtr<DOMNode>& node) const
{
if (clockConfig_) {
auto loader = DynamicModuleHelper::GetInstance().GetLoaderByName("clock");
if (loader) {
loader->UpdateDomConfig(node, clockConfig_.get());
}
}
}
void JsCommandDomElementOperator::UpdateForBadge(const RefPtr<DOMNode>& node) const
{
if (badgeConfig_) {
auto loader = DynamicModuleHelper::GetInstance().GetLoaderByName("badge");
if (loader) {
loader->UpdateDomConfig(node, badgeConfig_.get());
}
}
}
void JsCommandDomElementOperator::UpdateForStepperLabel(const RefPtr<DOMNode>& node) const
{
if (stepperLabel_) {
auto loader = DynamicModuleHelper::GetInstance().GetLoaderByName("stepper-item");
if (loader) {
loader->UpdateDomConfig(node, stepperLabel_.get());
}
}
}
void JsCommandDomElementOperator::UpdateForInput(const RefPtr<DOMNode>& node) const
{
if (!node || !inputOptions_) {
return;
}
auto inputLoader = DynamicModuleHelper::GetInstance().GetLoaderByName("input");
if (AceType::DynamicCast<DOMInput>(node) && inputLoader) {
inputLoader->UpdateDomConfig(node, inputOptions_.get());
return;
}
auto areaLoader = DynamicModuleHelper::GetInstance().GetLoaderByName("textarea");
if (AceType::DynamicCast<DOMTextarea>(node) && areaLoader) {
areaLoader->UpdateDomConfig(node, inputOptions_.get());
return;
}
auto searchloader = DynamicModuleHelper::GetInstance().GetLoaderByName("search");
if (AceType::DynamicCast<DOMSearch>(node) && searchloader) {
searchloader->UpdateDomConfig(node, inputOptions_.get());
}
}
RefPtr<DOMNode> JsCommandDomElementCreator::CreateDomNode(const RefPtr<JsAcePage>& page, NodeId parentNodeId) const
{
if (!page) {
return nullptr;
}
auto pageId = page->GetPageId();
auto domDocument = page->GetDomDocument();
ACE_DCHECK(domDocument);
RefPtr<DOMNode> parentNode;
if (parentNodeId != -1) {
parentNode = domDocument->GetDOMNodeById(parentNodeId);
if (!parentNode) {
LOGE("Parent node %{private}d not exists", nodeId_);
EventReport::SendJsException(JsExcepType::CREATE_NODE_ERR);
return nullptr;
}
}
std::string tagName = tagName_;
if (parentNode && parentNode->HasSvgTag() && tagName_ == DOM_NODE_TAG_TEXT) {
tagName = std::string(DOM_NODE_TAG_SVG_TEXT);
}
auto node = domDocument->CreateNodeWithId(tagName, nodeId_, itemIndex_);
if (!node) {
LOGE("Failed to create DOM node %{public}s", tagName.c_str());
EventReport::SendJsException(JsExcepType::CREATE_NODE_ERR);
return nullptr;
}
if (page->IsLiteStyle()) {
node->AdjustParamInLiteMode();
}
node->SetParentNode(parentNode);
TrySaveTargetAndIdNode(id_, target_, domDocument, node);
node->SetShareId(shareId_);
node->SetPipelineContext(pipelineContext_);
node->SetIsCustomComponent(isCustomComponent_);
node->SetBoxWrap(page->IsUseBoxWrap());
node->InitializeStyle();
auto declaration = node->GetDeclaration();
if (declaration) {
declaration->BindPipelineContext(pipelineContext_);
declaration->InitializeStyle();
}
node->SetAttr(attrs_);
if (animationStyles_) {
node->SetAnimationStyle(*animationStyles_);
}
if (transitionEnter_) {
node->SetIsTransition(true);
node->SetIsEnter(true);
node->SetAnimationStyle(*transitionEnter_);
}
if (transitionExit_) {
node->SetIsTransition(true);
node->SetIsEnter(false);
node->SetAnimationStyle(*transitionExit_);
}
if (sharedTransitionName_) {
node->SetSharedTransitionStyle(*sharedTransitionName_);
}
UpdateForChart(node);
UpdateForImageAnimator(node);
UpdateForClock(node);
UpdateForBadge(node);
UpdateForStepperLabel(node);
UpdateForInput(node);
node->SetStyle(styles_);
node->AddEvent(pageId, events_);
return node;
}
RefPtr<DOMNode> JsCommandDomElementCreator::CreateDomElement(const RefPtr<JsAcePage>& page) const
{
if (!page) {
return nullptr;
}
auto pageId = page->GetPageId();
auto domDocument = page->GetDomDocument();
ACE_DCHECK(domDocument);
std::string tagName = tagName_;
auto node = domDocument->CreateNodeWithId(tagName, nodeId_, -1);
if (!node) {
EventReport::SendJsException(JsExcepType::CREATE_NODE_ERR);
return nullptr;
}
if (page->IsLiteStyle()) {
node->AdjustParamInLiteMode();
}
node->SetBoxWrap(page->IsUseBoxWrap());
TrySaveTargetAndIdNode(id_, target_, domDocument, node);
node->SetShareId(shareId_);
node->SetPipelineContext(pipelineContext_);
node->SetIsCustomComponent(isCustomComponent_);
node->InitializeStyle();
node->SetAttr(attrs_);
if (animationStyles_) {
node->SetAnimationStyle(*animationStyles_);
}
if (transitionEnter_) {
node->SetIsTransition(true);
node->SetIsEnter(true);
node->SetAnimationStyle(*transitionEnter_);
}
if (transitionExit_) {
node->SetIsTransition(true);
node->SetIsEnter(false);
node->SetAnimationStyle(*transitionExit_);
}
if (sharedTransitionName_) {
node->SetSharedTransitionStyle(*sharedTransitionName_);
}
UpdateForChart(node);
UpdateForImageAnimator(node);
UpdateForClock(node);
UpdateForBadge(node);
UpdateForStepperLabel(node);
UpdateForInput(node);
node->SetStyle(styles_);
node->AddEvent(pageId, events_);
return node;
}
void JsCommandDomElementCreator::MountDomNode(
const RefPtr<DOMNode>& node, const RefPtr<DOMDocument>& domDocument, NodeId parentNodeId) const
{
if (!node || !domDocument) {
return;
}
bool useProxyNode = false;
bool isIgnored = false;
if (tagName_ == DOM_NODE_TAG_NAVIGATION_BAR) {
auto rootStack = domDocument->GetRootStackComponent();
if (rootStack && !rootStack->HasNavigationBar()) {
node->GenerateComponentNode();
rootStack->SetNavigationBar(node->GetRootComponent());
useProxyNode = true;
isIgnored = true;
}
} else if (node->GetPosition() == PositionType::PTFIXED) {
const auto& rootStack = domDocument->GetRootStackComponent();
if (rootStack) {
rootStack->AppendChild(node->GetRootComponent());
node->Mount(-1);
ScheduleUpdateForFixedNode(domDocument);
useProxyNode = true;
}
}
if (useProxyNode) {
auto proxy = CreateDomProxy(domDocument, parentNodeId);
if (proxy) {
proxy->ConnectWith(node);
proxy->SetIsIgnored(isIgnored);
proxy->Mount(itemIndex_);
}
} else {
node->Mount(itemIndex_);
}
}
RefPtr<DOMProxy> JsCommandDomElementCreator::CreateDomProxy(
const RefPtr<DOMDocument>& domDocument, NodeId parentNodeId) const
{
RefPtr<DOMNode> parentNode;
if (parentNodeId != -1) {
parentNode = domDocument->GetDOMNodeById(parentNodeId);
if (!parentNode) {
return nullptr;
}
}
auto proxy = domDocument->CreateProxyNodeWithId(tagName_, nodeId_);
if (!proxy) {
return nullptr;
}
proxy->SetParentNode(parentNode);
proxy->SetPipelineContext(pipelineContext_);
proxy->SetProxyNode(true);
return proxy;
}
void JsCommandDomElementCreator::ScheduleUpdateForFixedNode(const RefPtr<DOMDocument>& domDocument) const
{
const auto& rootComposedStack = domDocument->GetRootComposedStack();
if (rootComposedStack) {
rootComposedStack->MarkNeedUpdate();
auto context = pipelineContext_.Upgrade();
if (context) {
context->ScheduleUpdate(rootComposedStack);
}
}
}
void JsCommandCreateDomBody::Execute(const RefPtr<JsAcePage>& page) const
{
auto domDocument = page ? page->GetDomDocument() : nullptr;
if (!domDocument) {
LOGE("Failed to get DOM document");
EventReport::SendJsException(JsExcepType::CREATE_DOM_BODY_ERR);
return;
}
auto node = CreateDomNode(page);
if (!node) {
return;
}
auto transition = node->BuildTransitionComponent();
page->SetPageTransition(transition);
node->GenerateComponentNode();
domDocument->SetPipelineContext(pipelineContext_);
domDocument->SetUpRootComponent(node);
if (tagName_ == DOM_NODE_TAG_OPTION) {
return;
}
auto accessibilityManager = GetAccessibilityManager(page);
if (!accessibilityManager) {
LOGW("accessibilityManager not exists");
return;
}
accessibilityManager->SetRootNodeId(domDocument->GetRootNodeId());
auto accessibilityNode = accessibilityManager->CreateAccessibilityNode(tagName_, nodeId_, -1, itemIndex_);
if (!accessibilityNode) {
return;
}
accessibilityManager->TrySaveTargetAndIdNode(id_, target_, accessibilityNode);
accessibilityNode->SetAttr(attrs_);
#if defined(PREVIEW)
accessibilityNode->SetStyle(styles_);
#endif
accessibilityNode->AddEvent(page->GetPageId(), events_);
}
void JsCommandCreateDomElement::Execute(const RefPtr<JsAcePage>& page) const
{
auto domDocument = page ? page->GetDomDocument() : nullptr;
if (!domDocument) {
LOGE("Failed to get DOM document");
EventReport::SendJsException(JsExcepType::CREATE_NODE_ERR);
return;
}
auto node = CreateDomElement(page);
if (!node) {
LOGE("node is nullptr");
return;
}
}
void JsCommandAddDomElement::Execute(const RefPtr<JsAcePage>& page) const
{
auto domDocument = page ? page->GetDomDocument() : nullptr;
if (!domDocument) {
LOGE("Failed to get DOM document");
EventReport::SendJsException(JsExcepType::CREATE_NODE_ERR);
return;
}
auto node = CreateDomNode(page, parentNodeId_);
if (!node) {
return;
}
MountDomNode(node, domDocument, parentNodeId_);
if (tagName_ == DOM_NODE_TAG_CANVAS) {
auto bridge = JsEngineLoader::Get().CreateCanvasBridge();
page->PushCanvasBridge(nodeId_, bridge);
}
if (tagName_ == DOM_NODE_TAG_XCOMPONENT) {
auto bridge = JsEngineLoader::Get().CreateXComponentBridge();
page->PushXComponentBridge(nodeId_, bridge);
}
page->PushNewNode(nodeId_, parentNodeId_);
auto accessibilityManager = GetAccessibilityManager(page);
if (!accessibilityManager) {
LOGW("accessibilityManager not exists");
return;
}
if (tagName_ == DOM_NODE_TAG_OPTION) {
return;
}
auto accessibilityNode =
accessibilityManager->CreateAccessibilityNode(tagName_, nodeId_, parentNodeId_, itemIndex_);
if (!accessibilityNode) {
return;
}
accessibilityManager->TrySaveTargetAndIdNode(id_, target_, accessibilityNode);
accessibilityNode->SetAttr(attrs_);
#if defined(PREVIEW)
accessibilityNode->SetStyle(styles_);
if (!animationStyles_) {
return;
}
for (const auto& animationNameKeyframe : *animationStyles_.get()) {
auto animationName = animationNameKeyframe.find(DOM_ANIMATION_NAME);
if (animationName != animationNameKeyframe.end()) {
std::vector<std::pair<std::string, std::string>> vector;
vector.emplace_back(DOM_ANIMATION_NAME, animationName->second);
accessibilityNode->SetStyle(vector);
}
}
#endif
accessibilityNode->AddEvent(page->GetPageId(), events_);
}
void JsCommandRemoveDomElement::Execute(const RefPtr<JsAcePage>& page) const
{
auto domDocument = page ? page->GetDomDocument() : nullptr;
if (!domDocument) {
LOGE("Failed to get DOM document");
EventReport::SendJsException(JsExcepType::REMOVE_DOM_ELEMENT_ERR);
return;
}
auto node = domDocument->GetDOMNodeById(nodeId_);
if (!node) {
LOGE("Node %{private}d not exists", nodeId_);
EventReport::SendJsException(JsExcepType::REMOVE_DOM_ELEMENT_ERR);
return;
}
auto parentNodeId = node->GetParentId();
domDocument->RemoveNodes(node, true);
page->PushDirtyNode(parentNodeId);
auto accessibilityManager = GetAccessibilityManager(page);
if (!accessibilityManager) {
LOGW("accessibilityManager not exists");
return;
}
auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(nodeId_);
if (!accessibilityNode) {
LOGE("Accessibility Node %{private}d not exists", nodeId_);
return;
}
accessibilityManager->RemoveAccessibilityNodes(accessibilityNode);
}
void JsCommandAppendElement::Execute(const RefPtr<JsAcePage>& page) const
{
auto domDocument = page ? page->GetDomDocument() : nullptr;
if (!domDocument) {
LOGE("Failed to get DOM document");
EventReport::SendJsException(JsExcepType::CREATE_NODE_ERR);
return;
}
auto node = GetNodeFromPage(page, nodeId_);
if (!node) {
LOGE("node is nullptr");
return;
}
RefPtr<DOMNode> parentNode;
int32_t parentNodeId = parentNodeId_;
if (parentNodeId != -1) {
parentNode = domDocument->GetDOMNodeById(parentNodeId);
if (!parentNode) {
LOGE("Parent node %{private}d not exists", nodeId_);
EventReport::SendJsException(JsExcepType::CREATE_NODE_ERR);
}
}
node->SetParentNode(parentNode);
MountDomNode(node, domDocument, parentNodeId_);
page->PushNewNode(nodeId_, parentNodeId_);
}
void JsCommandUpdateDomElementAttrs::Execute(const RefPtr<JsAcePage>& page) const
{
auto node = GetNodeFromPage(page, nodeId_);
if (!node) {
LOGE("Node %{private}d not exists", nodeId_);
EventReport::SendJsException(JsExcepType::UPDATE_DOM_ELEMENT_ERR);
return;
}
if (page->IsLiteStyle()) {
node->AdjustParamInLiteMode();
}
if (page->CheckShowCommandConsumed()) {
auto showAttr = std::find_if(std::begin(attrs_), std::end(attrs_),
[](const std::pair<std::string, std::string>& attr) { return attr.first == DOM_SHOW; });
if (showAttr != std::end(attrs_)) {
return;
}
}
TrySaveTargetAndIdNode(id_, target_, page->GetDomDocument(), node);
node->SetBoxWrap(page->IsUseBoxWrap());
node->SetAttr(attrs_);
node->SetShareId(shareId_);
UpdateForChart(node);
UpdateForImageAnimator(node);
UpdateForClock(node);
UpdateForBadge(node);
UpdateForStepperLabel(node);
UpdateForInput(node);
node->GenerateComponentNode();
page->PushDirtyNode(node->GetDirtyNodeId());
auto accessibilityManager = GetAccessibilityManager(page);
if (!accessibilityManager) {
LOGW("accessibilityManager not exists");
return;
}
auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(nodeId_);
if (!accessibilityNode) {
LOGE("Accessibility Node %{private}d not exists", nodeId_);
return;
}
accessibilityManager->TrySaveTargetAndIdNode(id_, target_, accessibilityNode);
accessibilityNode->SetAttr(attrs_);
}
void JsCommandUpdateDomElementStyles::Execute(const RefPtr<JsAcePage>& page) const
{
auto node = GetNodeFromPage(page, nodeId_);
if (!node) {
LOGE("Node %{private}d not exists", nodeId_);
EventReport::SendJsException(JsExcepType::UPDATE_DOM_ELEMENT_ERR);
return;
}
if (animationStyles_) {
node->SetAnimationStyle(*animationStyles_);
}
DisplayType displayType = node->GetDisplay();
if (displayType == DisplayType::INLINE) {
std::vector < std::pair < std::string, std::string >> stylesTemp;
for (int32_t i = 0; i < static_cast<int32_t>(styles_.size()); i++) {
std::string key = styles_[i].first;
std::string value = styles_[i].second;
if (key == "width" || key == "height" || key.find("margin") != std::string::npos ||
key.find("padding") != std::string::npos) {
continue;
}
stylesTemp.emplace_back(key, value);
}
node->SetStyle(stylesTemp);
} else {
node->SetStyle(styles_);
}
node->GenerateComponentNode();
page->PushDirtyNode(nodeId_);
#if defined(PREVIEW)
auto accessibilityManager = GetAccessibilityManager(page);
if (!accessibilityManager) {
LOGE("accessibilityManager not exists");
return;
}
auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(nodeId_);
if (!accessibilityNode) {
LOGE("Accessibility Node %{private}d not exists", nodeId_);
return;
}
accessibilityManager->TrySaveTargetAndIdNode(id_, target_, accessibilityNode);
accessibilityNode->SetStyle(styles_);
#endif
}
void JsCommandCallDomElementMethod::Execute(const RefPtr<JsAcePage>& page) const
{
auto node = GetNodeFromPage(page, nodeId_);
if (!node) {
LOGE("Node %{private}d not exists", nodeId_);
return;
}
if (method_ == DOM_FOCUS) {
page->UpdateShowAttr();
}
auto declaration = node->GetDeclaration();
if (declaration &&
std::find(g_declarationNodes.begin(), g_declarationNodes.end(), node->GetTag()) != g_declarationNodes.end()) {
declaration->CallMethod(method_, param_);
} else {
node->CallMethod(method_, param_);
}
}
void JsCommandXComponentOperation::Execute(const RefPtr<JsAcePage>& page) const
{
if (!task_) {
return;
}
auto xcomponent = AceType::DynamicCast<DOMXComponent>(GetNodeFromPage(page, nodeId_));
if (!xcomponent) {
LOGE("Node %{private}d not exists or not a xcomponent", nodeId_);
return;
}
auto child = AceType::DynamicCast<XComponentComponent>(xcomponent->GetSpecializedComponent());
ACE_DCHECK(child);
auto pool = child->GetTaskPool();
if (!pool) {
LOGE("xcomponent get pool failed");
return;
}
task_(pool);
}
void JsCommandAnimation::Execute(const RefPtr<JsAcePage>& page) const
{
if (!page) {
LOGE("execute animation command failed. page is null.");
return;
}
if (task_) {
task_->AnimationBridgeTaskFunc(page, nodeId_);
}
}
void JsCommandAnimator::Execute(const RefPtr<JsAcePage>& page) const
{
if (!page) {
LOGE("execute animation command failed. page is null.");
return;
}
if (task_) {
task_->AnimatorBridgeTaskFunc(page, bridgeId_);
}
}
}