/* * Copyright (c) 2021-2022 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 "base/log/event_report.h" #include "frameworks/bridge/common/dom/dom_search.h" #include "frameworks/bridge/common/dom/dom_textarea.h" #include "frameworks/bridge/js_frontend/engine/common/js_engine_loader.h" #include "frameworks/bridge/js_frontend/js_ace_page.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 }; } // namespace void JsCommandDomElementOperator::UpdateForChart(const RefPtr<DOMNode>& node) const { if (chartDatasets_ || chartOptions_ || segments_) { auto chart = AceType::DynamicCast<DOMChart>(node); if (chart) { if (chartDatasets_) { chart->SetChartAttrDatasets(*chartDatasets_); } if (chartOptions_) { chart->SetChartAttrOptions(*chartOptions_); } if (segments_) { chart->SetChartAttrSegments(*segments_); } } } } void JsCommandDomElementOperator::UpdateForImageAnimator(const RefPtr<DOMNode>& node) const { if (images_) { auto imageAnimator = AceType::DynamicCast<DOMImageAnimator>(node); if (imageAnimator) { imageAnimator->SetImagesAttr(*images_); } } } void JsCommandDomElementOperator::UpdateForClock(const RefPtr<DOMNode>& node) const { if (clockConfig_) { auto domClock = AceType::DynamicCast<DOMClock>(node); if (domClock) { domClock->SetClockConfig(*clockConfig_); } } } void JsCommandDomElementOperator::UpdateForBadge(const RefPtr<DOMNode>& node) const { if (badgeConfig_) { auto domBadge = AceType::DynamicCast<DOMBadge>(node); if (domBadge) { domBadge->SetBadgeConfig(*badgeConfig_); } } } void JsCommandDomElementOperator::UpdateForStepperLabel(const RefPtr<DOMNode>& node) const { if (stepperLabel_) { auto domStepperItem = AceType::DynamicCast<DOMStepperItem>(node); if (domStepperItem) { domStepperItem->SetLabel(*stepperLabel_); } } } void JsCommandDomElementOperator::UpdateForInput(const RefPtr<DOMNode>& node) const { if (!node || !inputOptions_) { return; } auto input = AceType::DynamicCast<DOMInput>(node); if (input) { input->SetInputOptions(*inputOptions_); return; } auto textarea = AceType::DynamicCast<DOMTextarea>(node); if (textarea) { textarea->SetInputOptions(*inputOptions_); return; } auto search = AceType::DynamicCast<DOMSearch>(node); if (search) { search->SetInputOptions(*inputOptions_); } } 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) { // the input tag of ace text and svg text is same. 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(); } // supplement: set svg tag by parentNode.hasSvgTag_ or tagName_ 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; } // useProxyNode flag is used for the dom node which is out of its previous order. // For example, navigation bar is used to set outside the root div, thus there should be a proxy node in // its previous order. 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()); // mount node to root node->Mount(-1); ScheduleUpdateForFixedNode(domDocument); useProxyNode = true; } } if (useProxyNode) { // mount proxy dom node to replace the position of original node 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; } } // generate proxy id in DomDocument 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; // option of menu and select for popup do not need auto creating } // create root accessibility node 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_); // create other accessibility node auto accessibilityManager = GetAccessibilityManager(page); if (!accessibilityManager) { LOGW("accessibilityManager not exists"); return; } if (tagName_ == DOM_NODE_TAG_OPTION) { return; // option of menu and select for popup do not need auto creating } 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); // remove accessibility node and it's children 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()); // update accessibility node 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) // update accessibility node 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 JsCommandContextOperation::Execute(const RefPtr<JsAcePage>& page) const { if (!task_) { return; } auto canvas = AceType::DynamicCast<DOMCanvas>(GetNodeFromPage(page, nodeId_)); if (!canvas) { LOGE("Node %{private}d not exists or not a canvas", nodeId_); return; } auto paintChild = AceType::DynamicCast<CustomPaintComponent>(canvas->GetSpecializedComponent()); ACE_DCHECK(paintChild); auto pool = paintChild->GetTaskPool(); if (!pool) { LOGE("canvas get pool failed"); return; } task_(pool); } 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_); } } } // namespace OHOS::Ace::Framework