Merge changes Id61114b8,I3451e8a8,I01a258ba
* changes: Add AbtChromeExtensionProtocol Add GlobalConfig to customize Winscope behavior for testing Cross-tool protocol
This commit is contained in:
committed by
Android (Google) Code Review
commit
eaa86c789c
107
tools/winscope-ng/package-lock.json
generated
107
tools/winscope-ng/package-lock.json
generated
@@ -55,15 +55,16 @@
|
||||
"@angular/cli": "~14.0.0",
|
||||
"@angular/compiler-cli": "^14.0.0",
|
||||
"@ngxs/devtools-plugin": "^3.7.4",
|
||||
"@types/chrome": "^0.0.204",
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/jasmine": "~4.0.0",
|
||||
"@types/jasmine": "~4.3.1",
|
||||
"@types/jquery": "^3.5.14",
|
||||
"@types/node": "^18.0.4",
|
||||
"@types/w3c-web-usb": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"eslint": "^8.19.0",
|
||||
"jasmine": "^4.2.1",
|
||||
"jasmine": "~4.3.0",
|
||||
"jasmine-core": "~4.1.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
@@ -3359,6 +3360,16 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chrome": {
|
||||
"version": "0.0.204",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.204.tgz",
|
||||
"integrity": "sha512-EvnHfxMHUWP5EAlRMK66uIEUiy36t72vg5RwmzQv9tdIl2ZmAp92NwvmEZJKpbRnIMTEc2BmSmtrFiEISUJ0Sw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/filesystem": "*",
|
||||
"@types/har-format": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/component-emitter": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz",
|
||||
@@ -3462,6 +3473,27 @@
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/filesystem": {
|
||||
"version": "0.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz",
|
||||
"integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/filewriter": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/filewriter": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
|
||||
"integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/har-format": {
|
||||
"version": "1.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.10.tgz",
|
||||
"integrity": "sha512-o0J30wqycjF5miWDKYKKzzOU1ZTLuA42HZ4HE7/zqTOc/jTLdQ5NhYWvsRQo45Nfi1KHoRdNhteSI4BAxTF1Pg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/html-minifier-terser": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
|
||||
@@ -3477,9 +3509,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jasmine": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz",
|
||||
"integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.1.tgz",
|
||||
"integrity": "sha512-Vu8l+UGcshYmV1VWwULgnV/2RDbBaO6i2Ptx7nd//oJPIZGhoI1YLST4VKagD2Pq/Bc2/7zvtvhM7F3p4SN7kQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/jquery": {
|
||||
@@ -8976,13 +9008,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jasmine": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.2.1.tgz",
|
||||
"integrity": "sha512-LNZEKcScnjPRj5J92I1P515bxTvaHMRAERTyCoaGnWr87eOT6zv+b3M+kxKdH/06Gz4TnnXyHbXLiPtMHZ0ncw==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.3.0.tgz",
|
||||
"integrity": "sha512-ieBmwkd8L1DXnvSnxx7tecXgA0JDgMXPAwBcqM4lLPedJeI9hTHuWifPynTC+dLe4Y+GkSPSlbqqrmYIgGzYUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.6",
|
||||
"jasmine-core": "^4.2.0"
|
||||
"jasmine-core": "^4.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"jasmine": "bin/jasmine.js"
|
||||
@@ -9025,9 +9057,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jasmine/node_modules/jasmine-core": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.2.0.tgz",
|
||||
"integrity": "sha512-OcFpBrIhnbmb9wfI8cqPSJ50pv3Wg4/NSgoZIqHzIwO/2a9qivJWzv8hUvaREIMYYJBas6AvfXATFdVuzzCqVw==",
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.5.0.tgz",
|
||||
"integrity": "sha512-9PMzyvhtocxb3aXJVOPqBDswdgyAeSB81QnLop4npOpbqnheaTEwPc9ZloQeVswugPManznQBjD8kWDTjlnHuw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jasmine/node_modules/minimatch": {
|
||||
@@ -18115,6 +18147,16 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/chrome": {
|
||||
"version": "0.0.204",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.204.tgz",
|
||||
"integrity": "sha512-EvnHfxMHUWP5EAlRMK66uIEUiy36t72vg5RwmzQv9tdIl2ZmAp92NwvmEZJKpbRnIMTEc2BmSmtrFiEISUJ0Sw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/filesystem": "*",
|
||||
"@types/har-format": "*"
|
||||
}
|
||||
},
|
||||
"@types/component-emitter": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz",
|
||||
@@ -18218,6 +18260,27 @@
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/filesystem": {
|
||||
"version": "0.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz",
|
||||
"integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/filewriter": "*"
|
||||
}
|
||||
},
|
||||
"@types/filewriter": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
|
||||
"integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/har-format": {
|
||||
"version": "1.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.10.tgz",
|
||||
"integrity": "sha512-o0J30wqycjF5miWDKYKKzzOU1ZTLuA42HZ4HE7/zqTOc/jTLdQ5NhYWvsRQo45Nfi1KHoRdNhteSI4BAxTF1Pg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/html-minifier-terser": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
|
||||
@@ -18233,9 +18296,9 @@
|
||||
}
|
||||
},
|
||||
"@types/jasmine": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz",
|
||||
"integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.1.tgz",
|
||||
"integrity": "sha512-Vu8l+UGcshYmV1VWwULgnV/2RDbBaO6i2Ptx7nd//oJPIZGhoI1YLST4VKagD2Pq/Bc2/7zvtvhM7F3p4SN7kQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/jquery": {
|
||||
@@ -22309,13 +22372,13 @@
|
||||
}
|
||||
},
|
||||
"jasmine": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.2.1.tgz",
|
||||
"integrity": "sha512-LNZEKcScnjPRj5J92I1P515bxTvaHMRAERTyCoaGnWr87eOT6zv+b3M+kxKdH/06Gz4TnnXyHbXLiPtMHZ0ncw==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.3.0.tgz",
|
||||
"integrity": "sha512-ieBmwkd8L1DXnvSnxx7tecXgA0JDgMXPAwBcqM4lLPedJeI9hTHuWifPynTC+dLe4Y+GkSPSlbqqrmYIgGzYUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.6",
|
||||
"jasmine-core": "^4.2.0"
|
||||
"jasmine-core": "^4.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
@@ -22343,9 +22406,9 @@
|
||||
}
|
||||
},
|
||||
"jasmine-core": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.2.0.tgz",
|
||||
"integrity": "sha512-OcFpBrIhnbmb9wfI8cqPSJ50pv3Wg4/NSgoZIqHzIwO/2a9qivJWzv8hUvaREIMYYJBas6AvfXATFdVuzzCqVw==",
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.5.0.tgz",
|
||||
"integrity": "sha512-9PMzyvhtocxb3aXJVOPqBDswdgyAeSB81QnLop4npOpbqnheaTEwPc9ZloQeVswugPManznQBjD8kWDTjlnHuw==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
"scripts": {
|
||||
"eslint": "npx eslint --ignore-pattern flickerlib src/",
|
||||
"prettier": "npx prettier --write src/",
|
||||
"start": "webpack serve --config webpack.config.dev.js --open --hot",
|
||||
"start": "webpack serve --config webpack.config.dev.js --open --hot --port 8080",
|
||||
"start:remote_tool_mock": "webpack serve --config src/test/remote_tool_mock/webpack.config.js --open --hot --port 8081",
|
||||
"build:kotlin": "rm -rf kotlin_build && npx kotlinc-js -source-map -source-map-embed-sources always -module-kind commonjs -output kotlin_build/flicker.js ../../../platform_testing/libraries/flicker/src/com/android/server/wm/traces/common",
|
||||
"build:prod": "webpack --config webpack.config.prod.js --progress",
|
||||
"build:remote_tool_mock": "webpack --config src/test/remote_tool_mock/webpack.config.js --progress",
|
||||
"build:unit": "webpack --config webpack.config.unit.spec.js",
|
||||
"build:e2e": "rm -rf dist/e2e.spec && npx tsc -p ./src/test/e2e",
|
||||
"build:all": "npm run build:kotlin && npm run build:prod && npm run build:unit && npm run build:e2e",
|
||||
"build:all": "npm run build:kotlin && npm run build:prod && npm run build:remote_tool_mock && npm run build:unit && npm run build:e2e",
|
||||
"test:unit": "jasmine dist/unit.spec/bundle.js",
|
||||
"test:component": "npx karma start",
|
||||
"test:e2e": "npx protractor protractor.config.js",
|
||||
@@ -64,15 +66,16 @@
|
||||
"@angular/cli": "~14.0.0",
|
||||
"@angular/compiler-cli": "^14.0.0",
|
||||
"@ngxs/devtools-plugin": "^3.7.4",
|
||||
"@types/chrome": "^0.0.204",
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/jasmine": "~4.0.0",
|
||||
"@types/jasmine": "~4.3.1",
|
||||
"@types/jquery": "^3.5.14",
|
||||
"@types/node": "^18.0.4",
|
||||
"@types/w3c-web-usb": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"eslint": "^8.19.0",
|
||||
"jasmine": "^4.2.1",
|
||||
"jasmine": "~4.3.0",
|
||||
"jasmine-core": "~4.1.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {
|
||||
AbtChromeExtensionProtocolDependencyInversion,
|
||||
OnBugAttachmentsReceived
|
||||
} from "./abt_chrome_extension_protocol_dependency_inversion";
|
||||
import {
|
||||
MessageType,
|
||||
OpenBuganizerResponse,
|
||||
OpenRequest,
|
||||
WebCommandMessage} from "./messages";
|
||||
import {FunctionUtils} from "common/utils/function_utils";
|
||||
|
||||
export class AbtChromeExtensionProtocol implements AbtChromeExtensionProtocolDependencyInversion {
|
||||
static readonly ABT_EXTENSION_ID = "mbbaofdfoekifkfpgehgffcpagbbjkmj";
|
||||
onBugAttachmentsReceived: OnBugAttachmentsReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
|
||||
setOnBugAttachmentsReceived(callback: OnBugAttachmentsReceived) {
|
||||
this.onBugAttachmentsReceived = callback;
|
||||
}
|
||||
|
||||
run() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get("source") !== "openFromExtension" || !chrome) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openRequestMessage: OpenRequest = {
|
||||
action: MessageType.OPEN_REQUEST
|
||||
};
|
||||
|
||||
chrome.runtime.sendMessage(
|
||||
AbtChromeExtensionProtocol.ABT_EXTENSION_ID,
|
||||
openRequestMessage,
|
||||
async (message) => await this.onMessageReceived(message)
|
||||
);
|
||||
}
|
||||
|
||||
private async onMessageReceived(message: WebCommandMessage) {
|
||||
if (this.isOpenBuganizerResponseMessage(message)) {
|
||||
await this.onOpenBuganizerResponseMessageReceived(message);
|
||||
} else {
|
||||
console.warn("ABT chrome extension protocol received unexpected message:", message);
|
||||
}
|
||||
}
|
||||
|
||||
private async onOpenBuganizerResponseMessageReceived(message: OpenBuganizerResponse) {
|
||||
console.log(
|
||||
"ABT chrome extension protocol received OpenBuganizerResponse message:", message
|
||||
);
|
||||
|
||||
if (message.attachments.length === 0) {
|
||||
console.warn("ABT chrome extension protocol received no attachments");
|
||||
return;
|
||||
}
|
||||
|
||||
const filesBlobPromises = message.attachments.map(async attachment => {
|
||||
const fileQueryResponse = await fetch(attachment.objectUrl);
|
||||
const blob = await fileQueryResponse.blob();
|
||||
|
||||
// Note: the received blob's media type is wrong. It is always set to "image/png".
|
||||
// Context: http://google3/javascript/closure/html/safeurl.js?g=0&l=256&rcl=273756987
|
||||
// Cloning the blob clears the media type.
|
||||
const file = new File([blob], attachment.name);
|
||||
|
||||
return file;
|
||||
});
|
||||
|
||||
const files = await Promise.all(filesBlobPromises);
|
||||
await this.onBugAttachmentsReceived(files);
|
||||
}
|
||||
|
||||
private isOpenBuganizerResponseMessage(message: WebCommandMessage):
|
||||
message is OpenBuganizerResponse {
|
||||
return message.action === MessageType.OPEN_BUGANIZER_RESPONSE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 type OnBugAttachmentsReceived = (attachments: File[]) => Promise<void>;
|
||||
|
||||
export interface AbtChromeExtensionProtocolDependencyInversion {
|
||||
setOnBugAttachmentsReceived(callback: OnBugAttachmentsReceived): void;
|
||||
run(): void;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {
|
||||
OnBugAttachmentsReceived,
|
||||
AbtChromeExtensionProtocolDependencyInversion
|
||||
} from "./abt_chrome_extension_protocol_dependency_inversion";
|
||||
|
||||
export class AbtChromeExtensionProtocolStub implements AbtChromeExtensionProtocolDependencyInversion {
|
||||
setOnBugAttachmentsReceived(callback: OnBugAttachmentsReceived) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
run() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
133
tools/winscope-ng/src/abt_chrome_extension/messages.ts
Normal file
133
tools/winscope-ng/src/abt_chrome_extension/messages.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// TODO (b/262667224):
|
||||
// deduplicate all the type definitions in this file when we move Winscope to google3.
|
||||
// The definitions were duplicated from these source files:
|
||||
// - google3/wireless/android/tools/android_bug_tool/app/platform/web/interface/command.ts
|
||||
// - google3/wireless/android/tools/android_bug_tool/app/platform/web/interface/attachment_metadata.ts
|
||||
// - google3/wireless/android/tools/android_bug_tool/app/platform/web/interface/bug_report_metadata.ts
|
||||
|
||||
/** Describes the type of message enclosed in a {@code WebCommandMessage}. */
|
||||
export enum MessageType {
|
||||
UNKNOWN,
|
||||
OPEN_REQUEST,
|
||||
OPEN_BUGANIZER_RESPONSE,
|
||||
OPEN_WEB_RESPONSE,
|
||||
OPEN_URL_REQUEST,
|
||||
OPEN_URL_RESPONSE,
|
||||
CHECK_ISSUE_METADATA_REQUEST,
|
||||
CHECK_ISSUE_METADATA_RESPONSE,
|
||||
OPEN_TOOL_WEB_REQUEST,
|
||||
}
|
||||
|
||||
/** Base of all messages sent between the web and extension. */
|
||||
export declare interface WebCommandMessage {
|
||||
action: MessageType;
|
||||
}
|
||||
|
||||
/** Request from web to background to download the file. */
|
||||
export interface OpenRequest extends WebCommandMessage {
|
||||
action: MessageType.OPEN_REQUEST;
|
||||
}
|
||||
|
||||
/** Response of download the issue's attachment from background. */
|
||||
export declare interface OpenBuganizerResponse extends WebCommandMessage {
|
||||
action: MessageType.OPEN_BUGANIZER_RESPONSE;
|
||||
|
||||
/** issue id */
|
||||
issueId: string;
|
||||
|
||||
/** issue title */
|
||||
issueTitle: string|undefined;
|
||||
|
||||
/** issue access level */
|
||||
issueAccessLevel: IssueAccessLimit|undefined;
|
||||
|
||||
/** Attachment list. */
|
||||
attachments: AttachmentMetadata[];
|
||||
}
|
||||
|
||||
/** Attachment metadata. */
|
||||
export interface AttachmentMetadata {
|
||||
bugReportMetadata?: BugReportMetadata;
|
||||
author: string;
|
||||
name: string;
|
||||
objectUrl: string;
|
||||
restrictionSeverity: RestrictionSeverity;
|
||||
resourceName: string;
|
||||
entityStatus: EntityStatus;
|
||||
attachmentId: string;
|
||||
fileSize: number;
|
||||
commentTimestamp?: DateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Incorporates all of the metadata that can be retrieved from a bugreport
|
||||
* file name.
|
||||
*/
|
||||
export interface BugReportMetadata {
|
||||
uuid?: string;
|
||||
hasWinscope: boolean;
|
||||
hasTrace: boolean;
|
||||
isRedacted: boolean;
|
||||
device: string;
|
||||
build: string;
|
||||
// The date parsed from the bug report filename is only used for
|
||||
// grouping common files together. It is not used for display purposes.
|
||||
timestamp: DateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines of the issue access limit. See:
|
||||
* http://go/buganizer/concepts/access-control#accesslimit
|
||||
*/
|
||||
export const enum IssueAccessLimit {
|
||||
INTERNAL = "",
|
||||
VISIBLE_TO_PARTNERS = "Visible to Partners",
|
||||
VISIBLE_TO_PUBLIC = "Visible to Public",
|
||||
}
|
||||
|
||||
/**
|
||||
* Types of issue content restriction verdicts. See:
|
||||
* http://google3/google/devtools/issuetracker/v1/issuetracker.proto?l=1858&rcl=278024740
|
||||
*/
|
||||
export enum RestrictionSeverity {
|
||||
/** Unspecified restricted content severity */
|
||||
RESTRICTION_SEVERITY_UNSPECIFIED = 0,
|
||||
/** No restricted content was detected/flagged in the content */
|
||||
NONE_DETECTED = 1,
|
||||
/** Restricted content was detected/flagged in the content */
|
||||
RESTRICTED = 2,
|
||||
/** RESTRICTED_PLUS content was detected/flagged in the content */
|
||||
RESTRICTED_PLUS = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Types of entity statuses for issue tracker attachments. See:
|
||||
* https:google3/google/devtools/issuetracker/v1/issuetracker.proto;rcl=448855448;l=58
|
||||
*/
|
||||
export enum EntityStatus {
|
||||
// Default value. Entity exists and is available for use.
|
||||
ACTIVE = 0,
|
||||
// Entity is invisible except for administrative actions, i.e. undelete.
|
||||
DELETED = 1,
|
||||
// Entity is irretrievably wiped.
|
||||
PURGED = 2,
|
||||
}
|
||||
|
||||
// Actual definition is in google3/third_party/javascript/closure/date/date
|
||||
export type DateTime = object;
|
||||
@@ -23,13 +23,15 @@ import {
|
||||
ViewEncapsulation
|
||||
} from "@angular/core";
|
||||
import { createCustomElement } from "@angular/elements";
|
||||
import { AppComponentDependencyInversion } from "./app_component_dependency_inversion";
|
||||
import { TimelineComponent} from "./timeline/timeline.component";
|
||||
import {AbtChromeExtensionProtocol} from "abt_chrome_extension/abt_chrome_extension_protocol";
|
||||
import {CrossToolProtocol} from "cross_tool/cross_tool_protocol";
|
||||
import { Mediator } from "app/mediator";
|
||||
import { TraceData } from "app/trace_data";
|
||||
import { PersistentStore } from "common/utils/persistent_store";
|
||||
import { Timestamp } from "common/trace/timestamp";
|
||||
import { FileUtils } from "common/utils/file_utils";
|
||||
import { FunctionUtils } from "common/utils/function_utils";
|
||||
import { proxyClient, ProxyState } from "trace_collection/proxy_client";
|
||||
import { ViewerInputMethodComponent } from "viewers/components/viewer_input_method.component";
|
||||
import { View, Viewer } from "viewers/viewer";
|
||||
@@ -124,14 +126,14 @@ import {TRACE_INFO} from "app/trace_info";
|
||||
<collect-traces
|
||||
class="collect-traces-card homepage-card"
|
||||
[traceData]="traceData"
|
||||
(traceDataLoaded)="onTraceDataLoaded()"
|
||||
(traceDataLoaded)="mediator.onWinscopeTraceDataLoaded()"
|
||||
[store]="store"
|
||||
></collect-traces>
|
||||
|
||||
<upload-traces
|
||||
class="upload-traces-card homepage-card"
|
||||
[traceData]="traceData"
|
||||
(traceDataLoaded)="onTraceDataLoaded()"
|
||||
(traceDataLoaded)="mediator.onWinscopeTraceDataLoaded()"
|
||||
></upload-traces>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,9 +166,6 @@ import {TRACE_INFO} from "app/trace_info";
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
.timescrub {
|
||||
margin: 8px;
|
||||
}
|
||||
.center {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
@@ -188,12 +187,21 @@ import {TRACE_INFO} from "app/trace_info";
|
||||
],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AppComponent {
|
||||
export class AppComponent implements AppComponentDependencyInversion {
|
||||
title = "winscope-ng";
|
||||
changeDetectorRef: ChangeDetectorRef;
|
||||
traceData = new TraceData();
|
||||
timelineData = new TimelineData();
|
||||
mediator = new Mediator(this.traceData, this.timelineData);
|
||||
abtChromeExtensionProtocol = new AbtChromeExtensionProtocol();
|
||||
crossToolProtocol = new CrossToolProtocol();
|
||||
mediator = new Mediator(
|
||||
this.traceData,
|
||||
this.timelineData,
|
||||
this.abtChromeExtensionProtocol,
|
||||
this.crossToolProtocol,
|
||||
this,
|
||||
localStorage
|
||||
);
|
||||
states = ProxyState;
|
||||
store: PersistentStore = new PersistentStore();
|
||||
currentTimestamp?: Timestamp;
|
||||
@@ -257,24 +265,23 @@ export class AppComponent {
|
||||
|
||||
public onUploadNewClick() {
|
||||
this.dataLoaded = false;
|
||||
this.mediator.clearData();
|
||||
this.mediator.onWinscopeUploadNew();
|
||||
proxyClient.adbData = [];
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
public onTraceDataLoaded(viewers: Viewer[]) {
|
||||
this.viewers = viewers;
|
||||
this.dataLoaded = true;
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
public setDarkMode(enabled: boolean) {
|
||||
document.body.classList.toggle("dark-mode", enabled);
|
||||
this.store.add("dark-mode", `${enabled}`);
|
||||
this.isDarkModeOn = enabled;
|
||||
}
|
||||
|
||||
public onTraceDataLoaded() {
|
||||
this.mediator.onTraceDataLoaded(localStorage);
|
||||
this.viewers = this.mediator.getViewers();
|
||||
this.dataLoaded = true;
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
async onDownloadTracesButtonClick() {
|
||||
const traceFiles = await this.makeTraceFilesForDownload();
|
||||
const zipFileBlob = await FileUtils.createZipArchive(traceFiles);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {Viewer} from "viewers/viewer";
|
||||
|
||||
export interface AppComponentDependencyInversion {
|
||||
onTraceDataLoaded(viewers: Viewer[]): void;
|
||||
}
|
||||
24
tools/winscope-ng/src/app/components/app_component_stub.ts
Normal file
24
tools/winscope-ng/src/app/components/app_component_stub.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {AppComponentDependencyInversion} from "./app_component_dependency_inversion";
|
||||
import {Viewer} from "viewers/viewer";
|
||||
|
||||
export class AppComponentStub implements AppComponentDependencyInversion {
|
||||
onTraceDataLoaded(viewers: Viewer[]) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,10 @@ import {MatInputModule} from "@angular/material/input";
|
||||
import { SingleTimelineComponent } from "./single_timeline.component";
|
||||
import {Mediator} from "app/mediator";
|
||||
import {TraceData} from "app/trace_data";
|
||||
import {AbtChromeExtensionProtocolStub} from "abt_chrome_extension/abt_chrome_extension_protocol_stub";
|
||||
import {CrossToolProtocolStub} from "cross_tool/cross_tool_protocol_stub";
|
||||
import {AppComponentStub} from "app/components/app_component_stub";
|
||||
import {MockStorage} from "test/unit/mock_storage";
|
||||
|
||||
describe("TimelineComponent", () => {
|
||||
let fixture: ComponentFixture<TimelineComponent>;
|
||||
@@ -70,7 +74,17 @@ describe("TimelineComponent", () => {
|
||||
|
||||
const traceData = new TraceData();
|
||||
const timelineData = new TimelineData();
|
||||
component.mediator = new Mediator(traceData, timelineData);
|
||||
const abtChromeExtensionProtocol = new AbtChromeExtensionProtocolStub();
|
||||
const crossToolProtocol = new CrossToolProtocolStub();
|
||||
const appComponent = new AppComponentStub();
|
||||
component.mediator = new Mediator(
|
||||
traceData,
|
||||
timelineData,
|
||||
abtChromeExtensionProtocol,
|
||||
crossToolProtocol,
|
||||
appComponent,
|
||||
new MockStorage()
|
||||
);
|
||||
component.timelineData = timelineData;
|
||||
});
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
import { TRACE_INFO } from "app/trace_info";
|
||||
import { Mediator } from "app/mediator";
|
||||
import { TimelineComponentDependencyInversion } from "./timeline_component_dependency_inversion";
|
||||
import { TimelineData } from "app/timeline_data";
|
||||
import { MiniTimelineComponent } from "./mini_timeline.component";
|
||||
import { ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType } from "common/trace/timestamp";
|
||||
import { FunctionUtils } from "common/utils/function_utils";
|
||||
import { TimeUtils } from "common/utils/time_utils";
|
||||
|
||||
@Component({
|
||||
@@ -272,7 +272,7 @@ import { TimeUtils } from "common/utils/time_utils";
|
||||
}
|
||||
`],
|
||||
})
|
||||
export class TimelineComponent {
|
||||
export class TimelineComponent implements TimelineComponentDependencyInversion {
|
||||
public readonly TOGGLE_BUTTON_CLASS: string = "button-toggle-expansion";
|
||||
public readonly MAX_SELECTED_TRACES = 3;
|
||||
|
||||
@@ -339,9 +339,7 @@ export class TimelineComponent {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.mediator.setNotifyCurrentTimestampChangedToTimelineComponentCallback((timestamp: Timestamp|undefined) => {
|
||||
this.onCurrentTimestampChanged(timestamp);
|
||||
});
|
||||
this.mediator.setTimelineComponent(this);
|
||||
|
||||
if (this.timelineData.hasTimestamps()) {
|
||||
this.updateTimeInputValuesToCurrentTimestamp();
|
||||
@@ -355,9 +353,7 @@ export class TimelineComponent {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.mediator.setNotifyCurrentTimestampChangedToTimelineComponentCallback(
|
||||
FunctionUtils.DO_NOTHING
|
||||
);
|
||||
this.mediator.setTimelineComponent(undefined);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {Timestamp} from "common/trace/timestamp";
|
||||
|
||||
export interface TimelineComponentDependencyInversion {
|
||||
onCurrentTimestampChanged(timestamp: Timestamp|undefined): void;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {TimelineComponentDependencyInversion} from "./timeline_component_dependency_inversion";
|
||||
import {Timestamp} from "common/trace/timestamp";
|
||||
|
||||
export class TimelineComponentStub implements TimelineComponentDependencyInversion {
|
||||
onCurrentTimestampChanged(timestamp: Timestamp|undefined) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {Timestamp, TimestampType} from "common/trace/timestamp";
|
||||
|
||||
import {AppComponentStub} from "./components/app_component_stub";
|
||||
import {TimelineComponentStub} from "./components/timeline/timeline_component_stub";
|
||||
import {Mediator} from "./mediator";
|
||||
import {AbtChromeExtensionProtocolStub} from "abt_chrome_extension/abt_chrome_extension_protocol_stub";
|
||||
import {CrossToolProtocolStub} from "cross_tool/cross_tool_protocol_stub";
|
||||
import {RealTimestamp} from "common/trace/timestamp";
|
||||
import {UnitTestUtils} from "test/unit/utils";
|
||||
import {ViewerFactory} from "viewers/viewer_factory";
|
||||
import {ViewerStub} from "viewers/viewer_stub";
|
||||
@@ -22,82 +27,137 @@ import {TimelineData} from "./timeline_data";
|
||||
import {TraceData} from "./trace_data";
|
||||
import {MockStorage} from "test/unit/mock_storage";
|
||||
|
||||
class TimelineComponentStub {
|
||||
onCurrentTimestampChanged(timestamp: Timestamp|undefined) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
describe("Mediator", () => {
|
||||
const viewerStub = new ViewerStub("Title");
|
||||
let timelineComponent: TimelineComponentStub;
|
||||
let traceData: TraceData;
|
||||
let timelineData: TimelineData;
|
||||
let abtChromeExtensionProtocol: AbtChromeExtensionProtocolStub;
|
||||
let crossToolProtocol: CrossToolProtocolStub;
|
||||
let appComponent: AppComponentStub;
|
||||
let timelineComponent: TimelineComponentStub;
|
||||
let mediator: Mediator;
|
||||
|
||||
beforeEach(async () => {
|
||||
timelineComponent = new TimelineComponentStub();
|
||||
traceData = new TraceData();
|
||||
timelineData = new TimelineData();
|
||||
mediator = new Mediator(traceData, timelineData);
|
||||
mediator.setNotifyCurrentTimestampChangedToTimelineComponentCallback(timestamp => {
|
||||
timelineComponent.onCurrentTimestampChanged(timestamp);
|
||||
});
|
||||
abtChromeExtensionProtocol = new AbtChromeExtensionProtocolStub();
|
||||
crossToolProtocol = new CrossToolProtocolStub();
|
||||
appComponent = new AppComponentStub();
|
||||
timelineComponent = new TimelineComponentStub();
|
||||
mediator = new Mediator(
|
||||
traceData,
|
||||
timelineData,
|
||||
abtChromeExtensionProtocol,
|
||||
crossToolProtocol,
|
||||
appComponent,
|
||||
new MockStorage()
|
||||
);
|
||||
mediator.setTimelineComponent(timelineComponent);
|
||||
|
||||
spyOn(ViewerFactory.prototype, "createViewers").and.returnValue([viewerStub]);
|
||||
});
|
||||
|
||||
it("initializes TimelineData on data load event", async () => {
|
||||
it("handles data load event from Winscope", async () => {
|
||||
spyOn(timelineData, "initialize").and.callThrough();
|
||||
|
||||
await loadTraces();
|
||||
expect(timelineData.initialize).toHaveBeenCalledTimes(0);
|
||||
|
||||
mediator.onTraceDataLoaded(new MockStorage());
|
||||
expect(timelineData.initialize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
||||
it("it creates viewers on data load event", async () => {
|
||||
spyOn(appComponent, "onTraceDataLoaded");
|
||||
spyOn(viewerStub, "notifyCurrentTraceEntries");
|
||||
|
||||
await loadTraces();
|
||||
expect(mediator.getViewers()).toEqual([]);
|
||||
|
||||
mediator.onTraceDataLoaded(new MockStorage());
|
||||
expect(mediator.getViewers()).toEqual([viewerStub]);
|
||||
expect(timelineData.initialize).toHaveBeenCalledTimes(0);
|
||||
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledTimes(0);
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(0);
|
||||
|
||||
mediator.onWinscopeTraceDataLoaded();
|
||||
expect(timelineData.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
|
||||
// notifies viewer about current timestamp on creation
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("forwards timestamp changed events/notifications", async () => {
|
||||
const timestamp10 = new Timestamp(TimestampType.REAL, 10n);
|
||||
const timestamp11 = new Timestamp(TimestampType.REAL, 11n);
|
||||
|
||||
//TODO: enable/adapt this test once FileUtils is fully compatible with Node.js (b/262269229).
|
||||
// FileUtils#unzipFile() currently can't execute on Node.js.
|
||||
//it("processes bugreport message from remote tool", async () => {
|
||||
// spyOn(traceData, "loadTraces").and.callThrough();
|
||||
// spyOn(timelineData, "initialize").and.callThrough();
|
||||
// spyOn(appComponent, "onTraceDataLoaded");
|
||||
// spyOn(viewerStub, "notifyCurrentTraceEntries");
|
||||
|
||||
// const bugreport = await UnitTestUtils.getFixtureFile("bugreports/bugreport_stripped.zip");
|
||||
// const timestamp = new RealTimestamp(10n);
|
||||
// await crossToolProtocol.onBugreportReceived(bugreport, timestamp);
|
||||
|
||||
// expect(traceData.loadTraces).toHaveBeenCalledOnceWith([bugreport]);
|
||||
// expect(timelineData.initialize).toHaveBeenCalledTimes(1);
|
||||
// expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
|
||||
// expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
|
||||
//});
|
||||
|
||||
it("propagates current timestamp changed through timeline", async () => {
|
||||
const timestamp10 = new RealTimestamp(10n);
|
||||
const timestamp11 = new RealTimestamp(11n);
|
||||
|
||||
await loadTraces();
|
||||
mediator.onTraceDataLoaded(new MockStorage());
|
||||
expect(mediator.getViewers()).toEqual([viewerStub]);
|
||||
mediator.onWinscopeTraceDataLoaded();
|
||||
|
||||
spyOn(viewerStub, "notifyCurrentTraceEntries");
|
||||
spyOn(timelineComponent, "onCurrentTimestampChanged");
|
||||
spyOn(crossToolProtocol, "sendTimestamp");
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(0);
|
||||
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
|
||||
|
||||
// notify timestamp
|
||||
timelineData.setCurrentTimestamp(timestamp10);
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
|
||||
|
||||
// notify timestamp again (no timestamp change)
|
||||
// notify same timestamp again (ignored, no timestamp change)
|
||||
timelineData.setCurrentTimestamp(timestamp10);
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
|
||||
|
||||
// reset back to the default timestamp should trigger a change
|
||||
// notify another timestamp
|
||||
timelineData.setCurrentTimestamp(timestamp11);
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(2);
|
||||
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(2);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("propagates timestamp received from remote tool", async () => {
|
||||
const timestamp10 = new RealTimestamp(10n);
|
||||
const timestamp11 = new RealTimestamp(11n);
|
||||
|
||||
await loadTraces();
|
||||
mediator.onWinscopeTraceDataLoaded();
|
||||
|
||||
spyOn(viewerStub, "notifyCurrentTraceEntries");
|
||||
spyOn(timelineComponent, "onCurrentTimestampChanged");
|
||||
spyOn(crossToolProtocol, "sendTimestamp");
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(0);
|
||||
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
|
||||
|
||||
// receive timestamp
|
||||
await crossToolProtocol.onTimestampReceived(timestamp10);
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
|
||||
|
||||
// receive same timestamp again (ignored, no timestamp change)
|
||||
await crossToolProtocol.onTimestampReceived(timestamp10);
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
|
||||
|
||||
// receive another
|
||||
await crossToolProtocol.onTimestampReceived(timestamp11);
|
||||
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(2);
|
||||
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(2);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
const loadTraces = async () => {
|
||||
|
||||
@@ -14,71 +14,200 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Timestamp} from "common/trace/timestamp";
|
||||
import {TraceType} from "common/trace/trace_type";
|
||||
import {FunctionUtils} from "common/utils/function_utils";
|
||||
import { Viewer } from "viewers/viewer";
|
||||
import { ViewerFactory } from "viewers/viewer_factory";
|
||||
import {AppComponentDependencyInversion} from "./components/app_component_dependency_inversion";
|
||||
import {TimelineComponentDependencyInversion}
|
||||
from "./components/timeline/timeline_component_dependency_inversion";
|
||||
import {TimelineData} from "./timeline_data";
|
||||
import {TraceData} from "./trace_data";
|
||||
import {AbtChromeExtensionProtocolDependencyInversion}
|
||||
from "abt_chrome_extension/abt_chrome_extension_protocol_dependency_inversion";
|
||||
import {CrossToolProtocolDependencyInversion}
|
||||
from "cross_tool/cross_tool_protocol_dependency_inversion";
|
||||
import {FileUtils} from "common/utils/file_utils";
|
||||
import {Timestamp, TimestampType} from "common/trace/timestamp";
|
||||
import {TraceType} from "common/trace/trace_type";
|
||||
import {Viewer} from "viewers/viewer";
|
||||
import {ViewerFactory} from "viewers/viewer_factory";
|
||||
|
||||
type CurrentTimestampChangedCallback = (timestamp: Timestamp|undefined) => void;
|
||||
|
||||
class Mediator {
|
||||
export class Mediator {
|
||||
private traceData: TraceData;
|
||||
private timelineData: TimelineData;
|
||||
private abtChromeExtensionProtocol: AbtChromeExtensionProtocolDependencyInversion;
|
||||
private crossToolProtocol: CrossToolProtocolDependencyInversion;
|
||||
private appComponent: AppComponentDependencyInversion;
|
||||
private timelineComponent?: TimelineComponentDependencyInversion;
|
||||
private storage: Storage;
|
||||
private viewers: Viewer[] = [];
|
||||
private notifyCurrentTimestampChangedToTimelineComponent: CurrentTimestampChangedCallback =
|
||||
FunctionUtils.DO_NOTHING;
|
||||
private isChangingCurrentTimestamp = false;
|
||||
private blockWhileRemoteToolBugreportIsBeingLoaded = Promise.resolve();
|
||||
|
||||
constructor(
|
||||
traceData: TraceData,
|
||||
timelineData: TimelineData,
|
||||
abtChromeExtensionProtocol: AbtChromeExtensionProtocolDependencyInversion,
|
||||
crossToolProtocol: CrossToolProtocolDependencyInversion,
|
||||
appComponent: AppComponentDependencyInversion,
|
||||
storage: Storage) {
|
||||
|
||||
constructor(traceData: TraceData, timelineData: TimelineData) {
|
||||
this.traceData = traceData;
|
||||
this.timelineData = timelineData;
|
||||
this.timelineData.setOnCurrentTimestampChangedCallback(timestamp => {
|
||||
this.onCurrentTimestampChanged(timestamp);
|
||||
this.abtChromeExtensionProtocol = abtChromeExtensionProtocol;
|
||||
this.crossToolProtocol = crossToolProtocol;
|
||||
this.appComponent = appComponent;
|
||||
this.storage = storage;
|
||||
|
||||
this.timelineData.setOnCurrentTimestampChanged(timestamp => {
|
||||
this.onWinscopeCurrentTimestampChanged(timestamp);
|
||||
});
|
||||
|
||||
this.crossToolProtocol.setOnBugreportReceived(async (bugreport: File, timestamp?: Timestamp) => {
|
||||
await this.onRemoteToolBugreportReceived(bugreport, timestamp);
|
||||
});
|
||||
|
||||
this.crossToolProtocol.setOnTimestampReceived(async (timestamp: Timestamp) => {
|
||||
await this.onRemoteToolTimestampReceived(timestamp);
|
||||
});
|
||||
|
||||
this.abtChromeExtensionProtocol.setOnBugAttachmentsReceived(async (attachments: File[]) => {
|
||||
await this.onAbtChromeExtensionBugAttachmentsReceived(attachments);
|
||||
});
|
||||
this.abtChromeExtensionProtocol.run();
|
||||
}
|
||||
|
||||
public setTimelineComponent(timelineComponent: TimelineComponentDependencyInversion|undefined) {
|
||||
this.timelineComponent = timelineComponent;
|
||||
}
|
||||
|
||||
public onWinscopeTraceDataLoaded() {
|
||||
this.processTraceData();
|
||||
}
|
||||
|
||||
public async onRemoteToolBugreportReceived(bugreport: File, timestamp?: Timestamp) {
|
||||
let unblockOtherRemoteToolEventHandlers: () => void;
|
||||
|
||||
this.blockWhileRemoteToolBugreportIsBeingLoaded = new Promise<void>(resolve => {
|
||||
unblockOtherRemoteToolEventHandlers = resolve;
|
||||
});
|
||||
|
||||
try {
|
||||
await this.processFiles([bugreport]);
|
||||
} finally {
|
||||
unblockOtherRemoteToolEventHandlers!();
|
||||
}
|
||||
|
||||
if (timestamp !== undefined) {
|
||||
await this.onRemoteToolTimestampReceived(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
public async onAbtChromeExtensionBugAttachmentsReceived(attachments: File[]) {
|
||||
await this.processFiles(attachments);
|
||||
}
|
||||
|
||||
public onWinscopeCurrentTimestampChanged(timestamp: Timestamp|undefined) {
|
||||
this.executeIgnoringRecursiveTimestampNotifications(() => {
|
||||
const entries = this.traceData.getTraceEntries(timestamp);
|
||||
this.viewers.forEach(viewer => {
|
||||
viewer.notifyCurrentTraceEntries(entries);
|
||||
});
|
||||
|
||||
if (timestamp) {
|
||||
if (timestamp.getType() !== TimestampType.REAL) {
|
||||
console.warn(
|
||||
"Cannot propagate timestamp change to remote tool." +
|
||||
` Remote tool expects timestamp type ${TimestampType.REAL},` +
|
||||
` but Winscope wants to notify timestamp type ${timestamp.getType()}.`
|
||||
);
|
||||
} else {
|
||||
this.crossToolProtocol.sendTimestamp(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
this.timelineComponent?.onCurrentTimestampChanged(timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
public setNotifyCurrentTimestampChangedToTimelineComponentCallback(callback: CurrentTimestampChangedCallback) {
|
||||
this.notifyCurrentTimestampChangedToTimelineComponent = callback;
|
||||
}
|
||||
public async onRemoteToolTimestampReceived(timestamp: Timestamp) {
|
||||
await this.executeIgnoringRecursiveTimestampNotificationsAsync(async () => {
|
||||
if (this.timelineData.getTimestampType() != TimestampType.REAL) {
|
||||
console.warn(
|
||||
"Cannot apply new timestamp received from remote tool." +
|
||||
` Remote tool notified timestamp type type ${TimestampType.REAL},` +
|
||||
` but Winscope is accepting timestamp type ${this.timelineData.getTimestampType()}.`
|
||||
);
|
||||
}
|
||||
|
||||
public getViewers(): Viewer[] {
|
||||
return this.viewers;
|
||||
}
|
||||
if (this.timelineData.getCurrentTimestamp() === timestamp) {
|
||||
return; // no timestamp change
|
||||
}
|
||||
|
||||
public onTraceDataLoaded(storage: Storage) {
|
||||
this.timelineData.initialize(
|
||||
this.traceData.getTimelines(),
|
||||
this.traceData.getScreenRecordingVideo()
|
||||
);
|
||||
this.createViewers(storage);
|
||||
}
|
||||
// Make sure we finished loading the bugreport, before notifying the timestamp to the rest of
|
||||
// the system. Otherwise, the timestamp notification would just get lost.
|
||||
await this.blockWhileRemoteToolBugreportIsBeingLoaded;
|
||||
|
||||
public onCurrentTimestampChanged(timestamp: Timestamp|undefined) {
|
||||
const entries = this.traceData.getTraceEntries(timestamp);
|
||||
this.viewers.forEach(viewer => {
|
||||
viewer.notifyCurrentTraceEntries(entries);
|
||||
const entries = this.traceData.getTraceEntries(timestamp);
|
||||
this.viewers.forEach(viewer => {
|
||||
viewer.notifyCurrentTraceEntries(entries);
|
||||
});
|
||||
|
||||
this.timelineData.setCurrentTimestamp(timestamp);
|
||||
this.timelineComponent?.onCurrentTimestampChanged(timestamp);
|
||||
});
|
||||
|
||||
this.notifyCurrentTimestampChangedToTimelineComponent(timestamp);
|
||||
}
|
||||
|
||||
public clearData() {
|
||||
public onWinscopeUploadNew() {
|
||||
this.traceData.clear();
|
||||
this.timelineData.clear();
|
||||
this.viewers = [];
|
||||
}
|
||||
|
||||
private createViewers(storage: Storage) {
|
||||
private async processFiles(files: File[]) {
|
||||
const unzippedFiles = await FileUtils.unzipFilesIfNeeded(files);
|
||||
this.traceData.clear();
|
||||
await this.traceData.loadTraces(unzippedFiles);
|
||||
this.processTraceData();
|
||||
}
|
||||
|
||||
private processTraceData() {
|
||||
this.timelineData.initialize(
|
||||
this.traceData.getTimelines(),
|
||||
this.traceData.getScreenRecordingVideo()
|
||||
);
|
||||
this.createViewers();
|
||||
this.appComponent.onTraceDataLoaded(this.viewers);
|
||||
}
|
||||
|
||||
private createViewers() {
|
||||
const traceTypes = this.traceData.getLoadedTraces().map(trace => trace.type);
|
||||
this.viewers = new ViewerFactory().createViewers(new Set<TraceType>(traceTypes), storage);
|
||||
this.viewers = new ViewerFactory().createViewers(new Set<TraceType>(traceTypes), this.storage);
|
||||
|
||||
// Make sure to update the viewers active entries as soon as they are created.
|
||||
if (this.timelineData.getCurrentTimestamp()) {
|
||||
this.onCurrentTimestampChanged(this.timelineData.getCurrentTimestamp());
|
||||
this.onWinscopeCurrentTimestampChanged(this.timelineData.getCurrentTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
private executeIgnoringRecursiveTimestampNotifications(op: () => void) {
|
||||
if (this.isChangingCurrentTimestamp) {
|
||||
return;
|
||||
}
|
||||
this.isChangingCurrentTimestamp = true;
|
||||
try {
|
||||
op();
|
||||
} finally {
|
||||
this.isChangingCurrentTimestamp = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async executeIgnoringRecursiveTimestampNotificationsAsync(op: () => Promise<void>) {
|
||||
if (this.isChangingCurrentTimestamp) {
|
||||
return;
|
||||
}
|
||||
this.isChangingCurrentTimestamp = true;
|
||||
try {
|
||||
await op();
|
||||
} finally {
|
||||
this.isChangingCurrentTimestamp = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { Mediator };
|
||||
|
||||
@@ -43,7 +43,7 @@ describe("TimelineData", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
timelineData = new TimelineData();
|
||||
timelineData.setOnCurrentTimestampChangedCallback(timestamp => {
|
||||
timelineData.setOnCurrentTimestampChanged(timestamp => {
|
||||
timestampChangedObserver.onCurrentTimestampChanged(timestamp);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export class TimelineData {
|
||||
this.onCurrentTimestampChanged(this.getCurrentTimestamp());
|
||||
}
|
||||
|
||||
setOnCurrentTimestampChangedCallback(callback: TimestampCallbackType) {
|
||||
setOnCurrentTimestampChanged(callback: TimestampCallbackType) {
|
||||
this.onCurrentTimestampChanged = callback;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,8 @@ export class FunctionUtils {
|
||||
static readonly DO_NOTHING = () => {
|
||||
// do nothing
|
||||
};
|
||||
|
||||
static readonly DO_NOTHING_ASYNC = (): Promise<void> => {
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
31
tools/winscope-ng/src/common/utils/global_config.ts
Normal file
31
tools/winscope-ng/src/common/utils/global_config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 interface Schema {
|
||||
MODE: "DEV"|"PROD";
|
||||
REMOTE_TOOL_URL: "http://localhost:8081"|"https://android-build.googleplex.com/builds/bug_tool";
|
||||
}
|
||||
|
||||
export class GlobalConfig implements Schema {
|
||||
readonly MODE = "PROD" as const;
|
||||
readonly REMOTE_TOOL_URL = "https://android-build.googleplex.com/builds/bug_tool" as const;
|
||||
|
||||
set(config: Schema) {
|
||||
Object.assign(this, config);
|
||||
}
|
||||
}
|
||||
|
||||
export const globalConfig = new GlobalConfig();
|
||||
108
tools/winscope-ng/src/cross_tool/cross_tool_protocol.ts
Normal file
108
tools/winscope-ng/src/cross_tool/cross_tool_protocol.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {Message, MessageBugReport, MessagePong, MessageTimestamp, MessageType} from "./messages";
|
||||
import {
|
||||
CrossToolProtocolDependencyInversion,
|
||||
OnBugreportReceived,
|
||||
OnTimestampReceived} from "cross_tool/cross_tool_protocol_dependency_inversion";
|
||||
import {RealTimestamp} from "common/trace/timestamp";
|
||||
import {FunctionUtils} from "common/utils/function_utils";
|
||||
import {globalConfig} from "common/utils/global_config";
|
||||
|
||||
class CrossToolProtocol implements CrossToolProtocolDependencyInversion {
|
||||
private remoteToolWindow?: Window;
|
||||
private onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
private onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
|
||||
constructor() {
|
||||
window.addEventListener("message", async (event) => {
|
||||
await this.onMessageReceived(event);
|
||||
});
|
||||
}
|
||||
|
||||
setOnBugreportReceived(callback: OnBugreportReceived) {
|
||||
this.onBugreportReceived = callback;
|
||||
}
|
||||
|
||||
setOnTimestampReceived(callback: OnTimestampReceived) {
|
||||
this.onTimestampReceived = callback;
|
||||
}
|
||||
|
||||
sendTimestamp(timestamp: RealTimestamp) {
|
||||
if (!this.remoteToolWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = new MessageTimestamp(timestamp.getValueNs());
|
||||
this.remoteToolWindow.postMessage(message, globalConfig.REMOTE_TOOL_URL);
|
||||
console.log("Cross-tool protocol sent timestamp message:", message);
|
||||
}
|
||||
|
||||
private async onMessageReceived(event: MessageEvent) {
|
||||
if (event.origin !== globalConfig.REMOTE_TOOL_URL) {
|
||||
console.log("Cross-tool protocol ignoring message from unexpected origin.",
|
||||
"Origin:", event.origin, "Message:", event.data);
|
||||
return;
|
||||
}
|
||||
|
||||
this.remoteToolWindow = event.source as Window;
|
||||
|
||||
const message = event.data as Message;
|
||||
if (!message.type) {
|
||||
console.log("Cross-tool protocol received invalid message:", message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(message.type) {
|
||||
case MessageType.PING:
|
||||
console.log("Cross-tool protocol received ping message:", message);
|
||||
(event.source as Window).postMessage(new MessagePong(), globalConfig.REMOTE_TOOL_URL);
|
||||
break;
|
||||
case MessageType.PONG:
|
||||
console.log("Cross-tool protocol received unexpected pong message:", message);
|
||||
break;
|
||||
case MessageType.BUGREPORT:
|
||||
console.log("Cross-tool protocol received bugreport message:", message);
|
||||
await this.onMessageBugreportReceived(message as MessageBugReport);
|
||||
break;
|
||||
case MessageType.TIMESTAMP:
|
||||
console.log("Cross-tool protocol received timestamp message:", message);
|
||||
await this.onMessageTimestampReceived(message as MessageTimestamp);
|
||||
break;
|
||||
case MessageType.FILES:
|
||||
console.log("Cross-tool protocol received unexpected files message", message);
|
||||
break;
|
||||
default:
|
||||
console.log("Cross-tool protocol received unsupported message type:", message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async onMessageBugreportReceived(message: MessageBugReport) {
|
||||
const timestamp = message.timestampNs !== undefined
|
||||
? new RealTimestamp(message.timestampNs)
|
||||
: undefined;
|
||||
this.onBugreportReceived(message.file, timestamp);
|
||||
}
|
||||
|
||||
private async onMessageTimestampReceived(message: MessageTimestamp) {
|
||||
const timestamp = new RealTimestamp(message.timestampNs);
|
||||
await this.onTimestampReceived(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
export {CrossToolProtocol};
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {RealTimestamp} from "common/trace/timestamp";
|
||||
|
||||
export type OnBugreportReceived = (bugreport: File, timestamp?: RealTimestamp) => Promise<void>;
|
||||
export type OnTimestampReceived = (timestamp: RealTimestamp) => Promise<void>;
|
||||
|
||||
export interface CrossToolProtocolDependencyInversion {
|
||||
setOnBugreportReceived(callback: OnBugreportReceived): void;
|
||||
setOnTimestampReceived(callback: OnTimestampReceived): void;
|
||||
sendTimestamp(timestamp: RealTimestamp): void;
|
||||
}
|
||||
39
tools/winscope-ng/src/cross_tool/cross_tool_protocol_stub.ts
Normal file
39
tools/winscope-ng/src/cross_tool/cross_tool_protocol_stub.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {
|
||||
CrossToolProtocolDependencyInversion,
|
||||
OnBugreportReceived,
|
||||
OnTimestampReceived} from "cross_tool/cross_tool_protocol_dependency_inversion";
|
||||
import {RealTimestamp} from "common/trace/timestamp";
|
||||
import {FunctionUtils} from "common/utils/function_utils";
|
||||
|
||||
export class CrossToolProtocolStub implements CrossToolProtocolDependencyInversion {
|
||||
onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
|
||||
setOnBugreportReceived(callback: OnBugreportReceived) {
|
||||
this.onBugreportReceived = callback;
|
||||
}
|
||||
|
||||
setOnTimestampReceived(callback: OnTimestampReceived) {
|
||||
this.onTimestampReceived = callback;
|
||||
}
|
||||
|
||||
sendTimestamp(timestamp: RealTimestamp) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
65
tools/winscope-ng/src/cross_tool/messages.ts
Normal file
65
tools/winscope-ng/src/cross_tool/messages.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 enum MessageType {
|
||||
UNKNOWN = 0,
|
||||
PING,
|
||||
PONG,
|
||||
BUGREPORT,
|
||||
TIMESTAMP,
|
||||
FILES,
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
type: MessageType;
|
||||
}
|
||||
|
||||
export class MessagePing implements Message {
|
||||
type = MessageType.PING;
|
||||
}
|
||||
|
||||
export class MessagePong implements Message {
|
||||
type = MessageType.PONG;
|
||||
}
|
||||
|
||||
export class MessageBugReport implements Message {
|
||||
type = MessageType.BUGREPORT;
|
||||
|
||||
constructor(
|
||||
public file: File,
|
||||
public timestampNs?: bigint,
|
||||
public issueId?: string
|
||||
) {}
|
||||
}
|
||||
|
||||
export class MessageTimestamp implements Message {
|
||||
type = MessageType.TIMESTAMP;
|
||||
|
||||
constructor(
|
||||
public timestampNs: bigint,
|
||||
public sections?: string[]
|
||||
) {}
|
||||
}
|
||||
|
||||
export class MessageFiles implements Message {
|
||||
type = MessageType.FILES;
|
||||
|
||||
constructor(
|
||||
public files: File[],
|
||||
public timestampNs?: bigint,
|
||||
public issueId?: string
|
||||
) {}
|
||||
}
|
||||
@@ -15,6 +15,12 @@
|
||||
*/
|
||||
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
|
||||
import {AppModule} from "./app/app.module";
|
||||
import {globalConfig} from "common/utils/global_config";
|
||||
|
||||
globalConfig.set({
|
||||
MODE: "DEV",
|
||||
REMOTE_TOOL_URL: "http://localhost:8081"
|
||||
});
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
|
||||
160
tools/winscope-ng/src/test/e2e/cross_tool_protocol.spec.ts
Normal file
160
tools/winscope-ng/src/test/e2e/cross_tool_protocol.spec.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {browser, by, element, ElementFinder} from "protractor";
|
||||
import {E2eTestUtils} from "./utils";
|
||||
|
||||
describe("Cross-Tool Protocol", () => {
|
||||
const WINSCOPE_URL = "http://localhost:8080";
|
||||
const REMOTE_TOOL_MOCK_URL = "http://localhost:8081";
|
||||
|
||||
const TIMESTAMP_IN_BUGREPORT_MESSAGE = "1670509911000000000";
|
||||
const TIMESTAMP_FROM_REMOTE_TOOL_TO_WINSCOPE = "1670509912000000000";
|
||||
const TIMESTAMP_FROM_WINSCOPE_TO_REMOTE_TOOL = "1670509913000000000";
|
||||
|
||||
beforeAll(async () => {
|
||||
await browser.manage().timeouts().implicitlyWait(5000);
|
||||
await checkServerIsUp("Remote tool mock", REMOTE_TOOL_MOCK_URL);
|
||||
await checkServerIsUp("Winscope", WINSCOPE_URL);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await browser.get(REMOTE_TOOL_MOCK_URL);
|
||||
});
|
||||
|
||||
it("allows communication between remote tool and Winscope", async () => {
|
||||
await openWinscopeTabFromRemoteTool();
|
||||
await waitWinscopeTabIsOpen();
|
||||
|
||||
await sendBugreportToWinscope();
|
||||
await checkWinscopeRenderedSurfaceFlingerView();
|
||||
await checkWinscopeRenderedAllViewTabs();
|
||||
await checkWinscopeAppliedTimestampInBugreportMessage();
|
||||
|
||||
await sendTimestampToWinscope();
|
||||
await checkWinscopeReceivedTimestamp();
|
||||
|
||||
await changeTimestampInWinscope();
|
||||
await checkRemoteToolReceivedTimestamp();
|
||||
});
|
||||
|
||||
const checkServerIsUp = async (name: string, url: string) => {
|
||||
try {
|
||||
await browser.get(url);
|
||||
} catch (error) {
|
||||
fail(`${name} server (${url}) looks down. Did you start it?`);
|
||||
}
|
||||
};
|
||||
|
||||
const openWinscopeTabFromRemoteTool = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleRemoteToolMock());
|
||||
const buttonElement = element(by.css(".button-open-winscope"));
|
||||
await buttonElement.click();
|
||||
};
|
||||
|
||||
const sendBugreportToWinscope = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleRemoteToolMock());
|
||||
const inputFileElement = element(by.css(".button-upload-bugreport"));
|
||||
await inputFileElement.sendKeys(E2eTestUtils.getFixturePath("bugreports/bugreport_stripped.zip"));
|
||||
};
|
||||
|
||||
const waitWinscopeTabIsOpen = async () => {
|
||||
await browser.wait(async () => {
|
||||
const handles = await browser.getAllWindowHandles();
|
||||
return handles.length >= 2;
|
||||
},
|
||||
20000,
|
||||
"The Winscope tab did not open");
|
||||
};
|
||||
|
||||
const checkWinscopeRenderedSurfaceFlingerView = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleWinscope());
|
||||
const viewerPresent = await element(by.css("viewer-surface-flinger")).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
};
|
||||
|
||||
const checkWinscopeRenderedAllViewTabs = async () => {
|
||||
const linkElements = await element.all(by.css(".tabs-navigation-bar a"));
|
||||
|
||||
const actualLinks = await Promise.all(
|
||||
(linkElements as ElementFinder[]).map(async linkElement => await linkElement.getText())
|
||||
);
|
||||
|
||||
const expectedLinks = [
|
||||
"Input Method Clients",
|
||||
"Input Method Manager Service",
|
||||
"Input Method Service",
|
||||
"ProtoLog",
|
||||
"Surface Flinger",
|
||||
"Transactions",
|
||||
"Window Manager",
|
||||
];
|
||||
|
||||
expect(actualLinks.sort()).toEqual(expectedLinks.sort());
|
||||
};
|
||||
|
||||
const checkWinscopeAppliedTimestampInBugreportMessage = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleWinscope());
|
||||
const inputElement = element(by.css("input[name=\"nsTimeInput\"]"));
|
||||
const valueWithNsSuffix = await inputElement.getAttribute("value");
|
||||
expect(valueWithNsSuffix).toEqual(TIMESTAMP_IN_BUGREPORT_MESSAGE + " ns");
|
||||
};
|
||||
|
||||
const sendTimestampToWinscope = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleRemoteToolMock());
|
||||
const inputElement = element(by.css(".input-timestamp"));
|
||||
await inputElement.sendKeys(TIMESTAMP_FROM_REMOTE_TOOL_TO_WINSCOPE);
|
||||
const buttonElement = element(by.css(".button-send-timestamp"));
|
||||
await buttonElement.click();
|
||||
};
|
||||
|
||||
const checkWinscopeReceivedTimestamp = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleWinscope());
|
||||
const inputElement = element(by.css("input[name=\"nsTimeInput\"]"));
|
||||
const valueWithNsSuffix = await inputElement.getAttribute("value");
|
||||
expect(valueWithNsSuffix).toEqual(TIMESTAMP_FROM_REMOTE_TOOL_TO_WINSCOPE + " ns");
|
||||
};
|
||||
|
||||
const changeTimestampInWinscope = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleWinscope());
|
||||
const inputElement = element(by.css("input[name=\"nsTimeInput\"]"));
|
||||
const inputStringStep1 = TIMESTAMP_FROM_WINSCOPE_TO_REMOTE_TOOL.slice(0, -1);
|
||||
const inputStringStep2 = TIMESTAMP_FROM_WINSCOPE_TO_REMOTE_TOOL.slice(-1) + "\r\n";
|
||||
const script =
|
||||
`document.querySelector("input[name=\\"nsTimeInput\\"]").value = "${inputStringStep1}"`;
|
||||
await browser.executeScript(script);
|
||||
await inputElement.sendKeys(inputStringStep2);
|
||||
};
|
||||
|
||||
const checkRemoteToolReceivedTimestamp = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleRemoteToolMock());
|
||||
const paragraphElement = element(by.css(".paragraph-received-timestamp"));
|
||||
const value = await paragraphElement.getText();
|
||||
expect(value).toEqual(TIMESTAMP_FROM_WINSCOPE_TO_REMOTE_TOOL);
|
||||
};
|
||||
|
||||
const getWindowHandleRemoteToolMock = async (): Promise<string> => {
|
||||
const handles = await browser.getAllWindowHandles();
|
||||
expect(handles.length).toBeGreaterThan(0);
|
||||
return handles[0];
|
||||
};
|
||||
|
||||
const getWindowHandleWinscope = async (): Promise<string> => {
|
||||
const handles = await browser.getAllWindowHandles();
|
||||
expect(handles.length).toEqual(2);
|
||||
return handles[1];
|
||||
};
|
||||
});
|
||||
201
tools/winscope-ng/src/test/remote_tool_mock/app.component.ts
Normal file
201
tools/winscope-ng/src/test/remote_tool_mock/app.component.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {ChangeDetectorRef, Component, Inject} from "@angular/core";
|
||||
import {
|
||||
Message,
|
||||
MessageBugReport,
|
||||
MessagePing,
|
||||
MessageTimestamp,
|
||||
MessageType} from "cross_tool/messages";
|
||||
import {FunctionUtils} from "common/utils/function_utils";
|
||||
|
||||
@Component({
|
||||
selector: "app-root",
|
||||
template: `
|
||||
<span class="app-title">Remote Tool Mock (simulates cross-tool protocol)</span>
|
||||
|
||||
<hr>
|
||||
<p>Open Winscope tab</p>
|
||||
<input class="button-open-winscope" type="button" value="Open"
|
||||
(click)="onButtonOpenWinscopeClick()">
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
Send bugreport
|
||||
</p>
|
||||
<input class="button-upload-bugreport" type="file" value=""
|
||||
(change)="onUploadBugreport($event)">
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
Send timestamp [ns]
|
||||
</p>
|
||||
<input class="input-timestamp" type="number" id="name" name="name">
|
||||
<input class="button-send-timestamp" type="button" value="Send"
|
||||
(click)="onButtonSendTimestampClick()">
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
Received timestamp:
|
||||
</p>
|
||||
<p class="paragraph-received-timestamp">
|
||||
</p>
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
static readonly TARGET = "http://localhost:8080";
|
||||
static readonly TIMESTAMP_IN_BUGREPORT_MESSAGE = 1670509911000000000n;
|
||||
|
||||
private winscope: WindowProxy|null = null;
|
||||
private isWinscopeUp = false;
|
||||
private onMessagePongReceived = FunctionUtils.DO_NOTHING;
|
||||
|
||||
constructor(@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef) {
|
||||
window.addEventListener("message", (event) => {
|
||||
this.onMessageReceived(event);
|
||||
});
|
||||
}
|
||||
|
||||
public async onButtonOpenWinscopeClick() {
|
||||
this.openWinscope();
|
||||
await this.waitWinscopeUp();
|
||||
}
|
||||
|
||||
public async onUploadBugreport(event: Event) {
|
||||
const [file, buffer] = await this.readInputFile(event);
|
||||
this.sendBugreport(file, buffer);
|
||||
}
|
||||
|
||||
public onButtonSendTimestampClick() {
|
||||
const inputTimestampElement =
|
||||
document.querySelector(".input-timestamp")! as HTMLInputElement;
|
||||
this.sendTimestamp(BigInt(inputTimestampElement.value));
|
||||
}
|
||||
|
||||
private openWinscope() {
|
||||
this.printStatus("OPENING WINSCOPE");
|
||||
|
||||
this.winscope = window.open(AppComponent.TARGET);
|
||||
if (!this.winscope) {
|
||||
throw new Error("Failed to open winscope");
|
||||
}
|
||||
|
||||
this.printStatus("OPENED WINSCOPE");
|
||||
}
|
||||
|
||||
private async waitWinscopeUp() {
|
||||
this.printStatus("WAITING WINSCOPE UP");
|
||||
|
||||
const promise = new Promise<void>((resolve) => {
|
||||
this.onMessagePongReceived = () => {
|
||||
this.isWinscopeUp = true;
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
setTimeout(async () => {
|
||||
while (!this.isWinscopeUp) {
|
||||
this.winscope!.postMessage(
|
||||
new MessagePing(),
|
||||
AppComponent.TARGET
|
||||
);
|
||||
await this.sleep(10);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
await promise;
|
||||
|
||||
this.printStatus("DONE WAITING (WINSCOPE IS UP)");
|
||||
}
|
||||
|
||||
private sendBugreport(file: File, buffer: ArrayBuffer) {
|
||||
this.printStatus("SENDING BUGREPORT");
|
||||
|
||||
this.winscope!.postMessage(
|
||||
new MessageBugReport(file, AppComponent.TIMESTAMP_IN_BUGREPORT_MESSAGE),
|
||||
AppComponent.TARGET
|
||||
);
|
||||
|
||||
this.printStatus("SENT BUGREPORT");
|
||||
}
|
||||
|
||||
private sendTimestamp(value: bigint) {
|
||||
this.printStatus("SENDING TIMESTAMP");
|
||||
|
||||
this.winscope!.postMessage(
|
||||
new MessageTimestamp(value),
|
||||
AppComponent.TARGET
|
||||
);
|
||||
|
||||
this.printStatus("SENT TIMESTAMP");
|
||||
}
|
||||
|
||||
private onMessageReceived(event: MessageEvent) {
|
||||
const message = event.data as Message;
|
||||
if (!message.type) {
|
||||
console.log("Cross-tool protocol received unrecognized message:", message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case MessageType.PING:
|
||||
console.log("Cross-tool protocol received unexpected ping message:", message);
|
||||
break;
|
||||
case MessageType.PONG:
|
||||
this.onMessagePongReceived();
|
||||
break;
|
||||
case MessageType.BUGREPORT:
|
||||
console.log("Cross-tool protocol received unexpected bugreport message:", message);
|
||||
break;
|
||||
case MessageType.TIMESTAMP:
|
||||
console.log("Cross-tool protocol received timestamp message:", message);
|
||||
this.onMessageTimestampReceived(message as MessageTimestamp);
|
||||
break;
|
||||
case MessageType.FILES:
|
||||
console.log("Cross-tool protocol received unexpected files message:", message);
|
||||
break;
|
||||
default:
|
||||
console.log("Cross-tool protocol received unrecognized message:", message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private onMessageTimestampReceived(message: MessageTimestamp) {
|
||||
const paragraph =
|
||||
document.querySelector(".paragraph-received-timestamp") as HTMLParagraphElement;
|
||||
paragraph.textContent = message.timestampNs.toString();
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
private printStatus(status: string) {
|
||||
console.log("STATUS: " + status);
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise( resolve => setTimeout(resolve, ms) );
|
||||
}
|
||||
|
||||
private async readInputFile(event: Event): Promise<[File, ArrayBuffer]> {
|
||||
const files: FileList|null = (event?.target as HTMLInputElement)?.files;
|
||||
|
||||
if (!files || !files[0]) {
|
||||
throw new Error("Failed to read input files");
|
||||
}
|
||||
|
||||
return [files[0], await files[0].arrayBuffer()];
|
||||
}
|
||||
}
|
||||
35
tools/winscope-ng/src/test/remote_tool_mock/app.module.ts
Normal file
35
tools/winscope-ng/src/test/remote_tool_mock/app.module.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {CommonModule} from "@angular/common";
|
||||
import {NgModule} from "@angular/core";
|
||||
import {BrowserModule} from "@angular/platform-browser";
|
||||
import {AppComponent} from "./app.component";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
CommonModule,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
class AppModule {
|
||||
}
|
||||
|
||||
export {AppModule};
|
||||
25
tools/winscope-ng/src/test/remote_tool_mock/index.html
Normal file
25
tools/winscope-ng/src/test/remote_tool_mock/index.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
Copyright (C) 2022 The Android Open Source Project
|
||||
|
||||
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.
|
||||
-->
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ABT Mock</title>
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
20
tools/winscope-ng/src/test/remote_tool_mock/main.ts
Normal file
20
tools/winscope-ng/src/test/remote_tool_mock/main.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
|
||||
import {AppModule} from "./app.module";
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
53
tools/winscope-ng/src/test/remote_tool_mock/polyfills.ts
Normal file
53
tools/winscope-ng/src/test/remote_tool_mock/polyfills.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes recent versions of Safari, Chrome (including
|
||||
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import "zone.js"; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
||||
|
||||
module.exports = {
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".css"],
|
||||
modules: [
|
||||
__dirname + "/../../../node_modules",
|
||||
__dirname + "/../../../src",
|
||||
__dirname
|
||||
]
|
||||
},
|
||||
|
||||
module: {
|
||||
rules:[
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: ["ts-loader", "angular2-template-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: ["html-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", "css-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ["style-loader", "css-loader", "sass-loader"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
mode: "development",
|
||||
|
||||
entry: {
|
||||
polyfills: __dirname + "/polyfills.ts",
|
||||
app: __dirname + "/main.ts"
|
||||
},
|
||||
|
||||
output: {
|
||||
path: __dirname + "/../../../dist/remote_tool_mock",
|
||||
publicPath: "/",
|
||||
filename: "js/[name].[hash].js",
|
||||
chunkFilename: "js/[name].[id].[hash].chunk.js",
|
||||
},
|
||||
|
||||
devtool: "source-map",
|
||||
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: __dirname + "/index.html",
|
||||
inject: "body",
|
||||
inlineSource: ".(css|js)$",
|
||||
}),
|
||||
new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
|
||||
]
|
||||
};
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
@@ -63,14 +62,6 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: "src/index.html",
|
||||
inject: "body",
|
||||
inlineSource: ".(css|js)$",
|
||||
})
|
||||
],
|
||||
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
const {merge} = require("webpack-merge");
|
||||
const configCommon = require("./webpack.config.common");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
|
||||
const configDev = {
|
||||
mode: "development",
|
||||
@@ -27,6 +28,13 @@ const configDev = {
|
||||
app: "./src/main.dev.ts"
|
||||
},
|
||||
devtool: "source-map",
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: "src/index.html",
|
||||
inject: "body",
|
||||
inlineSource: ".(css|js)$",
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = merge(configCommon, configDev);
|
||||
|
||||
@@ -60,6 +60,11 @@ const configProd = {
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: "src/index.html",
|
||||
inject: "body",
|
||||
inlineSource: ".(css|js)$",
|
||||
}),
|
||||
new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
|
||||
]
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user