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', + } + }; +};