diff --git a/OAT.xml b/OAT.xml
index 2e61a7ec4c..6e26ec7e77 100644
--- a/OAT.xml
+++ b/OAT.xml
@@ -74,6 +74,10 @@ Note:If the text contains special characters, please escape them according to th
+
+
+
+
diff --git a/tools/ap_file_viewer/.gitignore b/tools/ap_file_viewer/.gitignore
new file mode 100755
index 0000000000..b2ae261f16
--- /dev/null
+++ b/tools/ap_file_viewer/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+coverage/
+dist
+package-lock.json
+.idea
diff --git a/tools/ap_file_viewer/README_zh.md b/tools/ap_file_viewer/README_zh.md
new file mode 100755
index 0000000000..ea85facd52
--- /dev/null
+++ b/tools/ap_file_viewer/README_zh.md
@@ -0,0 +1,69 @@
+# Ap文件可视化工具
+
+#### 简介
+
+本文主要介绍Ap文件可视化工具,该工具根据ark_js_vm生成的ap文件实现可视化分析,该工具通过web访问。
+
+#### 目录
+
+```
+arkcompiler/ets_runtime/tools/ap_file_viewer
+├── build.py 构建脚本
+├── native ap文件解析为json源码
+├── package.json 项目配置文件
+├── README.md 工具使用指导文档
+├── server 工具web服务代码
+├── src 工具前端源码
+└── webpack.config.js webpack打包配置文件
+```
+
+#### 使用对象
+
+分析ap文件的开发者
+
+#### 使用场景
+
+需对ap文件分析,窗口中展示可视化展示ap文件内容
+
+#### 工具使用
+
+具体的工具使用步骤,可以左键单击以下链接了解:
+
+[工具使用说明](./docs/INSTRUCTION_zh.md)
+
+
+### 工具输出
+
+在window或linux环境下,根据导入ap件,如下图所示:
+
+![](./figures/img.png)
+
+## 工具开发说明
+
+### 对象
+
+工具的开发者
+
+### 开发场景
+
+若当前工具的功能已经不能满足开发者的全部需求,则开发者可以基于已有的源码对工具进行二次开发,来增强工具的能力,编译打包生成自定义的工具。
+
+### 开发步骤
+
+[工具开发说明](./docs/DEVELOP_zh.md)
+
+## 版本说明
+
+暂无
+
+## FAQ
+
+## 参与贡献
+
+暂无
+
+## 相关仓
+
+暂无
+
+
diff --git a/tools/ap_file_viewer/build.py b/tools/ap_file_viewer/build.py
new file mode 100755
index 0000000000..ca808039fa
--- /dev/null
+++ b/tools/ap_file_viewer/build.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2024 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.
+#
+
+import os
+import subprocess
+import argparse
+import shutil
+
+
+def get_script_directory():
+ return os.path.dirname(os.path.abspath(__file__))
+
+
+def copy_files_to_dist(target_dir):
+ file_list = [
+ "out/sdk/clang_x64/thirdparty/icu/libhmicui18n.so",
+ "out/sdk/clang_x64/thirdparty/icu/libhmicuuc.so",
+ "out/sdk/clang_x64/arkcompiler/ets_runtime/libprofDumpJson.so",
+ "out/sdk/mingw_x86_64/thirdparty/icu/libhmicui18n.dll",
+ "out/sdk/mingw_x86_64/thirdparty/icu/libhmicuuc.dll",
+ "out/sdk/mingw_x86_64/arkcompiler/ets_runtime/libprofDumpJson.dll",
+ "out/sdk/mingw_x86_64/hiviewdfx/hilog/libhilog.dll",
+ "out/sdk/mingw_x86_64/thirdparty/bounds_checking_function/libsec_shared.dll",
+ "out/sdk/mingw_x86_64/thirdparty/zlib/libshared_libz.dll"
+ ]
+ current_path = get_script_directory()
+ dist_dir = os.path.join(current_path, "dist")
+ for relative_file_path in file_list:
+ absolute_file_path = os.path.join(target_dir, relative_file_path)
+ if os.path.exists(absolute_file_path):
+ print(f'Copied {absolute_file_path} to {dist_dir}')
+ shutil.copy(absolute_file_path, dist_dir)
+ else:
+ print(f"File not found: {absolute_file_path}")
+
+
+def run_command(command):
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ for line in process.stdout:
+ print(line.decode('utf-8').strip())
+ process.wait()
+ if process.returncode != 0:
+ raise Exception(f"Command failed with return code {process.returncode}")
+
+
+def main(target_dir):
+ original_dir = get_script_directory()
+ try:
+ command = ["npm", "install"]
+ print(f"Running command: {command}")
+ run_command(command)
+ os.chdir(target_dir)
+ print(f"Changed directory to: {os.getcwd()}")
+ command = ["/bin/bash", "build.sh", "--product-name", "ohos-sdk", "--build-target",
+ "ap_viewer_all_host_tools_packages"]
+ print(f"Running command: {command}")
+ run_command(command)
+ os.chdir(original_dir)
+ print(f"Returned to original directory: {os.getcwd()}")
+ npm_command = ["npm", "run", "build"]
+ print(f"Running command: {npm_command}")
+ run_command(npm_command)
+ copy_files_to_dist(target_dir)
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ finally:
+ os.chdir(original_dir)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Build script with optional target directory.")
+ parser.add_argument("--target-dir", metavar="dir", type=str,
+ help="The target directory to cd into before running the build script.")
+ args = parser.parse_args()
+ target_directory = args.target_dir if args.target_dir else "../../../../"
+ main(target_directory)
diff --git a/tools/ap_file_viewer/docs/DEVELOP_zh.md b/tools/ap_file_viewer/docs/DEVELOP_zh.md
new file mode 100755
index 0000000000..94976714eb
--- /dev/null
+++ b/tools/ap_file_viewer/docs/DEVELOP_zh.md
@@ -0,0 +1,42 @@
+# AP可视化工具开发说明
+
+若当前工具功能不满足开发者需求,开发者需增强工具能力,则可基于已有源码进行工具二次开发,编译打包生成自定义的工具。
+
+## 使用场景
+
+1. 编译打包发布工具
+
+2. 开发者需增强工具能力,进行二次开发
+
+## 编译步骤:
+
+#### 编译:
+
+在ap_file_viewer 目录下执行
+
+```
+python build.py
+```
+
+生成dist目录,dist目录中内容如下:
+
+![](../figures/dist.png)
+
+#### 运行
+
+在dist目录内运行python apDumpServer.py启动web服务,如下图所示:
+
+![](../figures/start_web.png)
+
+打开chrome浏览器,访问http://{ip}:9001/ap/,如下图所示:
+
+![](../figures/main.png)
+
+#### 开发
+
+```
+项目整体为BS架构:
+ native为 C++ 代码,提供将ap文件转换为json的接口。
+ server为 python 代码, 提供http接口,主要有:文件上传接口和静态文件访问接口
+ src 是前端js代码, 提供json数据渲染
+```
diff --git a/tools/ap_file_viewer/docs/INSTRUCTION_zh.md b/tools/ap_file_viewer/docs/INSTRUCTION_zh.md
new file mode 100755
index 0000000000..570d6988ca
--- /dev/null
+++ b/tools/ap_file_viewer/docs/INSTRUCTION_zh.md
@@ -0,0 +1,21 @@
+# AP可视化工具使用说明
+
+## 简介
+
+Ap文件可视化工具根据ark_js_vm生成的ap文件实现可视化分析。
+
+### 使用说明
+
+运行ark_js_vm,采集PGO信息,生成的PGO信息落盘在ap文件。
+
+根据开发说明打包工具,并运行起来:
+
+[开发说明](./DEVELOP_zh.md)
+
+将ap文件拖入浏览器窗口区域,等待解析完成,界面功能如下图所示:
+
+![](../figures/img.png)
+
+## 注意
+
+暂无
diff --git a/tools/ap_file_viewer/figures/dist.png b/tools/ap_file_viewer/figures/dist.png
new file mode 100755
index 0000000000..daf03dc94e
Binary files /dev/null and b/tools/ap_file_viewer/figures/dist.png differ
diff --git a/tools/ap_file_viewer/figures/img.png b/tools/ap_file_viewer/figures/img.png
new file mode 100755
index 0000000000..64a52e2f83
Binary files /dev/null and b/tools/ap_file_viewer/figures/img.png differ
diff --git a/tools/ap_file_viewer/figures/main.png b/tools/ap_file_viewer/figures/main.png
new file mode 100755
index 0000000000..dc098d2e81
Binary files /dev/null and b/tools/ap_file_viewer/figures/main.png differ
diff --git a/tools/ap_file_viewer/figures/start_web.png b/tools/ap_file_viewer/figures/start_web.png
new file mode 100755
index 0000000000..0524f3aeaf
Binary files /dev/null and b/tools/ap_file_viewer/figures/start_web.png differ
diff --git a/tools/ap_file_viewer/package.json b/tools/ap_file_viewer/package.json
new file mode 100755
index 0000000000..0f51f9935a
--- /dev/null
+++ b/tools/ap_file_viewer/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "ApDump",
+ "version": "1.0.0",
+ "description": "Ap Parse",
+ "main": "main.js",
+ "scripts": {
+ "build": "npx webpack"
+ },
+ "repository": {
+ "type": "git",
+ "url": ""
+ },
+ "author": "",
+ "license": "Apache License",
+ "devDependencies": {
+ "webpack": "^5.64.4",
+ "webpack-cli": "^4.9.1"
+ }
+}
diff --git a/tools/ap_file_viewer/server/apdump_server.py b/tools/ap_file_viewer/server/apdump_server.py
new file mode 100755
index 0000000000..0218315bcf
--- /dev/null
+++ b/tools/ap_file_viewer/server/apdump_server.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2024 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.
+
+import argparse
+import cgi
+import ctypes
+import http
+import json
+import os
+import re
+import signal
+import sys
+import threading
+import time
+import webbrowser
+from datetime import datetime
+from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
+from pathlib import Path
+from urllib.parse import urlparse
+
+VERSION = 'v1.0.0'
+LIB_NAME = 'libprofDumpJson'
+current = os.path.dirname(os.path.abspath(__file__))
+libs_directory = current
+dependencies = ['hmicuuc', 'hmicui18n']
+keep_running = True
+httpd = None
+
+
+def is_windows():
+ return os.name == 'nt'
+
+
+def is_linux():
+ return os.name == 'posix'
+
+
+def load_dependencies(load_dependencies_lib, lib_directory):
+ for dep in load_dependencies_lib:
+ dep_path = f"{lib_directory}/lib{dep}.so"
+ try:
+ ctypes.CDLL(dep_path)
+ except OSError as e:
+ raise OSError(f"Failed to load dependency {dep_path}: {e}")
+
+
+def load_library_os_compatible(load_lib_name, lib_directory=None, lib_dependencies=None):
+ if lib_directory is None:
+ lib_directory = Path.cwd()
+
+ if is_windows():
+ lib_extension = '.dll'
+ load_lib_path = f"{lib_directory}/{load_lib_name}{lib_extension}"
+ load_lib = ctypes.CDLL(str(load_lib_path))
+ else:
+ lib_extension = '.so'
+ load_lib_path = f"{lib_directory}/{load_lib_name}{lib_extension}"
+ if lib_dependencies:
+ load_dependencies(lib_dependencies, lib_directory)
+ try:
+ load_lib = ctypes.CDLL(str(load_lib_path))
+ except OSError as e:
+ raise OSError(f"Failed to load {load_lib_path}: {e}")
+
+ if load_lib is None:
+ raise ImportError(f"Could not load library {load_lib_name}.")
+
+ return load_lib
+
+
+lib = load_library_os_compatible(LIB_NAME, libs_directory, dependencies)
+ConvertApToJson = lib.ConvertApToJson
+ConvertApToJson.argtypes = [ctypes.c_char_p, ctypes.c_size_t]
+ConvertApToJson.restype = ctypes.c_size_t
+GetConvertResult = lib.GetConvertResult
+GetConvertResult.argtypes = [ctypes.c_char_p, ctypes.c_size_t]
+GetConvertResult.restype = ctypes.c_bool
+
+
+def get_content_type(file_extension):
+ if file_extension == '.js':
+ return 'application/javascript'
+ if file_extension == '.wasm':
+ return 'application/wasm'
+ if file_extension == '.json':
+ return 'application/json'
+ if file_extension == '.html':
+ return 'text/html'
+ if file_extension == '.svg':
+ return 'image/svg+xml'
+ return 'text/plain'
+
+
+def parse_ap_file(file_path):
+ encoded_path = file_path.encode('utf-8')
+ path_length = len(encoded_path)
+ written_size = ConvertApToJson(encoded_path, path_length)
+ if written_size > 0:
+ buffer = ctypes.create_string_buffer(int(written_size))
+ GetConvertResult(buffer, written_size)
+ json_result = buffer[:written_size].decode('utf-8')
+ return json_result
+ else:
+ return None
+
+
+def is_subpath(parent_path, child_path):
+ try:
+ relative_path = os.path.relpath(child_path, parent_path)
+ return relative_path != os.pardir
+ except ValueError:
+ return False
+
+
+def open_web(url):
+ webbrowser.open(url)
+
+
+class SafeFileHandler:
+ def __init__(self):
+ self.lock = threading.Lock()
+
+ def parse_ap_file_safely(self, file_path):
+ with self.lock:
+ return parse_ap_file(file_path)
+
+
+safe_handler = SafeFileHandler()
+
+
+class ThreadedHTTPServer(ThreadingHTTPServer):
+ daemon_threads = True
+
+
+class ApRequestHandler(SimpleHTTPRequestHandler):
+ global VERSION
+
+ def log_message(self, formate, *arg):
+ return
+
+ def simple_secure_filename(self, filename):
+ ascii_filename = filename.encode('ascii', errors='ignore').decode('ascii')
+ safe_chars = re.compile(r'[^\w\.\- ]')
+ safe_filename = safe_chars.sub('_', ascii_filename)
+ safe_filename = safe_filename.lstrip('.')
+ if not safe_filename:
+ return "index.html"
+ return safe_filename
+
+ def do_GET(self):
+ parse_result = urlparse(self.path)
+ if parse_result.path.startswith('/ap'):
+ self.application_handler(parse_result)
+ else:
+ self.send_error(http.HTTPStatus.NOT_FOUND, 'Not found')
+
+ def upload_handler(self):
+ form = cgi.FieldStorage(
+ fp=self.rfile,
+ headers=self.headers,
+ environ={
+ 'REQUEST_METHOD': 'POST',
+ 'CONTENT_TYPE': self.headers['Content-Type'],
+ }
+ )
+ if 'file' in form:
+ file_item = form['file']
+ filename = self.simple_secure_filename(file_item.filename)
+ save_path = os.path.join(current, 'uploads', datetime.now().strftime('%Y%m%d%H%M%S%f'))
+ file_path = os.path.join(save_path, filename)
+ if not os.path.exists(save_path):
+ os.makedirs(save_path, mode=0o755)
+ fd = os.open(file_path, os.O_WRONLY | os.O_CREAT, 0o644)
+ with os.fdopen(fd, 'wb') as f:
+ f.write(file_item.file.read())
+ ap_res = safe_handler.parse_ap_file_safely(file_path)
+ os.remove(file_path)
+ dir_path = os.path.dirname(file_path)
+ if not os.listdir(dir_path):
+ os.rmdir(dir_path)
+ self.send_response(http.HTTPStatus.OK)
+ self.send_header('Content-Type', 'application/json')
+ self.end_headers()
+ if ap_res is None:
+ response = {"success": False, "code": -1, "message": "parse ap failed", "data": ""}
+ else:
+ response = {"success": True, "code": 0, "message": "success", "data": ap_res}
+ self.wfile.write(bytes(json.dumps(response), "utf-8"))
+ else:
+ self.send_error(http.HTTPStatus.BAD_REQUEST, 'Bad request')
+
+ def do_POST(self):
+ parse_result = urlparse(self.path)
+ if parse_result.path.startswith('/ap/upload'):
+ self.upload_handler()
+ else:
+ self.send_error(http.HTTPStatus.NOT_FOUND, 'Not found')
+
+ def application_handler(self, parse_result):
+ file_path = parse_result.path[3:]
+ file_extension = os.path.splitext(file_path)[1]
+ safe_path = os.path.normpath(file_path).lstrip('/')
+ if is_windows():
+ safe_path = os.path.normpath(file_path).lstrip("\\")
+ full_path = os.path.join(current, safe_path)
+ if file_path == '' or file_path == '/' or file_path is None or safe_path.strip() == ".":
+ full_path = os.path.join(current, "index.html")
+ file_extension = '.html'
+ elif not is_subpath(current, full_path):
+ self.send_error(http.HTTPStatus.NOT_FOUND, 'Not found')
+ return
+ try:
+ with open(full_path, 'rb') as file:
+ content = file.read()
+ self.send_response(http.HTTPStatus.OK)
+ self.send_header('Content-type', get_content_type(file_extension))
+ self.send_header("Cross-Origin-Opener-Policy", "unsafe-none")
+ self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
+ self.send_header("Access-Control-Allow-Origin", "*")
+ self.send_header("Access-Control-Allow-Credentials", "true")
+ self.send_header("Access-Control-Allow-Headers", "x-requested-with, authorization, blade-auth")
+ self.send_header("Access-Control-Allow-Methods", "*")
+ self.send_header("Access-Control-Max-Age", "3600")
+ self.send_header("data-version", VERSION)
+ self.end_headers()
+ self.wfile.write(content)
+ except FileNotFoundError:
+ self.send_error(http.HTTPStatus.NOT_FOUND, 'File not found')
+ except Exception as e:
+ self.log_message("ERROR", f"Error handling GET request: {str(e)}")
+ self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, 'Internal Server Error')
+
+
+def sigint_handler(sig, frame):
+ print("\nServer stopped by user")
+ global keep_running
+ keep_running = False
+ httpd.shutdown()
+ sys.exit(0)
+
+
+def main(port):
+ global httpd, keep_running
+ server_address = ('', port)
+ httpd = ThreadedHTTPServer(server_address, ApRequestHandler)
+ print(f'Starting http server on port {port}...')
+ thread = threading.Thread(target=httpd.serve_forever, daemon=True)
+ thread.start()
+ if is_windows():
+ open_web(f'http:127.0.0.1:{args.port}/ap/')
+ while keep_running:
+ time.sleep(1)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description="Run a ap dump HTTP server.")
+ parser.add_argument("-p", "--port", type=int, default=9001, help="Specify the server port.")
+ args = parser.parse_args()
+ signal.signal(signal.SIGINT, sigint_handler)
+ main(args.port)
diff --git a/tools/ap_file_viewer/server/config.json b/tools/ap_file_viewer/server/config.json
new file mode 100755
index 0000000000..0e5dccefb5
--- /dev/null
+++ b/tools/ap_file_viewer/server/config.json
@@ -0,0 +1,32 @@
+{
+ "filterKeys": [
+ "moduleName",
+ "funcName",
+ "type",
+ "isRoot",
+ "apKind",
+ "apAbcId",
+ "apId",
+ "offset"
+ ],
+ "summaryTreeLevel": [
+ "ModuleName",
+ "->FunctionName",
+ "->Offset",
+ "->Type"
+ ],
+ "apTableTr": [
+ {
+ "is_root_column_td": "isRoot"
+ },
+ {
+ "kind_column_td": "apKind"
+ },
+ {
+ "abc_column_td": "apAbcId"
+ },
+ {
+ "typeid_column_td": "apId"
+ }
+ ]
+}
diff --git a/tools/ap_file_viewer/src/ap/ApApplication.js b/tools/ap_file_viewer/src/ap/ApApplication.js
new file mode 100755
index 0000000000..c8c6ba1126
--- /dev/null
+++ b/tools/ap_file_viewer/src/ap/ApApplication.js
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+import '../base-ui/menu/MainMenu.js';
+import '../base-ui/icon/LitIcon.js';
+import '../base-ui/progress-bar/ProgressBar.js';
+import './component/search/Search.js';
+import './component/ApWelcome.js';
+import {applicationHtml,} from './ApApplicationPublicFunc.js';
+import './component/sheet/TabPaneApSummary.js';
+import {TabPaneApSummary} from './component/sheet/TabPaneApSummary.js';
+
+export class ApApplication extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
+ this.initElements();
+ }
+
+ set search(search) {
+ if (search) {
+ this.setAttribute('search', '');
+ } else {
+ this.removeAttribute('search');
+ }
+ }
+
+ get search() {
+ return this.hasAttribute('search');
+ }
+
+ initHtml() {
+ return applicationHtml;
+ }
+
+ initElements() {
+ this.rootEL = this.shadowRoot.querySelector('.root');
+ this.headerDiv = this.shadowRoot.querySelector('.search-vessel');
+ this.apWelcome = this.shadowRoot.querySelector('#ap-welcome');
+ this.apTreeTable = this.shadowRoot.querySelector('#tabpane-summary');
+ this.mainMenu = this.shadowRoot?.querySelector('#main-menu');
+ this.menu = this.mainMenu.shadowRoot?.querySelector('.menu-button');
+ this.progressEL = this.shadowRoot?.querySelector('.progress');
+ this.litSearch = this.shadowRoot?.querySelector('#lit-search');
+ this.initElementsAttr();
+ this.initSearchEvents();
+ this.resetMenus();
+ this.initGlobalEvents();
+ this.initSearchChangeEvents();
+ }
+
+ initElementsAttr() {
+ this.mainMenu.setAttribute('main_menu', '1');
+ this.childComponent = [
+ this.apTreeTable,
+ this.apWelcome,
+ ];
+ }
+
+ openTraceFile(file) {
+ if (file === null) {
+ return;
+ }
+ this.litSearch.style.marginLeft = '0px';
+ this.openFileInit();
+ this.parseApFile(file);
+ }
+
+ initGlobalDropEvents() {
+ let body = document.querySelector('body');
+ body.addEventListener('drop', (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (this.rootEL.classList.contains('filedrag')) {
+ this.rootEL.classList.remove('filedrag');
+ }
+ if (event && event.dataTransfer && event.dataTransfer.items &&
+ event.dataTransfer.items.length > 0) {
+ let item = event.dataTransfer.items[0];
+ const entry = item.webkitGetAsEntry();
+ if (entry?.isFile) {
+ this.openTraceFile(item.getAsFile());
+ } else if (entry?.isDirectory) {
+ this.litSearch.setPercent('This File is not supported!', -1);
+ this.progressEL.loading = false;
+ this.freshMenuDisable(false);
+ }
+ }
+ }, false);
+ }
+
+ initGlobalEvents() {
+ let body = document.querySelector('body');
+ body.addEventListener('dragover', (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (event && event.dataTransfer && event.dataTransfer.items.length > 0 &&
+ event.dataTransfer.items[0].kind === 'file') {
+ event.dataTransfer.dropEffect = 'copy';
+ if (!this.rootEL.classList.contains('filedrag')) {
+ this.rootEL.classList.add('filedrag');
+ }
+ }
+ }, false);
+ body.addEventListener('dragleave', (event) => {
+ event.stopPropagation();
+ event.preventDefault();
+ if (this.rootEL.classList.contains('filedrag')) {
+ this.rootEL.classList.remove('filedrag');
+ }
+ }, false);
+ this.initGlobalDropEvents();
+ }
+
+ parseApFile(file) {
+ let fileName = file.name;
+ let showFileName = fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.'));
+ this.litSearch.setPercent('', 1);
+ let fileSizeInKB = (file.size / 1024).toFixed(1);
+ document.title = `${showFileName} (${fileSizeInKB}KB)`;
+ if (!fileName.endsWith('.ap')) {
+ this.litSearch.setPercent('This File is not supported!', -1);
+ this.progressEL.loading = false;
+ this.freshMenuDisable(false);
+ } else {
+ try {
+ this.uploadFile(file).then((value) => {
+ this.litSearch.setPercent('', 101);
+ this.litSearch.style.pointerEvents = 'auto';
+ this.progressEL.loading = false;
+ this.freshMenuDisable(false);
+ this.apTreeTable.data = TabPaneApSummary.TransformJson(value);
+ });
+ } catch (httpError) {
+ this.litSearch.setPercent('Parse Filed Please Wait', -1);
+ this.progressEL.loading = false;
+ this.freshMenuDisable(false);
+ }
+ }
+ }
+
+ async uploadFile(file) {
+ const formData = new FormData();
+ formData.append('file', file, file.name);
+ const response = await fetch(`${window.location.origin}/ap/upload`, {
+ method: 'POST',
+ body: formData,
+ });
+ if (!response.ok) {
+ throw new Error(`file Upload Failed,status:${response.status}`);
+ }
+ return await response.text();
+ }
+
+ initSearchEvents() {
+ this.litSearch.addEventListener('focus', () => {
+ });
+ this.litSearch.addEventListener('ap-change', () => {
+ });
+ this.initSearchChangeEvents();
+ }
+
+ resetMenus() {
+ this.mainMenu.menus = [
+ {
+ collapsed: false,
+ title: 'Navigation',
+ second: false,
+ icon: 'caret-down',
+ describe: 'Import ap file',
+ children: [
+ {
+ title: 'Import ap file',
+ icon: 'folder',
+ fileChoose: true,
+ fileHandler: (ev) => {
+ this.openTraceFile(ev.detail);
+ },
+ }
+ ],
+ }
+ ];
+ }
+
+ openFileInit() {
+ this.litSearch.clear();
+ this.headerDiv.style.pointerEvents = 'none';
+ this.resetMenus();
+ this.freshMenuDisable(true);
+ this.showContent(this.apTreeTable);
+ this.progressEL.loading = true;
+ }
+
+ initSearchChangeEvents() {
+ let timer;
+ this.litSearch.valueChangeHandler = (value) => {
+ this.litSearch.list = [];
+ if (timer) {
+ clearTimeout(timer);
+ }
+ timer = setTimeout(() => {
+ this.litSearch.isClearValue = false;
+ if (value.length > 0) {
+ this.apTreeTable.filterData(value.toString().toLowerCase());
+ } else {
+ this.litSearch.list = [];
+ this.apTreeTable.filterData('');
+ }
+ this.apTreeTable.expansionAll();
+ }, 500);
+ };
+ }
+
+ showContent(showNode) {
+ if (showNode === this.apTreeTable) {
+ this.menu.style.pointerEvents = 'auto';
+ this.search = true;
+ this.litSearch.style.display = 'block';
+ } else {
+ this.menu.style.pointerEvents = 'none';
+ this.search = this.litSearch.isLoading;
+ if (!this.search) {
+ this.litSearch.style.display = 'none';
+ }
+ }
+ this.childComponent.forEach((node) => {
+ if (node === showNode) {
+ showNode.style.visibility = 'visible';
+ } else {
+ node.style.visibility = 'hidden';
+ }
+ });
+ }
+
+
+ freshMenuDisable(disable) {
+ let menus;
+ this.mainMenu.menus[0].children[0].disabled = disable;
+ if (this.mainMenu.menus.length > 2) {
+ (menus = this.mainMenu.menus) === null || menus === void 0 ? void 0 : menus[1].children.map((it) => (it.disabled = disable));
+ }
+ }
+}
+
+if (!customElements.get('ap-application')) {
+ customElements.define('ap-application', ApApplication);
+}
diff --git a/tools/ap_file_viewer/src/ap/ApApplicationPublicFunc.js b/tools/ap_file_viewer/src/ap/ApApplicationPublicFunc.js
new file mode 100755
index 0000000000..c0b1abb263
--- /dev/null
+++ b/tools/ap_file_viewer/src/ap/ApApplicationPublicFunc.js
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+export const applicationHtml = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
diff --git a/tools/ap_file_viewer/src/ap/component/ApWelcome.js b/tools/ap_file_viewer/src/ap/component/ApWelcome.js
new file mode 100755
index 0000000000..8ee775028f
--- /dev/null
+++ b/tools/ap_file_viewer/src/ap/component/ApWelcome.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+export class ApWelcome extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' }).innerHTML = this.initHtml();
+ }
+ initHtml() {
+ return `
+
+
+
+
+ `;
+ }
+}
+if (!customElements.get('ap-welcome')) {
+ customElements.define('ap-welcome', ApWelcome);
+}
diff --git a/tools/ap_file_viewer/src/ap/component/search/Search.html.js b/tools/ap_file_viewer/src/ap/component/search/Search.html.js
new file mode 100755
index 0000000000..d748ca7843
--- /dev/null
+++ b/tools/ap_file_viewer/src/ap/component/search/Search.html.js
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+export const SearchHtml = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
diff --git a/tools/ap_file_viewer/src/ap/component/search/Search.js b/tools/ap_file_viewer/src/ap/component/search/Search.js
new file mode 100755
index 0000000000..7f889a4927
--- /dev/null
+++ b/tools/ap_file_viewer/src/ap/component/search/Search.js
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+import {SearchHtml} from './Search.html.js';
+
+const LOCAL_STORAGE_SEARCH_KEY = 'search_key';
+
+export class LitSearch extends HTMLElement {
+
+
+ constructor() {
+ super();
+ this._index = 0;
+ this._list = [];
+ this._value = false;
+ this.historyMaxCount = 100;
+ this.searchList = [];
+ this.searchELList = [];
+ this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
+ this.initElements();
+ }
+
+ get list() {
+ return this._list;
+ }
+
+ set list(value) {
+ this._list = value;
+ }
+
+ get index() {
+ return this._index;
+ }
+
+ set index(value) {
+ this._index = value;
+ }
+
+ get searchValue() {
+ return this.search?.value ?? '';
+ }
+
+ get isLoading() {
+ return this.hasAttribute('isLoading');
+ }
+
+ set isLoading(va) {
+ if (va) {
+ this.setAttribute('isLoading', '');
+ } else {
+ this.removeAttribute('isLoading');
+ window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, '');
+ }
+ }
+
+ set isClearValue(value) {
+ this._value = value;
+ }
+
+ get isClearValue() {
+ return this._value;
+ }
+
+ valueChangeHandler = () => {
+ };
+
+ setPercent(name = '', value) {
+ let searchHide = this.shadowRoot.querySelector('.root');
+ let searchIcon = this.shadowRoot.querySelector('#search-icon');
+ if (this.hasAttribute('textRoll')) {
+ this.removeAttribute('textRoll');
+ }
+ this.isLoading = false;
+ if (value > 0 && value <= 100) {
+ searchHide.style.display = 'flex';
+ searchHide.style.backgroundColor = '#e3e3e3';
+ searchIcon === null || searchIcon === void 0 ? void 0 : searchIcon.setAttribute('name', 'cloud-sync');
+ this.search.setAttribute('placeholder', `${name}${value}%`);
+ this.search.setAttribute('readonly', '');
+ this.search.className = 'readonly';
+ this.isLoading = true;
+ } else if (value > 100) {
+ searchHide.style.display = 'flex';
+ searchHide.style.backgroundColor = '#fff';
+ searchIcon?.setAttribute('name', 'search');
+ this.search?.setAttribute('placeholder', 'search');
+ this.search?.removeAttribute('readonly');
+ this.search.className = 'write';
+ } else if (value === -1) {
+ searchHide.style.display = 'flex';
+ searchHide.style.backgroundColor = '#e3e3e3';
+ searchIcon === null || searchIcon === void 0 ? void 0 : searchIcon.setAttribute('name', 'cloud-sync');
+ this.search.setAttribute('placeholder', `${name}`);
+ this.search.setAttribute('readonly', '');
+ this.search.className = 'readonly';
+ } else {
+ searchHide.style.display = 'none';
+ }
+ }
+
+ clear() {
+ this.search = this.shadowRoot.querySelector('input');
+ this.search.value = '';
+ this.list = [];
+ }
+
+ blur() {
+ this.search?.blur();
+ }
+
+ updateSearchList(searchStr) {
+ if (searchStr === null || searchStr.length === 0 || searchStr.trim().length === 0) {
+ return;
+ }
+ let searchInfo = this.searchList.find((searchInfo) => searchInfo.searchContent === searchStr);
+ if (searchInfo !== undefined) {
+ let index = this.searchList.indexOf(searchInfo);
+ this.searchList.splice(index, 1);
+ this.searchList.unshift({searchContent: searchStr, useCount: 1});
+ } else {
+ this.searchList.unshift({searchContent: searchStr, useCount: 1});
+ }
+ }
+
+ getSearchHistory() {
+ let searchString = window.localStorage.getItem(LOCAL_STORAGE_SEARCH_KEY);
+ if (searchString) {
+ let searHistory = JSON.parse(searchString);
+ if (Array.isArray(searHistory)) {
+ this.searchList = searHistory;
+ return searHistory;
+ }
+ }
+ return [];
+ }
+
+ searchFocusListener() {
+ }
+
+ searchBlurListener() {
+ this.dispatchEvent(new CustomEvent('blur', {
+ detail: {
+ value: this.search.value,
+ },
+ }));
+ }
+
+ searchKeyupListener(e) {
+ if (e.code === 'Enter' || e.code === 'NumpadEnter') {
+ this.updateSearchList(this.search.value);
+ } else {
+ this.updateSearchHistoryList(this.search.value);
+ this.valueChangeHandler?.call(this, this.trimSideSpace(this.search.value));
+ }
+ e.stopPropagation();
+ }
+
+ trimSideSpace(str) {
+ return str.replace(/(^\s*)|(\s*$)/g, '');
+ }
+
+ initElements() {
+ this.search = this.shadowRoot.querySelector('input');
+ this.searchHistoryListEL = this.shadowRoot.querySelector('.search-history-list');
+ this.retargetIndex = this.shadowRoot.querySelector('input[name="retarge_index"]');
+ this.search.addEventListener('focus', () => {
+ });
+ this.search.addEventListener('blur', () => {
+ this.searchBlurListener();
+ });
+ this.search.addEventListener('change', () => {
+ this.index = -1;
+ this.retargetIndex.value = '';
+ });
+ this.search.addEventListener('keyup', (e) => {
+ this.retargetIndex.value = '';
+ this.index = -1;
+ this.searchKeyupListener(e);
+ });
+ (this.shadowRoot?.querySelector('input[name="retarget_index"]'))?.addEventListener('keydown', (event) => {
+ if (event.key === 'Enter') {
+ event.stopPropagation();
+ }
+ });
+ }
+
+ initHtml() {
+ return SearchHtml;
+ }
+
+ hideSearchHistoryList() {
+ this.searchHistoryListEL.style.display = 'none';
+ if (this.searchList.length > this.historyMaxCount) {
+ this.searchList = this.searchList.slice(0, this.historyMaxCount);
+ }
+ if (this.searchList.length === 0) {
+ return;
+ }
+ let historyStr = JSON.stringify(this.searchList);
+ window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, historyStr);
+ this.searchList = [];
+ this.searchELList = [];
+ }
+
+ updateSearchHistoryList(searchValue) {
+ const keyword = searchValue.toLowerCase();
+ this.searchELList.forEach((item) => {
+ if (item.textContent.toLowerCase().includes(keyword)) {
+ item.style.display = 'block';
+ } else {
+ item.style.display = 'none';
+ }
+ });
+ }
+}
+
+if (!customElements.get('lit-search')) {
+ customElements.define('lit-search', LitSearch);
+}
diff --git a/tools/ap_file_viewer/src/ap/component/sheet/TabPaneApSummary.html.js b/tools/ap_file_viewer/src/ap/component/sheet/TabPaneApSummary.html.js
new file mode 100755
index 0000000000..ca3b1f827e
--- /dev/null
+++ b/tools/ap_file_viewer/src/ap/component/sheet/TabPaneApSummary.html.js
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+export const TabPaneApSummaryHtml = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
diff --git a/tools/ap_file_viewer/src/ap/component/sheet/TabPaneApSummary.js b/tools/ap_file_viewer/src/ap/component/sheet/TabPaneApSummary.js
new file mode 100755
index 0000000000..a44ebe9d3a
--- /dev/null
+++ b/tools/ap_file_viewer/src/ap/component/sheet/TabPaneApSummary.js
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+import {TabPaneApSummaryHtml} from './TabPaneApSummary.html.js';
+
+export class TabPaneApSummary extends HTMLElement {
+ static jsonData = null;
+ constructor() {
+ super();
+ this.apDataSource = [];
+ this.apData = [];
+ this.apTreeNodes = [];
+ this.expandedNodeList = new Set();
+ this.selectTreeDepth = 0;
+ fetch('/ap/config.json')
+ .then(response => response.json())
+ .then(data => {
+ TabPaneApSummary.jsonData = data;
+ this.expansionClickEvent = () => {
+ let apItem;
+ this.expandedNodeList.clear();
+ if (((apItem = this.expansionUpIcon) === null || apItem === void 0 ? void 0 : apItem.name) === 'down') {
+ this.selectTreeDepth = 0;
+ this.expansionUpIcon.name = 'up';
+ this.expansionDownIcon.name = 'down';
+ } else {
+ this.selectTreeDepth = TabPaneApSummary.jsonData.summaryTreeLevel.length;
+ this.expansionUpIcon.name = 'down';
+ this.expansionDownIcon.name = 'up';
+ }
+ this.refreshSelectDepth(this.apTreeNodes);
+ this.refreshRowNodeTable(true);
+ };
+ this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
+ this.initElements();
+ });
+ }
+
+ set data(apDatas) {
+ this.apDataSource = [];
+ this.expandedNodeList.clear();
+ this.expansionUpIcon.name = 'up';
+ this.expansionDownIcon.name = 'down';
+ this.apSummaryTable.innerHTML = '';
+ this.apDataSource = apDatas;
+ this.apData = this.apDataSource;
+ if (this.apData.length !== 0) {
+ this.refreshRowNodeTable();
+ }
+ }
+
+ filterData(filter) {
+ const keysOfApBean = TabPaneApSummary.jsonData.filterKeys;
+ if (filter === '') {
+ this.apData = this.apDataSource;
+ } else {
+ this.apData = this.apDataSource.filter((item) => {
+ return keysOfApBean.some(field => {
+ const value = item[field];
+ return value !== undefined && value !== null && value.toString().toLowerCase().includes(filter);
+ });
+ });
+ }
+ this.expandedNodeList.clear();
+ this.expansionUpIcon.name = 'up';
+ this.expansionDownIcon.name = 'down';
+ this.apSummaryTable.innerHTML = '';
+ this.refreshRowNodeTable();
+ }
+
+ initElements() {
+ this.apSummaryTable = this.shadowRoot?.querySelector('#tab-summary');
+ this.expansionDiv = this.shadowRoot?.querySelector('.expansion-div');
+ this.expansionUpIcon = this.shadowRoot?.querySelector('.expansion-up-icon');
+ this.expansionDownIcon = this.shadowRoot?.querySelector('.expansion-down-icon');
+ let summaryTreeLevel = TabPaneApSummary.jsonData.summaryTreeLevel;
+ this.shadowRoot?.querySelectorAll('.head-label').forEach((summaryTreeHead) => {
+ summaryTreeHead.addEventListener('click', () => {
+ this.selectTreeDepth = summaryTreeLevel.indexOf(summaryTreeHead.textContent);
+ this.expandedNodeList.clear();
+ this.refreshSelectDepth(this.apTreeNodes);
+ this.refreshRowNodeTable(true);
+ });
+ });
+ this.apSummaryTable.onscroll = () => {
+ let apTreeTableEl = this.shadowRoot?.querySelector('.ap-tree-table');
+ if (apTreeTableEl) {
+ apTreeTableEl.scrollTop = (this.apSummaryTable?.scrollTop) || 0;
+ }
+ };
+ }
+
+ expansionAll() {
+ this.selectTreeDepth = TabPaneApSummary.jsonData.summaryTreeLevel.length - 1;
+ this.expandedNodeList.clear();
+ this.refreshSelectDepth(this.apTreeNodes);
+ this.refreshRowNodeTable(true);
+ }
+
+ initHtml() {
+ return TabPaneApSummaryHtml;
+ }
+
+ connectedCallback() {
+ new ResizeObserver(() => {
+ this.parentElement.style.overflow = 'hidden';
+ this.refreshRowNodeTable();
+ }).observe(this.parentElement);
+ this.expansionDiv?.addEventListener('click', this.expansionClickEvent);
+ }
+
+ disconnectedCallback() {
+ this.expansionDiv?.removeEventListener('click', this.expansionClickEvent);
+ }
+
+ refreshSelectDepth(apTreeNodes) {
+ apTreeNodes.forEach((item) => {
+ if (item.depth < this.selectTreeDepth) {
+ this.expandedNodeList.add(item.id);
+ if (item.children.length > 0) {
+ this.refreshSelectDepth(item.children);
+ }
+ }
+ });
+ }
+
+ createTr(rowNode, className, proper) {
+ let elementTr = document.createElement('tr');
+ let elementTd = document.createElement('td');
+ elementTr.title = rowNode[proper];
+ elementTd.textContent = rowNode[proper];
+ elementTd.className = className;
+ elementTr.appendChild(elementTd);
+ return elementTr;
+ }
+
+ createRowNodeTableEL(rowNodeList, tableTreeEl, tableRightDivs) {
+ let unitPadding = 20;
+ let leftPadding = 5;
+ rowNodeList.forEach((rowNode) => {
+ let tableTreeRowEl = document.createElement('tr');
+ tableTreeRowEl.className = 'tree-row-tr';
+ tableTreeRowEl.title = `${rowNode.apName}`;
+ let leftSpacingEl = document.createElement('td');
+ leftSpacingEl.style.paddingLeft = `${rowNode.depth * unitPadding + leftPadding}px`;
+ tableTreeRowEl.appendChild(leftSpacingEl);
+ this.addToggleIconEl(rowNode, tableTreeRowEl);
+ let rowNodeTextEL = document.createElement('td');
+ rowNodeTextEL.textContent = `${rowNode.apName}`;
+ rowNodeTextEL.className = 'row-name-td';
+ tableTreeRowEl.appendChild(rowNodeTextEL);
+ tableTreeEl.appendChild(tableTreeRowEl);
+ let apTrs = TabPaneApSummary.jsonData.apTableTr;
+ for (let index = 0; index < apTrs.length; index++) {
+ let item = apTrs[index];
+ Object.keys(item).forEach((key) => {
+ tableRightDivs[index].appendChild(this.createTr(rowNode, key, item[key]));
+ });
+ }
+ if (rowNode.children && this.expandedNodeList.has(rowNode.id)) {
+ this.createRowNodeTableEL(rowNode.children, tableTreeEl, tableRightDivs);
+ }
+ });
+ }
+
+ addToggleIconEl(rowNode, tableRowEl) {
+ let toggleIconEl = document.createElement('td');
+ let expandIcon = document.createElement('lit-icon');
+ expandIcon.classList.add('tree-icon');
+ if (rowNode.children && rowNode.children.length > 0) {
+ toggleIconEl.appendChild(expandIcon);
+ expandIcon.name = this.expandedNodeList.has(rowNode.id) ? 'minus-square' : 'plus-square';
+ toggleIconEl.classList.add('expand-icon');
+ toggleIconEl.addEventListener('click', () => {
+ let scrollTop = this.apSummaryTable?.scrollTop ?? 0;
+ this.changeNode(rowNode.id);
+ this.apSummaryTable.scrollTop = scrollTop;
+ let apTreeTableEl = this.shadowRoot?.querySelector('.ap-tree-table');
+ if (apTreeTableEl) {
+ apTreeTableEl.scrollTop = scrollTop;
+ }
+ });
+ }
+ tableRowEl.appendChild(toggleIconEl);
+ }
+
+ changeNode(currentNode) {
+ if (this.expandedNodeList.has(currentNode)) {
+ this.expandedNodeList.delete(currentNode);
+ } else {
+ this.expandedNodeList.add(currentNode);
+ }
+ this.refreshRowNodeTable();
+ }
+
+ refreshRowNodeTable(useCacheRefresh = false) {
+ if (this.apSummaryTable === undefined) {
+ return;
+ }
+ this.apSummaryTable.innerHTML = '';
+ if (this.apSummaryTable && this.parentElement) {
+ this.apSummaryTable.style.height = `${this.parentElement.clientHeight - 30}px`;
+ }
+ if (!useCacheRefresh) {
+ this.apTreeNodes = this.buildTreeTblNodes(this.apData);
+ }
+ let tableFragmentEl = document.createDocumentFragment();
+ let tableTreeEl = document.createElement('div');
+ tableTreeEl.className = 'ap-tree-table';
+ let apTrs = TabPaneApSummary.jsonData.apTableTr;
+ let tableRightDivs = [];
+ for (let index = 0; index < apTrs.length; index++) {
+ tableRightDivs.push(document.createElement('div'));
+ }
+ if (this.parentElement) {
+ tableTreeEl.style.height = `${this.parentElement.clientHeight - 40}px`;
+ }
+ this.createRowNodeTableEL(this.apTreeNodes, tableTreeEl, tableRightDivs);
+ let emptyTr = document.createElement('tr');
+ emptyTr.className = 'tree-row-tr';
+ tableTreeEl === null || tableTreeEl === void 0 ? void 0 : tableTreeEl.appendChild(emptyTr);
+ tableFragmentEl.appendChild(tableTreeEl);
+ for (let index = 0; index < tableRightDivs.length; index++) {
+ let emptyItemTr = document.createElement('tr');
+ emptyItemTr.className = 'tree-row-tr';
+ tableRightDivs[index].appendChild(emptyItemTr);
+ tableFragmentEl.appendChild(tableRightDivs[index]);
+ }
+ this.apSummaryTable.appendChild(tableFragmentEl);
+ }
+
+ buildTreeTblNodes(apTreeNodes) {
+ let id = 0;
+ let root = {
+ id: id,
+ depth: 0,
+ children: [],
+ apName: 'All',
+ type: '',
+ isRoot: '',
+ apKind: '',
+ apAbcId: '',
+ apId: ''
+ };
+ apTreeNodes.forEach((item) => {
+ id++;
+ let moduleNode = root.children.find((node) => node.apName === item.moduleName);
+ let result = this.buildItem(moduleNode, id, item, root);
+ id = result.id;
+ let offsetNode = result.offsetNode;
+ if (offsetNode.depth === 2) {
+ let typeNode;
+ id++;
+ typeNode = {
+ id: id, depth: 3, children: [], apName: item.type, type: '',
+ isRoot: `${item.isRoot}`,
+ apKind: item.apKind === '-' ? '-' : TabPaneApSummary.KindMap[item.apKind === undefined ?
+ 'UnknowId' : item.apKind],
+ apAbcId: `${item.apAbcId}`,
+ apId: `${item.apId}`
+ };
+ offsetNode.children.push(typeNode);
+ }
+ });
+ return root.children.sort((leftData, rightData) => {
+ return leftData.apName.localeCompare(rightData.apName);
+ });
+ }
+
+ buildItem(moduleNode, id, item, root) {
+ if (!moduleNode) {
+ id++;
+ moduleNode = {
+ id: id, depth: 0,
+ children: [],
+ apName: item.moduleName,
+ type: '',
+ isRoot: '-',
+ apKind: '-',
+ apAbcId: '-',
+ apId: '-'
+ };
+ root.children.push(moduleNode);
+ }
+ let funcNode = moduleNode.children.find((node) => node.apName === item.funcName);
+ if (!funcNode) {
+ id++;
+ funcNode = {
+ id: id, depth: 1,
+ children: [],
+ apName: item.funcName,
+ type: '',
+ isRoot: '-',
+ apKind: '-',
+ apAbcId: '-',
+ apId: '-'
+ };
+ moduleNode.children.push(funcNode);
+ }
+ if (item.offset === '-') {
+ return {id: id, offsetNode: funcNode};
+ }
+ let offsetNode = funcNode.children.find((node) => node.apName === item.offset);
+ if (!offsetNode) {
+ id++;
+ offsetNode = {
+ id: id, depth: 2,
+ children: [],
+ apName: item.offset,
+ type: '',
+ isRoot: '-',
+ apKind: '-',
+ apAbcId: '-',
+ apId: '-'
+ };
+ funcNode.children.push(offsetNode);
+ }
+ return {id, offsetNode};
+ }
+
+ static MakeApObj(moduleName, functionName, item) {
+ if (item === undefined) {
+ item = new TypeItem();
+ }
+ let abcId = this.abcFilePoolMap.get(String(item.abcId));
+ if (abcId === undefined) {
+ abcId = '-';
+ }
+ let transformedItem = {
+ moduleName: moduleName,
+ funcName: functionName,
+ offset: (item === null || item === void 0 ? void 0 : item.typeOffset) === undefined ? '-' : item.typeOffset,
+ type: (item === null || item === void 0 ? void 0 : item.typeName) === undefined ? '-' : item.typeName,
+ apKind: (item === null || item === void 0 ? void 0 : item.kind) === undefined ? '-' : item.kind,
+ apAbcId: (item === null || item === void 0 ? void 0 : item.abcId) === undefined ? '-' : abcId,
+ apId: (item === null || item === void 0 ? void 0 : item.id) === undefined ? '-' : item.id,
+ isRoot: (item === null || item === void 0 ? void 0 : item.isRoot) === undefined ? '-' : item.isRoot
+ };
+ return transformedItem;
+ }
+
+ static TransformJson(value) {
+ let apRes = JSON.parse(value);
+ let apData = JSON.parse(apRes.data);
+ let jsonData = apData.recordDetail;
+ let abcFilePool = apData.abcFilePool;
+ this.abcFilePoolMap = new Map(abcFilePool.map(item => {
+ let parts = item.abcFile.split(/\/|\\/);
+ let fileName = parts.length > 1 ? parts[parts.length - 1] : item.abcFile;
+ return [item.abcId, fileName];
+ }));
+ let result = [];
+ for (let moduleItem of jsonData) {
+ for (let functionItem of moduleItem.function) {
+ if (functionItem.type.length === 0) {
+ result.push(TabPaneApSummary.MakeApObj(moduleItem.moduleName, functionItem.functionName));
+ }
+ for (let typeItem of functionItem.type) {
+ if (Array.isArray(typeItem)) {
+ for (let typesItem of typeItem) {
+ if (Array.isArray(typesItem)) {
+ for (let item of typesItem) {
+ result.push(TabPaneApSummary.MakeApObj(moduleItem.moduleName, functionItem.functionName, item));
+ }
+ } else {
+ result.push(TabPaneApSummary.MakeApObj(moduleItem.moduleName, functionItem.functionName, typesItem));
+ }
+ }
+ } else {
+ result.push(TabPaneApSummary.MakeApObj(moduleItem.moduleName, functionItem.functionName, typeItem));
+ }
+ }
+ }
+ }
+ return result;
+ }
+}
+
+TabPaneApSummary.KindMap = {
+ '0': 'ClassId',
+ '1': 'LiteralId',
+ '2': 'BuiltinsId',
+ '3': 'LegacyKind',
+ '4': 'MethodId',
+ '5': 'BuiltinFunctionId',
+ '6': 'RecordClassId',
+ '7': 'PrototypeId',
+ '8': 'ConstructorId',
+ '9': 'MegaStateKinds',
+ '10': 'TotalKinds',
+ '11': 'UnknowId',
+ '12': 'GlobalsId'
+};
+TabPaneApSummary.abcFilePoolMap = new Map();
+if (!customElements.get('tab-ap-summary')) {
+ customElements.define('tab-ap-summary', TabPaneApSummary);
+}
+export class TypeItem {
+ constructor() {
+ this.typeName = '-';
+ this.typeOffset = '-';
+ this.isRoot = '-';
+ this.kind = '-';
+ this.abcId = '-';
+ this.id = '-';
+ }
+}
diff --git a/tools/ap_file_viewer/src/base-ui/icon.svg b/tools/ap_file_viewer/src/base-ui/icon.svg
new file mode 100755
index 0000000000..e17d0b850d
--- /dev/null
+++ b/tools/ap_file_viewer/src/base-ui/icon.svg
@@ -0,0 +1,90 @@
+
diff --git a/tools/ap_file_viewer/src/base-ui/icon/LitIcon.js b/tools/ap_file_viewer/src/base-ui/icon/LitIcon.js
new file mode 100755
index 0000000000..3fb4255019
--- /dev/null
+++ b/tools/ap_file_viewer/src/base-ui/icon/LitIcon.js
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+export class LitIcon extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' }).innerHTML = this.initHtml();
+ this.initElements();
+ }
+ static get observedAttributes() {
+ return ['name', 'size', 'color', 'path'];
+ }
+ get name() {
+ return this.getAttribute('name') || '';
+ }
+ set name(value) {
+ this.setAttribute('name', value);
+ }
+ get size() {
+ return parseInt(this.getAttribute('size') || '0', 10);
+ }
+ set size(value) {
+ this.setAttribute('size', `${value}`);
+ }
+ set color(value) {
+ this.setAttribute('color', value);
+ }
+ set path(value) {
+ this._path = value;
+ this.setAttribute('path', value);
+ }
+ initHtml() {
+ return `
+
+
+ `;
+ }
+ initElements() {
+ if (this.shadowRoot) {
+ this.icon = this.shadowRoot.getElementById('icon');
+ this.use = this.shadowRoot.querySelector('use');
+ this.apPath = this.shadowRoot.querySelector('path');
+ }
+ }
+ attributeChangedCallback(name, oldValue, value) {
+ switch (name) {
+ case 'name':
+ if (this.use) {
+ this.use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `./base-ui/icon.svg#icon-${value}`);
+ }
+ break;
+ case 'path':
+ if (this.apPath) {
+ this.apPath.setAttribute('apPath', value);
+ }
+ break;
+ case 'color':
+ if (this.icon) {
+ this.icon.style.color = value;
+ }
+ break;
+ case 'size':
+ if (this.icon) {
+ this.icon.style.fontSize = `${value}px`;
+ }
+ break;
+ }
+ }
+}
+if (!customElements.get('lit-icon')) {
+ customElements.define('lit-icon', LitIcon);
+}
diff --git a/tools/ap_file_viewer/src/base-ui/menu/MainMenu.js b/tools/ap_file_viewer/src/base-ui/menu/MainMenu.js
new file mode 100755
index 0000000000..3dd9966a2c
--- /dev/null
+++ b/tools/ap_file_viewer/src/base-ui/menu/MainMenu.js
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+import './MainMenuItem.js';
+import './MainMenuGroup.js';
+import { MainMenuGroup } from './MainMenuGroup.js';
+import { MainMenuItem } from './MainMenuItem.js';
+const initHtmlStyle = `
+
+ `;
+export class MainMenu extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' }).innerHTML = this.initHtml();
+ this.initElements();
+ }
+ get menus() {
+ return this._menus;
+ }
+ set menus(value) {
+ this._menus = value;
+ this.shadowRoot?.querySelectorAll('lit-main-menu-group').forEach((menuItem) => menuItem.remove());
+ let menuBody = this.shadowRoot?.querySelector('.menu-body');
+ if (this.getAttribute('main_menu') === '1' && window.localStorage.getItem('Theme') === 'dark') {
+ this.style.backgroundColor = '#262f3c';
+ }
+ else {
+ this.style.backgroundColor = '#fff';
+ }
+ value === null || value === void 0 ? void 0 : value.forEach((it) => {
+ let group = new MainMenuGroup();
+ group.setAttribute('title', it.title || '');
+ if (it.describe !== '') {
+ group.setAttribute('describe', it.describe || '');
+ }
+ else {
+ group.removeAttribute('describe');
+ }
+ group.setAttribute('icon', it.icon || '');
+ if (it.collapsed) {
+ group.setAttribute('collapsed', '');
+ }
+ else {
+ group.removeAttribute('collapsed');
+ }
+ let groupName = group.shadowRoot.querySelector('.group-name');
+ let groupDescribe = group.shadowRoot.querySelector('.group-describe');
+ menuBody === null || menuBody === void 0 ? void 0 : menuBody.appendChild(group);
+ it.children?.forEach((item) => {
+ if (item.fileModel !== undefined && item.fileModel === 'db') {
+ return;
+ }
+ this.notChildren(item, group, groupName, groupDescribe);
+ });
+ });
+ }
+ notChildren(item, group, groupName, groupDescribe) {
+ let th = new MainMenuItem();
+ th.setAttribute('icon', item.icon || '');
+ th.setAttribute('title', item.title || '');
+ if (this.getAttribute('main_menu') === '1' && window.localStorage.getItem('Theme') === 'dark') {
+ groupName.style.color = 'white';
+ groupDescribe.style.color = 'white';
+ th.style.color = 'white';
+ }
+ else {
+ groupName.style.color = 'black';
+ groupDescribe.style.color = 'black';
+ th.style.color = 'black';
+ }
+ if (item.fileChoose) {
+ th.setAttribute('file', '');
+ th.addEventListener('file-change', (event) => {
+ if (item.fileHandler && !th.disabled) {
+ item.fileHandler(event);
+ }
+ });
+ }
+ else {
+ th.removeAttribute('file');
+ th.addEventListener('click', (event) => {
+ if (item.clickHandler && !th.disabled) {
+ item.clickHandler(item);
+ }
+ });
+ }
+ if (item.multi) {
+ th.multi = true;
+ }
+ if (item.disabled !== undefined) {
+ th.disabled = item.disabled;
+ }
+ group === null || group === void 0 ? void 0 : group.appendChild(th);
+ }
+ initElements() {
+ }
+ initHtml() {
+ return `
+ ${initHtmlStyle}
+
+
+
+
+
+
+
+
+
+ `;
+ }
+}
+if (!customElements.get('lit-main-menu')) {
+ customElements.define('lit-main-menu', MainMenu);
+}
diff --git a/tools/ap_file_viewer/src/base-ui/menu/MainMenuGroup.js b/tools/ap_file_viewer/src/base-ui/menu/MainMenuGroup.js
new file mode 100755
index 0000000000..c7a0ad0231
--- /dev/null
+++ b/tools/ap_file_viewer/src/base-ui/menu/MainMenuGroup.js
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+const initHtmlStyle = `
+
+ `;
+export class MainMenuGroup extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' }).innerHTML = this.initHtml();
+ this.initElements();
+ }
+ static get observedAttributes() {
+ return ['title', 'describe', 'collapsed', 'nocollapse', 'radius', 'second', 'icon'];
+ }
+ get second() {
+ return this.hasAttribute('second');
+ }
+ set second(value) {
+ if (value) {
+ this.setAttribute('second', '');
+ }
+ else {
+ this.removeAttribute('second');
+ }
+ }
+ get collapsed() {
+ return this.hasAttribute('collapsed');
+ }
+ set collapsed(value) {
+ if (value) {
+ this.setAttribute('collapsed', '');
+ }
+ else {
+ this.removeAttribute('collapsed');
+ }
+ }
+ get nocollapsed() {
+ return this.hasAttribute('nocollapsed');
+ }
+ set nocollapsed(value) {
+ if (value) {
+ this.setAttribute('nocollapsed', '');
+ }
+ else {
+ this.removeAttribute('nocollapsed');
+ }
+ }
+ get radius() {
+ return this.hasAttribute('radius');
+ }
+ initElements() {
+ this.groupNameEl = this.shadowRoot?.querySelector('.group-title');
+ this.groupDescEl = this.shadowRoot?.querySelector('.group-describe');
+ this.iconEl = this.shadowRoot?.querySelector('.icon');
+ this.group = this.shadowRoot?.querySelector('#group');
+ this.group.addEventListener('click', (event) => {
+ if (this.nocollapsed) {
+ return;
+ }
+ this.collapsed = !this.collapsed;
+ });
+ }
+ initHtml() {
+ return `
+ ${initHtmlStyle}
+
+
+
+
+
+
+
+
+ `;
+ }
+ attributeChangedCallback(name, oldValue, newValue) {
+ switch (name) {
+ case 'title':
+ if (this.groupNameEl) {
+ this.groupNameEl.textContent = newValue;
+ }
+ break;
+ case 'describe':
+ if (this.groupDescEl) {
+ this.groupDescEl.textContent = newValue;
+ }
+ break;
+ case 'icon':
+ if (this.iconEl) {
+ this.iconEl.setAttribute('name', newValue);
+ }
+ break;
+ }
+ }
+}
+if (!customElements.get('lit-main-menu-group')) {
+ customElements.define('lit-main-menu-group', MainMenuGroup);
+}
diff --git a/tools/ap_file_viewer/src/base-ui/menu/MainMenuItem.js b/tools/ap_file_viewer/src/base-ui/menu/MainMenuItem.js
new file mode 100755
index 0000000000..bcdbf7eb1c
--- /dev/null
+++ b/tools/ap_file_viewer/src/base-ui/menu/MainMenuItem.js
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+const initHtmlStyle = `
+
+ `;
+
+export class MainMenuItem extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
+ this.initElements();
+ }
+
+ static get observedAttributes() {
+ return ['title', 'icon', 'file', 'multi', 'disabled'];
+ }
+
+ get title() {
+ return this.getAttribute('title') || '';
+ }
+
+ set title(val) {
+ this.setAttribute('title', val);
+ }
+
+ get multi() {
+ return this.hasAttribute('multi');
+ }
+
+ set multi(val) {
+ if (val) {
+ this.setAttribute('multi', '');
+ } else {
+ this.removeAttribute('multi');
+ }
+ }
+
+ get disabled() {
+ return this.hasAttribute('disabled');
+ }
+
+ set disabled(val) {
+ if (val) {
+ this.setAttribute('disabled', val.toString());
+ this.fileEL?.setAttribute('disabled', val.toString());
+ } else {
+ this.removeAttribute('disabled');
+ this.fileEL?.removeAttribute('disabled');
+ }
+ }
+
+ get back() {
+ return this.hasAttribute('back');
+ }
+
+ set back(isShowBack) {
+ if (isShowBack) {
+ this.setAttribute('back', '');
+ } else {
+ this.removeAttribute('back');
+ }
+ }
+
+ initElements() {
+ this.rootEL = this.shadowRoot?.querySelector('.root');
+ this.titleEl = this.shadowRoot?.querySelector('.name');
+ this.iconEl = this.shadowRoot?.querySelector('.icon');
+ this.fileEL = this.shadowRoot?.querySelector('.file');
+ }
+
+ isFile() {
+ if (this.hasAttribute('file')) {
+ if (this.fileEL) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ connectedCallback() {
+ if (this.hasAttribute('file')) {
+ this.setupFileChangeEvents();
+ this.setupClickEvents();
+ }
+ }
+
+ setupFileChangeEvents() {
+ if (this.fileEL) {
+ this.fileEL.addEventListener('change', event => {
+ const files = this.fileEL.files;
+ if (files && files.length > 0) {
+ this.dispatchEvent(new CustomEvent('file-change', { detail: files[0] }));
+ this.resetFileInput();
+ }
+ });
+ }
+ }
+
+ resetFileInput() {
+ if (this.fileEL) {
+ this.fileEL.value = '';
+ }
+ }
+
+ setupClickEvents() {
+ this.addEventListener('click', mouseEvent => {
+ mouseEvent.stopPropagation();
+ });
+ }
+
+ initHtml() {
+ return `
+ ${initHtmlStyle}
+
+
+ `;
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ switch (name) {
+ case 'title':
+ if (this.titleEl) {
+ this.titleEl.textContent = newValue;
+ }
+ break;
+ case 'icon':
+ if (this.iconEl) {
+ this.iconEl.setAttribute('name', newValue);
+ }
+ break;
+ }
+ }
+}
+
+if (!customElements.get('lit-main-menu-item')) {
+ customElements.define('lit-main-menu-item', MainMenuItem);
+}
diff --git a/tools/ap_file_viewer/src/base-ui/progress-bar/ProgressBar.js b/tools/ap_file_viewer/src/base-ui/progress-bar/ProgressBar.js
new file mode 100755
index 0000000000..ebf813b812
--- /dev/null
+++ b/tools/ap_file_viewer/src/base-ui/progress-bar/ProgressBar.js
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+export class ProgressBar extends HTMLElement {
+ static get observedAttributes() {
+ return ['loading'];
+ }
+
+ constructor() {
+ super();
+ this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
+ }
+
+ get loading() {
+ return this.hasAttribute('loading');
+ }
+
+ set loading(value) {
+ if (value) {
+ this.setAttribute('loading', '');
+ } else {
+ this.removeAttribute('loading');
+ }
+ }
+
+ initHtml() {
+ return `
+
+
+
+
+
+ `;
+ }
+}
+
+if (!customElements.get('lit-progress-bar')) {
+ customElements.define('lit-progress-bar', ProgressBar);
+}
diff --git a/tools/ap_file_viewer/src/img/ap_logo.png b/tools/ap_file_viewer/src/img/ap_logo.png
new file mode 100755
index 0000000000..19993c1479
Binary files /dev/null and b/tools/ap_file_viewer/src/img/ap_logo.png differ
diff --git a/tools/ap_file_viewer/src/img/pic.png b/tools/ap_file_viewer/src/img/pic.png
new file mode 100755
index 0000000000..ffbc872c03
Binary files /dev/null and b/tools/ap_file_viewer/src/img/pic.png differ
diff --git a/tools/ap_file_viewer/src/index.html b/tools/ap_file_viewer/src/index.html
new file mode 100755
index 0000000000..3f6cb793d3
--- /dev/null
+++ b/tools/ap_file_viewer/src/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+ Ap Profdump
+
+
+
+
+
+
diff --git a/tools/ap_file_viewer/src/index.js b/tools/ap_file_viewer/src/index.js
new file mode 100755
index 0000000000..9dde43f736
--- /dev/null
+++ b/tools/ap_file_viewer/src/index.js
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+import './ap/ApApplication.js';
+
+document.body.innerHTML = '';
diff --git a/tools/ap_file_viewer/test/README.md b/tools/ap_file_viewer/test/README.md
new file mode 100755
index 0000000000..6a6df3a13f
--- /dev/null
+++ b/tools/ap_file_viewer/test/README.md
@@ -0,0 +1,52 @@
+# AP分析工具单元测试
+
+## 概述
+为了确保开发过程中代码逻辑紧密贴合设计要求,实施单元测试是一种关键实践。本文主要介绍AP分析工具单元测试使用指导。
+
+## 目录
+
+ ap_code
+ ├── ... # 其他文件
+ └──test
+ ├── figures # 图片文件
+ ├── test.txt # txt格式文件
+ ├── test.ap # ap后缀文件
+ ├── test_ap.py # 单元测试代码
+ ├── requirements.txt # python依赖库
+ └── README.md # 指导文档
+
+## 软件环境准备
+
+依赖版本:python3.11
+
+1.右键windows开始菜单,单击运行,输入cmd,单击确定。
+
+2.在命令行中进入到ap_code\test目录下,安装依赖库命令如下:
+
+ pip install -r requirements.txt
+
+3.查看谷歌浏览器版本,此处得到浏览器版本为126.0.6478.61,查询网址为:chrome://version/,在浏览器中访问,如下图所示:
+
+![img.png](figures/img.png)
+
+4.由于浏览器版本为126.0.6478.61,查看驱动版本列表中是否有126.0.6478.61版本,若有可下载此版本驱动;若没有可下载126.0.6478版本。进入版本系统选择列表后,选择win32(win系统不区分win32或win64)下载压缩包,下载地址如下:
+
+[驱动下载地址](https://googlechromelabs.github.io/chrome-for-testing/)
+
+5.将解压缩获得的chromedriver.exe放到Python的Scripts目录下。
+
+## 使用说明
+
+在命令行中进入到ap_code\test目录下:
+
+1.执行所有用例,命令为:
+
+ pytest -s -W ignore test_ap.py
+
+2.执行单个用例,如test_import_file,命令为:
+
+ pytest -s -W ignore test_ap.py::test_import_file
+
+## 相关仓
+
+暂无
diff --git a/tools/ap_file_viewer/test/figures/img.png b/tools/ap_file_viewer/test/figures/img.png
new file mode 100755
index 0000000000..8229078e3a
Binary files /dev/null and b/tools/ap_file_viewer/test/figures/img.png differ
diff --git a/tools/ap_file_viewer/test/requirements.txt b/tools/ap_file_viewer/test/requirements.txt
new file mode 100755
index 0000000000..24de8979e6
--- /dev/null
+++ b/tools/ap_file_viewer/test/requirements.txt
@@ -0,0 +1,2 @@
+pytest==7.4.0
+selenium==4.10.0
diff --git a/tools/ap_file_viewer/test/test.ap b/tools/ap_file_viewer/test/test.ap
new file mode 100755
index 0000000000..a87c03be1e
Binary files /dev/null and b/tools/ap_file_viewer/test/test.ap differ
diff --git a/tools/ap_file_viewer/test/test.txt b/tools/ap_file_viewer/test/test.txt
new file mode 100755
index 0000000000..076d199a61
--- /dev/null
+++ b/tools/ap_file_viewer/test/test.txt
@@ -0,0 +1 @@
+Test text.
\ No newline at end of file
diff --git a/tools/ap_file_viewer/test/test_ap.py b/tools/ap_file_viewer/test/test_ap.py
new file mode 100755
index 0000000000..fed49b8007
--- /dev/null
+++ b/tools/ap_file_viewer/test/test_ap.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (c) 2024 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.
+
+"""
+
+import pytest
+import os
+import subprocess
+import time
+
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+
+driver = webdriver.Chrome()
+root = None
+file_input = None
+script_directory = ''
+
+
+def setup():
+ global root
+ os.chdir("..")
+ subprocess.Popen(["python", 'dist/apDumpServer.py'])
+ print("setup : Selenium opens the test page")
+ time.sleep(2)
+ driver.get("http://127.0.0.1:9001/ap/")
+ application = driver.find_element(By.TAG_NAME, "ap-application")
+ root = application.shadow_root.find_element(By.CSS_SELECTOR, ".root")
+
+
+def test_import_file():
+ global root, file_input, script_directory
+ main_menu = root.find_element(By.TAG_NAME, "lit-main-menu")
+ menu_body = main_menu.shadow_root.find_element(By.CSS_SELECTOR, ".menu-body")
+ menu_group = menu_body.find_element(By.TAG_NAME, "lit-main-menu-group")
+ menu_item = menu_group.find_element(By.TAG_NAME, "lit-main-menu-item")
+ file_input = menu_item.shadow_root.find_element(By.CSS_SELECTOR, ".file")
+ current_script_path = os.path.abspath(__file__)
+ script_directory = os.path.dirname(current_script_path)
+ path = os.path.join(script_directory, 'test.ap')
+ file_path = os.path.normpath(path).replace("\\", "/")
+ file_input.send_keys(file_path)
+ time.sleep(2)
+
+
+def test_node_click():
+ global root
+ app_content = root.find_element(By.CSS_SELECTOR, ".content")
+ summary_div = app_content.find_element(By.TAG_NAME, "div")
+ tab_summary = summary_div.find_element(By.TAG_NAME, "tab-ap-summary")
+ summary_head = tab_summary.shadow_root.find_element(By.CSS_SELECTOR, ".tab-summary-head")
+ expansion_div = summary_head.find_element(By.TAG_NAME, "div").find_element(By.TAG_NAME, "div")
+ expansion_div.click()
+ time.sleep(2)
+ expansion_div.click()
+ time.sleep(2)
+ labels = summary_head.find_elements(By.TAG_NAME, "label")
+ for label in labels:
+ label.click()
+ time.sleep(2)
+
+
+def test_search():
+ global root
+ search_vessel = root.find_element(By.CSS_SELECTOR, ".search-vessel")
+ search_div = search_vessel.find_element(By.CSS_SELECTOR, ".search")
+ lit_search = search_div.find_element(By.CSS_SELECTOR, "lit-search")
+ search_input = lit_search.shadow_root.find_element(By.CSS_SELECTOR, ".root").find_element(By.TAG_NAME, "input")
+ search_input.send_keys("d")
+ time.sleep(2)
+ search_input.send_keys("w")
+ time.sleep(2)
+ current_text = search_input.get_attribute("value")
+ for _ in range(len(current_text)):
+ search_input.send_keys(Keys.BACK_SPACE)
+ time.sleep(2)
+
+
+def test_unsupported_file():
+ global script_directory
+ driver.refresh()
+ application_node = driver.find_element(By.TAG_NAME, "ap-application")
+ root_node = application_node.shadow_root.find_element(By.CSS_SELECTOR, ".root")
+ main_menu = root_node.find_element(By.TAG_NAME, "lit-main-menu")
+ menu_body = main_menu.shadow_root.find_element(By.CSS_SELECTOR, ".menu-body")
+ menu_group = menu_body.find_element(By.TAG_NAME, "lit-main-menu-group")
+ menu_item = menu_group.find_element(By.TAG_NAME, "lit-main-menu-item")
+ file_input2 = menu_item.shadow_root.find_element(By.CSS_SELECTOR, ".file")
+ path = os.path.join(script_directory, 'test.txt')
+ file_path = os.path.normpath(path).replace("\\", "/")
+ file_input2.send_keys(file_path)
+ time.sleep(5)
+
+
+def teardown():
+ print("teardown : Close selenium")
+ driver.close()
diff --git a/tools/ap_file_viewer/webpack.config.js b/tools/ap_file_viewer/webpack.config.js
new file mode 100755
index 0000000000..f8017db744
--- /dev/null
+++ b/tools/ap_file_viewer/webpack.config.js
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+const path = require('path');
+const childProcess = require('child_process');
+const fs = require('fs');
+
+const staticPath = ['/src/img', '/server'];
+const staticFiles = [
+ '/src/index.html',
+ '/src/base-ui/icon.svg'
+];
+
+function cpDir(sourcePath, targetPath) {
+ fs.readdir(sourcePath, async (err, files) => {
+ if (err) {
+ console.error('unable read dir', err);
+ return;
+ }
+ for (const file of files) {
+ const source = `${sourcePath}/${file}`;
+ const target = `${targetPath}/${file}`;
+ await cpFile(source, target);
+ }
+ });
+}
+
+async function cpFile(source, target) {
+ if (fs.lstatSync(source).isFile()) {
+ const dirPath = path.dirname(target);
+ if (!fs.existsSync(dirPath)) {
+ await fs.promises.mkdir(dirPath, {recursive: true});
+ }
+ await fs.promises.copyFile(source, target);
+ }
+}
+
+function clearDirectory(directoryPath) {
+ let files = [];
+ if (fs.existsSync(directoryPath)) {
+ files = fs.readdirSync(directoryPath);
+ files.forEach((file, index) => {
+ let curPath = directoryPath + '/' + file;
+ if (fs.statSync(curPath).isDirectory()) {
+ clearDirectory(curPath);
+ } else {
+ fs.unlinkSync(curPath);
+ }
+ });
+ fs.rmdirSync(directoryPath);
+ }
+}
+
+
+module.exports = (env, argv) => {
+ const outPath = path.normalize(path.join(__dirname, '/', 'dist'));
+ clearDirectory(outPath);
+ staticPath.forEach((value) => {
+ let pa = path.join(__dirname, value);
+ let distPath;
+ if (value.startsWith('/src')) {
+ distPath = path.join(outPath, value.substring(4, value.length + 1));
+ } else if (value.startsWith('/server')) {
+ distPath = path.join(outPath, value.substring(7, value.length + 1));
+ }
+ cpDir(pa, distPath);
+ });
+ staticFiles.forEach((value) => {
+ let filePath = path.join(__dirname, value);
+ let distFile;
+ if (value.startsWith('/src')) {
+ distFile = path.join(outPath, value.substring(4, value.length + 1));
+ } else if (value.startsWith('/server')) {
+ distFile = path.join(outPath, value.substring(7, value.length + 1));
+ }
+ cpFile(filePath, distFile);
+ });
+
+ return {
+ mode: 'production',
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ filename: 'index.js',
+ }
+ };
+};