[DO NOT MERGE] Sync flicker from master to sc-v2
Flicker on master diverged form sc-v2, to make it easier to debug flicker issues on sc-v2, push the current version of flicker into sc-v2 Test: atest FlickerTests WMShellFlickerTests Bug: 183993924 Change-Id: I0bd06578e7c93271d4f84361c15818385a8a4fdb
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"typescript": "^4.3.5",
|
||||
"vue": "^2.6.14",
|
||||
"vue-context": "^6.0.0",
|
||||
"vue-gtag": "^1.16.1",
|
||||
"vue-material": "^1.0.0-beta-15",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { decodeAndTransformProto, FILE_TYPES, FILE_DECODERS } from '../src/decode';
|
||||
import Tag from '../src/flickerlib/tags/Tag';
|
||||
import Error from '../src/flickerlib/errors/Error';
|
||||
import { TaggingEngine } from '../src/flickerlib/common.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
@@ -33,6 +34,26 @@ describe("Tag Transformation", () => {
|
||||
})
|
||||
});
|
||||
|
||||
describe("Detect Tag", () => {
|
||||
it("can detect tags", () => {
|
||||
const wmFile = '../spec/traces/regular_rotation_in_last_state_wm_trace.winscope'
|
||||
const layersFile = '../spec/traces/regular_rotation_in_last_state_layers_trace.winscope'
|
||||
const wmBuffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, wmFile)));
|
||||
const layersBuffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, layersFile)));
|
||||
|
||||
const wmTrace = decodeAndTransformProto(wmBuffer, FILE_DECODERS[FILE_TYPES.WINDOW_MANAGER_TRACE].decoderParams, true);
|
||||
const layersTrace = decodeAndTransformProto(layersBuffer, FILE_DECODERS[FILE_TYPES.SURFACE_FLINGER_TRACE].decoderParams, true);
|
||||
|
||||
const engine = new TaggingEngine(wmTrace, layersTrace, (text) => { console.log(text) });
|
||||
const tagTrace = engine.run();
|
||||
expect(tagTrace.size).toEqual(4);
|
||||
expect(tagTrace.entries[0].timestamp.toString()).toEqual('280186737540384');
|
||||
expect(tagTrace.entries[1].timestamp.toString()).toEqual('280187243649340');
|
||||
expect(tagTrace.entries[2].timestamp.toString()).toEqual('280188522078113');
|
||||
expect(tagTrace.entries[3].timestamp.toString()).toEqual('280189020672174');
|
||||
})
|
||||
});
|
||||
|
||||
describe("Error Transformation", () => {
|
||||
it("can transform error traces", () => {
|
||||
const buffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, errorTrace)));
|
||||
@@ -47,4 +68,4 @@ describe("Error Transformation", () => {
|
||||
expect(data.entries[1].errors).toEqual([new Error("","",66,"",66)]);
|
||||
expect(data.entries[2].errors).toEqual([new Error("","",99,"",99)]);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -19,6 +19,11 @@
|
||||
<h1 class="md-title" style="flex: 1">{{title}}</h1>
|
||||
<md-button
|
||||
class="md-primary md-theme-default download-all-btn"
|
||||
@click="generateTags()"
|
||||
v-if="dataLoaded && canGenerateTags"
|
||||
>Generate Tags</md-button>
|
||||
<md-button
|
||||
class="md-primary md-theme-default"
|
||||
@click="downloadAsZip(files)"
|
||||
v-if="dataLoaded"
|
||||
>Download All</md-button>
|
||||
@@ -62,7 +67,6 @@
|
||||
<overlay
|
||||
:presentTags="Object.freeze(presentTags)"
|
||||
:presentErrors="Object.freeze(presentErrors)"
|
||||
:tagAndErrorTraces="tagAndErrorTraces"
|
||||
:store="store"
|
||||
:ref="overlayRef"
|
||||
:searchTypes="searchTypes"
|
||||
@@ -86,6 +90,8 @@ import FocusedDataViewFinder from './mixins/FocusedDataViewFinder';
|
||||
import {DIRECTION} from './utils/utils';
|
||||
import Searchbar from './Searchbar.vue';
|
||||
import {NAVIGATION_STYLE, SEARCH_TYPE} from './utils/consts';
|
||||
import {TRACE_TYPES, FILE_TYPES, dataFile} from './decode.js';
|
||||
import { TaggingEngine } from './flickerlib/common';
|
||||
|
||||
const APP_NAME = 'Winscope';
|
||||
|
||||
@@ -107,15 +113,17 @@ export default {
|
||||
navigationStyle: NAVIGATION_STYLE.GLOBAL,
|
||||
flickerTraceView: false,
|
||||
showFileTypes: [],
|
||||
isInputMode: false,
|
||||
}),
|
||||
overlayRef: 'overlay',
|
||||
mainContentStyle: {
|
||||
'padding-bottom': `${CONTENT_BOTTOM_PADDING}px`,
|
||||
},
|
||||
tagFile: null,
|
||||
presentTags: [],
|
||||
presentErrors: [],
|
||||
searchTypes: [SEARCH_TYPE.TIMESTAMP],
|
||||
tagAndErrorTraces: false,
|
||||
hasTagOrErrorTraces: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -139,11 +147,14 @@ export default {
|
||||
},
|
||||
/** Get tags from all uploaded tag files*/
|
||||
getUpdatedTags() {
|
||||
var tagStates = this.getUpdatedStates(this.tagFiles);
|
||||
if (this.tagFile === null) return [];
|
||||
const tagStates = this.getUpdatedStates([this.tagFile]);
|
||||
var tags = [];
|
||||
tagStates.forEach(tagState => {
|
||||
tagState.tags.forEach(tag => {
|
||||
tag.timestamp = tagState.timestamp;
|
||||
tag.timestamp = Number(tagState.timestamp);
|
||||
// tags generated on frontend have transition.name due to kotlin enum
|
||||
tag.transition = tag.transition.name ?? tag.transition;
|
||||
tags.push(tag);
|
||||
});
|
||||
});
|
||||
@@ -156,20 +167,31 @@ export default {
|
||||
//TODO (b/196201487) add check if errors empty
|
||||
errorStates.forEach(errorState => {
|
||||
errorState.errors.forEach(error => {
|
||||
error.timestamp = errorState.timestamp;
|
||||
error.timestamp = Number(errorState.timestamp);
|
||||
errors.push(error);
|
||||
});
|
||||
});
|
||||
return errors;
|
||||
},
|
||||
/** Set flicker mode check for if there are tag/error traces uploaded*/
|
||||
shouldUpdateTagAndErrorTraces() {
|
||||
return this.tagFiles.length > 0 || this.errorFiles.length > 0;
|
||||
updateHasTagOrErrorTraces() {
|
||||
return this.hasTagTrace() || this.hasErrorTrace();
|
||||
},
|
||||
hasTagTrace() {
|
||||
return this.tagFile !== null;
|
||||
},
|
||||
hasErrorTrace() {
|
||||
return this.errorFiles.length > 0;
|
||||
},
|
||||
/** Activate flicker search tab if tags/errors uploaded*/
|
||||
updateSearchTypes() {
|
||||
this.searchTypes = [SEARCH_TYPE.TIMESTAMP];
|
||||
if (this.tagAndErrorTraces) this.searchTypes.push(SEARCH_TYPE.TAG);
|
||||
if (this.hasTagTrace()) {
|
||||
this.searchTypes.push(SEARCH_TYPE.TRANSITIONS);
|
||||
}
|
||||
if (this.hasErrorTrace()) {
|
||||
this.searchTypes.push(SEARCH_TYPE.ERRORS);
|
||||
}
|
||||
},
|
||||
/** Filter data view files by current show settings*/
|
||||
updateShowFileTypes() {
|
||||
@@ -179,7 +201,9 @@ export default {
|
||||
},
|
||||
clear() {
|
||||
this.store.showFileTypes = [];
|
||||
this.tagFile = null;
|
||||
this.$store.commit('clearFiles');
|
||||
this.buttonClicked("Clear")
|
||||
},
|
||||
onDataViewFocus(file) {
|
||||
this.$store.commit('setActiveFile', file);
|
||||
@@ -187,6 +211,7 @@ export default {
|
||||
},
|
||||
onKeyDown(event) {
|
||||
event = event || window.event;
|
||||
if (this.store.isInputMode) return false;
|
||||
if (event.keyCode == 37 /* left */ ) {
|
||||
this.$store.dispatch('advanceTimeline', DIRECTION.BACKWARD);
|
||||
} else if (event.keyCode == 39 /* right */ ) {
|
||||
@@ -203,7 +228,9 @@ export default {
|
||||
},
|
||||
onDataReady(files) {
|
||||
this.$store.dispatch('setFiles', files);
|
||||
this.tagAndErrorTraces = this.shouldUpdateTagAndErrorTraces();
|
||||
|
||||
this.tagFile = this.tagFiles[0] ?? null;
|
||||
this.hasTagOrErrorTraces = this.updateHasTagOrErrorTraces();
|
||||
this.presentTags = this.getUpdatedTags();
|
||||
this.presentErrors = this.getUpdatedErrors();
|
||||
this.updateSearchTypes();
|
||||
@@ -224,11 +251,43 @@ export default {
|
||||
`${ CONTENT_BOTTOM_PADDING + newHeight }px`,
|
||||
);
|
||||
},
|
||||
generateTags() {
|
||||
// generate tag file
|
||||
this.buttonClicked("Generate Tags");
|
||||
const engine = new TaggingEngine(
|
||||
this.$store.getters.tagGenerationWmTrace,
|
||||
this.$store.getters.tagGenerationSfTrace,
|
||||
(text) => { console.log(text) }
|
||||
);
|
||||
const tagTrace = engine.run();
|
||||
const tagFile = this.generateTagFile(tagTrace);
|
||||
|
||||
// update tag trace in set files, update flicker mode
|
||||
this.tagFile = tagFile;
|
||||
this.hasTagOrErrorTraces = this.updateHasTagOrErrorTraces();
|
||||
this.presentTags = this.getUpdatedTags();
|
||||
this.presentErrors = this.getUpdatedErrors();
|
||||
this.updateSearchTypes();
|
||||
},
|
||||
|
||||
generateTagFile(tagTrace) {
|
||||
const data = tagTrace.entries;
|
||||
const blobUrl = URL.createObjectURL(new Blob([], {type: undefined}));
|
||||
return dataFile(
|
||||
"GeneratedTagTrace.winscope",
|
||||
data.map((x) => x.timestamp),
|
||||
data,
|
||||
blobUrl,
|
||||
FILE_TYPES.TAG_TRACE
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
files() {
|
||||
return this.$store.getters.sortedFiles.map(file => {
|
||||
if (this.hasDataView(file)) file.show = true;
|
||||
if (this.hasDataView(file)) {
|
||||
file.show = true;
|
||||
}
|
||||
return file;
|
||||
});
|
||||
},
|
||||
@@ -257,6 +316,11 @@ export default {
|
||||
timelineFiles() {
|
||||
return this.$store.getters.timelineFiles;
|
||||
},
|
||||
canGenerateTags() {
|
||||
const fileTypes = this.dataViewFiles.map((file) => file.type);
|
||||
return fileTypes.includes(TRACE_TYPES.WINDOW_MANAGER)
|
||||
&& fileTypes.includes(TRACE_TYPES.SURFACE_FLINGER);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
title() {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<p>Or get it from the AOSP repository.</p>
|
||||
</div>
|
||||
<div class="md-layout">
|
||||
<md-button class="md-accent" :href="downloadProxyUrl">Download from AOSP</md-button>
|
||||
<md-button class="md-accent" :href="downloadProxyUrl" @click="buttonClicked(`Download from AOSP`)">Download from AOSP</md-button>
|
||||
<md-button class="md-accent" @click="restart">Retry</md-button>
|
||||
</div>
|
||||
</md-card-content>
|
||||
@@ -306,8 +306,10 @@ export default {
|
||||
if (requested.length < 1) {
|
||||
this.errorText = 'No targets selected';
|
||||
this.status = STATES.ERROR;
|
||||
this.newEventOccurred("No targets selected");
|
||||
return;
|
||||
}
|
||||
this.newEventOccurred("Start Trace");
|
||||
this.callProxy('POST', PROXY_ENDPOINTS.CONFIG_TRACE + this.deviceId() + '/', this, null, null, requestedConfig);
|
||||
this.status = STATES.END_TRACE;
|
||||
this.callProxy('POST', PROXY_ENDPOINTS.START_TRACE + this.deviceId() + '/', this, function(request, view) {
|
||||
@@ -315,10 +317,12 @@ export default {
|
||||
}, null, requested);
|
||||
},
|
||||
dumpState() {
|
||||
this.buttonClicked("Dump State");
|
||||
const requested = this.toDump();
|
||||
if (requested.length < 1) {
|
||||
this.errorText = 'No targets selected';
|
||||
this.status = STATES.ERROR;
|
||||
this.newEventOccurred("No targets selected");
|
||||
return;
|
||||
}
|
||||
this.status = STATES.LOAD_DATA;
|
||||
@@ -331,6 +335,7 @@ export default {
|
||||
this.callProxy('POST', PROXY_ENDPOINTS.END_TRACE + this.deviceId() + '/', this, function(request, view) {
|
||||
view.loadFile(view.toTrace(), 0);
|
||||
});
|
||||
this.newEventOccurred("Ended Trace");
|
||||
},
|
||||
loadFile(files, idx) {
|
||||
this.callProxy('GET', PROXY_ENDPOINTS.FETCH + this.deviceId() + '/' + files[idx] + '/', this, function(request, view) {
|
||||
@@ -388,9 +393,11 @@ export default {
|
||||
return this.selectedDevice;
|
||||
},
|
||||
restart() {
|
||||
this.buttonClicked("Connect / Retry");
|
||||
this.status = STATES.CONNECTING;
|
||||
},
|
||||
resetLastDevice() {
|
||||
this.buttonClicked("Change Device");
|
||||
this.adbStore.lastDevice = '';
|
||||
this.restart();
|
||||
},
|
||||
|
||||
@@ -19,25 +19,41 @@
|
||||
<div class="md-title">Open files</div>
|
||||
</md-card-header>
|
||||
<md-card-content>
|
||||
<md-list>
|
||||
<md-list-item v-for="file in dataFiles" v-bind:key="file.filename">
|
||||
<md-icon>{{FILE_ICONS[file.type]}}</md-icon>
|
||||
<span class="md-list-item-text">{{file.filename}} ({{file.type}})
|
||||
</span>
|
||||
<md-button
|
||||
class="md-icon-button md-accent"
|
||||
@click="onRemoveFile(file.type)"
|
||||
>
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
<md-progress-spinner
|
||||
:md-diameter="30"
|
||||
:md-stroke="3"
|
||||
md-mode="indeterminate"
|
||||
v-show="loadingFiles"
|
||||
/>
|
||||
<div class="dropbox">
|
||||
<md-list style="background: none">
|
||||
<md-list-item v-for="file in dataFiles" v-bind:key="file.filename">
|
||||
<md-icon>{{FILE_ICONS[file.type]}}</md-icon>
|
||||
<span class="md-list-item-text">{{file.filename}} ({{file.type}})
|
||||
</span>
|
||||
<md-button
|
||||
class="md-icon-button md-accent"
|
||||
@click="onRemoveFile(file.type)"
|
||||
>
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
<md-progress-spinner
|
||||
:md-diameter="30"
|
||||
:md-stroke="3"
|
||||
md-mode="indeterminate"
|
||||
v-show="loadingFiles"
|
||||
class="progress-spinner"
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
@change="onLoadFile"
|
||||
v-on:drop="handleFileDrop"
|
||||
ref="fileUpload"
|
||||
id="dropzone"
|
||||
v-show="false"
|
||||
multiple
|
||||
/>
|
||||
<p v-if="!dataReady">
|
||||
Drag your <b>.winscope</b> or <b>.zip</b> file(s) here to begin
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="md-layout">
|
||||
<div class="md-layout-item md-small-size-100">
|
||||
<md-field>
|
||||
@@ -53,13 +69,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-layout">
|
||||
<input
|
||||
type="file"
|
||||
@change="onLoadFile"
|
||||
ref="fileUpload"
|
||||
v-show="false"
|
||||
:multiple="fileType === 'auto'"
|
||||
/>
|
||||
<md-button
|
||||
class="md-primary md-theme-default"
|
||||
@click="$refs.fileUpload.click()"
|
||||
@@ -143,6 +152,7 @@ export default {
|
||||
},
|
||||
hideSnackbarMessage() {
|
||||
this.showSnackbar = false;
|
||||
this.buttonClicked("Hide Snackbar Message")
|
||||
},
|
||||
getFetchFilesLoadingAnimation() {
|
||||
let frame = 0;
|
||||
@@ -226,20 +236,24 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
fileDragIn(e){
|
||||
fileDragIn(e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
fileDragOut(e){
|
||||
fileDragOut(e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
handleFileDrop(e) {
|
||||
e.preventDefault();
|
||||
let droppedFiles = e.dataTransfer.files;
|
||||
if(!droppedFiles) return;
|
||||
// Record analytics event
|
||||
this.draggedAndDropped(droppedFiles);
|
||||
|
||||
this.processFiles(droppedFiles);
|
||||
},
|
||||
onLoadFile(e) {
|
||||
const files = event.target.files || event.dataTransfer.files;
|
||||
this.uploadedFileThroughFilesystem(files);
|
||||
this.processFiles(files);
|
||||
},
|
||||
async processFiles(files) {
|
||||
@@ -534,4 +548,30 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
</script>
|
||||
<style>
|
||||
.dropbox:hover {
|
||||
background: rgb(224, 224, 224);
|
||||
}
|
||||
|
||||
.dropbox p {
|
||||
font-size: 1.2em;
|
||||
text-align: center;
|
||||
padding: 50px 10px;
|
||||
}
|
||||
|
||||
.dropbox {
|
||||
outline: 2px dashed #448aff; /* the dash box */
|
||||
outline-offset: -10px;
|
||||
background: white;
|
||||
color: #448aff;
|
||||
padding: 10px 10px 10px 10px;
|
||||
min-height: 200px; /* minimum height */
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.progress-spinner {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -16,13 +16,13 @@
|
||||
<div @click="onClick($event)">
|
||||
<flat-card v-if="hasDataView(file)">
|
||||
<md-card-header>
|
||||
<button class="toggle-view-button" @click="toggleView">
|
||||
<i aria-hidden="true" class="md-icon md-theme-default material-icons">
|
||||
{{ isShowFileType(file.type) ? "expand_more" : "chevron_right" }}
|
||||
</i>
|
||||
</button>
|
||||
<md-card-header-text>
|
||||
<div class="md-title">
|
||||
<button class="toggle-view-button" @click="toggleView">
|
||||
<i aria-hidden="true" class="md-icon md-theme-default material-icons">
|
||||
{{ isShowFileType(file.type) ? "expand_more" : "chevron_right" }}
|
||||
</i>
|
||||
</button>
|
||||
<md-icon>{{ TRACE_ICONS[file.type] }}</md-icon>
|
||||
{{ file.type }}
|
||||
</div>
|
||||
@@ -160,6 +160,7 @@ export default {
|
||||
// Pass click event to parent, so that click event handler can be attached
|
||||
// to component.
|
||||
this.$emit('click', e);
|
||||
this.newEventOccurred(e.toString());
|
||||
},
|
||||
/** Filter data view files by current show settings */
|
||||
updateShowFileTypes() {
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
<div class="flicker-tags" v-for="error in errors" :key="error.message">
|
||||
<Arrow class="error-arrow"/>
|
||||
<md-tooltip md-direction="right"> Error: {{error.message}} </md-tooltip>
|
||||
<md-tooltip md-direction="right"> {{errorTooltip(error.message)}} </md-tooltip>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
@@ -80,6 +80,12 @@ export default {
|
||||
transitionTooltip(transition) {
|
||||
return transitionMap.get(transition).desc;
|
||||
},
|
||||
errorTooltip(errorMessage) {
|
||||
if (errorMessage.length>100) {
|
||||
return `Error: ${errorMessage.substring(0,100)}...`;
|
||||
}
|
||||
return `Error: ${errorMessage}`;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Arrow,
|
||||
|
||||
@@ -65,6 +65,13 @@
|
||||
md-elevation="0"
|
||||
class="md-transparent">
|
||||
|
||||
<md-button
|
||||
@click="toggleSearch()"
|
||||
class="drop-search"
|
||||
>
|
||||
Toggle search bar
|
||||
</md-button>
|
||||
|
||||
<div class="toolbar" :class="{ expanded: expanded }">
|
||||
<div class="resize-bar" v-show="expanded">
|
||||
<div v-if="video" @mousedown="resizeBottomNav">
|
||||
@@ -75,11 +82,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<md-button
|
||||
@click="toggleSearch()"
|
||||
class="drop-search"
|
||||
>Show/hide search bar</md-button>
|
||||
|
||||
<div class="active-timeline" v-show="minimized">
|
||||
<div
|
||||
class="active-timeline-icon"
|
||||
@@ -149,14 +151,20 @@
|
||||
v-show="minimized"
|
||||
v-if="hasTimeline"
|
||||
>
|
||||
<label>
|
||||
{{ seekTime }}
|
||||
</label>
|
||||
<input
|
||||
class="timestamp-search-input"
|
||||
v-model="searchInput"
|
||||
spellcheck="false"
|
||||
:placeholder="seekTime"
|
||||
@focus="updateInputMode(true)"
|
||||
@blur="updateInputMode(false)"
|
||||
@keyup.enter="updateSearchForTimestamp"
|
||||
/>
|
||||
<timeline
|
||||
:store="store"
|
||||
:flickerMode="flickerMode"
|
||||
:tags="Object.freeze(tags)"
|
||||
:errors="Object.freeze(errors)"
|
||||
:tags="Object.freeze(presentTags)"
|
||||
:errors="Object.freeze(presentErrors)"
|
||||
:timeline="Object.freeze(minimizedTimeline.timeline)"
|
||||
:selected-index="minimizedTimeline.selectedIndex"
|
||||
:scale="scale"
|
||||
@@ -188,11 +196,11 @@
|
||||
>
|
||||
<md-icon v-if="minimized">
|
||||
expand_less
|
||||
<md-tooltip md-direction="top">Expand timeline</md-tooltip>
|
||||
<md-tooltip md-direction="top" @click="buttonClicked(`Expand Timeline`)">Expand timeline</md-tooltip>
|
||||
</md-icon>
|
||||
<md-icon v-else>
|
||||
expand_more
|
||||
<md-tooltip md-direction="top">Collapse timeline</md-tooltip>
|
||||
<md-tooltip md-direction="top" @click="buttonClicked(`Collapse Timeline`)">Collapse timeline</md-tooltip>
|
||||
</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
@@ -213,7 +221,17 @@
|
||||
:style="`padding-top: ${resizeOffset}px;`"
|
||||
>
|
||||
<div class="seek-time" v-if="seekTime">
|
||||
<b>Seek time</b>: {{ seekTime }}
|
||||
<b>Seek time: </b>
|
||||
<input
|
||||
class="timestamp-search-input"
|
||||
:class="{ expanded: expanded }"
|
||||
v-model="searchInput"
|
||||
spellcheck="false"
|
||||
:placeholder="seekTime"
|
||||
@focus="updateInputMode(true)"
|
||||
@blur="updateInputMode(false)"
|
||||
@keyup.enter="updateSearchForTimestamp"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<timelines
|
||||
@@ -283,14 +301,14 @@ import MdIconOption from './components/IconSelection/IconSelectOption.vue';
|
||||
import Searchbar from './Searchbar.vue';
|
||||
import FileType from './mixins/FileType.js';
|
||||
import {NAVIGATION_STYLE} from './utils/consts';
|
||||
import {TRACE_ICONS, FILE_TYPES} from '@/decode.js';
|
||||
import {TRACE_ICONS} from '@/decode.js';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
import {nanos_to_string} from './transform.js';
|
||||
import {nanos_to_string, getClosestTimestamp} from './transform.js';
|
||||
|
||||
export default {
|
||||
name: 'overlay',
|
||||
props: ['store', 'presentTags', 'presentErrors', 'tagAndErrorTraces', 'searchTypes'],
|
||||
props: ['store', 'presentTags', 'presentErrors', 'searchTypes'],
|
||||
mixins: [FileType],
|
||||
data() {
|
||||
return {
|
||||
@@ -312,8 +330,8 @@ export default {
|
||||
cropIntent: null,
|
||||
TRACE_ICONS,
|
||||
search: false,
|
||||
tags: [],
|
||||
errors: [],
|
||||
searchInput: "",
|
||||
isSeekTimeInputMode: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -326,6 +344,7 @@ export default {
|
||||
},
|
||||
destroyed() {
|
||||
this.$store.commit('removeMergedTimeline', this.mergedTimeline);
|
||||
this.updateInputMode(false);
|
||||
},
|
||||
watch: {
|
||||
navigationStyle(style) {
|
||||
@@ -433,8 +452,6 @@ export default {
|
||||
}
|
||||
},
|
||||
minimizedTimeline() {
|
||||
this.updateFlickerMode(this.navigationStyle);
|
||||
|
||||
if (this.navigationStyle === NAVIGATION_STYLE.GLOBAL) {
|
||||
return this.mergedTimeline;
|
||||
}
|
||||
@@ -471,7 +488,7 @@ export default {
|
||||
return this.timelineFiles.length > 1;
|
||||
},
|
||||
flickerMode() {
|
||||
return this.tags.length>0 || this.errors.length>0;
|
||||
return this.presentTags.length>0 || this.presentErrors.length>0;
|
||||
},
|
||||
},
|
||||
updated() {
|
||||
@@ -486,7 +503,29 @@ export default {
|
||||
methods: {
|
||||
toggleSearch() {
|
||||
this.search = !(this.search);
|
||||
this.buttonClicked("Toggle Search Bar");
|
||||
},
|
||||
/**
|
||||
* determines whether left/right arrow keys should move cursor in input field
|
||||
* and upon click of input field, fills with current timestamp
|
||||
*/
|
||||
updateInputMode(isInputMode) {
|
||||
this.isSeekTimeInputMode = isInputMode;
|
||||
this.store.isInputMode = isInputMode;
|
||||
if (!isInputMode) {
|
||||
this.searchInput = "";
|
||||
} else {
|
||||
this.searchInput = this.seekTime;
|
||||
}
|
||||
},
|
||||
/** Navigates to closest timestamp in timeline to search input*/
|
||||
updateSearchForTimestamp() {
|
||||
const closestTimestamp = getClosestTimestamp(this.searchInput, this.mergedTimeline.timeline);
|
||||
this.$store.dispatch("updateTimelineTime", closestTimestamp);
|
||||
this.updateInputMode(false);
|
||||
this.newEventOccurred("Searching for timestamp")
|
||||
},
|
||||
|
||||
emitBottomHeightUpdate() {
|
||||
if (this.$refs.bottomNav) {
|
||||
const newHeight = this.$refs.bottomNav.$el.clientHeight;
|
||||
@@ -599,12 +638,15 @@ export default {
|
||||
},
|
||||
closeVideoOverlay() {
|
||||
this.showVideoOverlay = false;
|
||||
this.buttonClicked("Close Video Overlay")
|
||||
},
|
||||
openVideoOverlay() {
|
||||
this.showVideoOverlay = true;
|
||||
this.buttonClicked("Open Video Overlay")
|
||||
},
|
||||
toggleVideoOverlay() {
|
||||
this.showVideoOverlay = !this.showVideoOverlay;
|
||||
this.buttonClicked("Toggle Video Overlay")
|
||||
},
|
||||
videoLoaded() {
|
||||
this.$refs.videoOverlay.contentLoaded();
|
||||
@@ -647,43 +689,6 @@ export default {
|
||||
|
||||
this.$store.commit('setNavigationFilesFilter', navigationStyleFilter);
|
||||
},
|
||||
updateFlickerMode(style) {
|
||||
if (style === NAVIGATION_STYLE.GLOBAL ||
|
||||
style === NAVIGATION_STYLE.CUSTOM) {
|
||||
this.tags = this.presentTags;
|
||||
this.errors = this.presentErrors;
|
||||
|
||||
} else if (style === NAVIGATION_STYLE.FOCUSED) {
|
||||
if (this.focusedFile.timeline) {
|
||||
this.tags = this.getTagTimelineComponents(this.presentTags, this.focusedFile);
|
||||
this.errors = this.getTagTimelineComponents(this.presentErrors, this.focusedFile);
|
||||
}
|
||||
} else if (
|
||||
style.split('-').length >= 2 &&
|
||||
style.split('-')[0] === NAVIGATION_STYLE.TARGETED
|
||||
) {
|
||||
const file = this.$store.state.traces[style.split('-')[1]];
|
||||
if (file.timeline) {
|
||||
this.tags = this.getTagTimelineComponents(this.presentTags, file);
|
||||
this.errors = this.getTagTimelineComponents(this.presentErrors, file);
|
||||
}
|
||||
//Unexpected navigation type or no timeline present in file
|
||||
} else {
|
||||
console.warn('Unexpected timeline or navigation type; no flicker mode available');
|
||||
this.tags = [];
|
||||
this.errors = [];
|
||||
}
|
||||
},
|
||||
getTagTimelineComponents(items, file) {
|
||||
if (file.type===FILE_TYPES.SURFACE_FLINGER_TRACE) {
|
||||
return items.filter(item => item.layerId !== -1);
|
||||
}
|
||||
if (file.type===FILE_TYPES.WINDOW_MANAGER_TRACE) {
|
||||
return items.filter(item => item.taskId !== -1);
|
||||
}
|
||||
// if focused file is not one supported by tags/errors
|
||||
return [];
|
||||
},
|
||||
updateVideoOverlayWidth(width) {
|
||||
this.videoOverlayExtraWidth = width;
|
||||
},
|
||||
@@ -896,6 +901,7 @@ export default {
|
||||
color: rgba(0,0,0,0.54);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.minimized-timeline-content .minimized-timeline {
|
||||
@@ -921,6 +927,27 @@ export default {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.timestamp-search-input {
|
||||
outline: none;
|
||||
border-width: 0 0 1px;
|
||||
border-color: gray;
|
||||
font-family: inherit;
|
||||
color: #448aff;
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
letter-spacing: inherit;
|
||||
width: 125px;
|
||||
}
|
||||
|
||||
.timestamp-search-input:focus {
|
||||
border-color: #448aff;
|
||||
}
|
||||
|
||||
.timestamp-search-input.expanded {
|
||||
font-size: 14px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.drop-search:hover {
|
||||
background-color: #9af39f;
|
||||
}
|
||||
|
||||
@@ -14,82 +14,114 @@
|
||||
-->
|
||||
<template>
|
||||
<md-content class="searchbar">
|
||||
<div class="search-timestamp" v-if="isTimestampSearch()">
|
||||
<md-button
|
||||
class="search-timestamp-button"
|
||||
@click="updateSearchForTimestamp"
|
||||
>
|
||||
Navigate to timestamp
|
||||
</md-button>
|
||||
<md-field class="search-input">
|
||||
<label>Enter timestamp</label>
|
||||
<md-input v-model="searchInput" @keyup.enter.native="updateSearchForTimestamp" />
|
||||
</md-field>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-content" v-if="isTagSearch()">
|
||||
<table>
|
||||
<tr class="header">
|
||||
<th style="width: 10%">Global Start</th>
|
||||
<th style="width: 10%">Global End</th>
|
||||
<th style="width: 80%">Description</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="item in filteredTransitionsAndErrors" :key="item">
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="
|
||||
setCurrentTimestamp(transitionStart(transitionTags(item.id)))
|
||||
"
|
||||
>
|
||||
<span>{{ transitionTags(item.id)[0].desc }}</span>
|
||||
</td>
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="setCurrentTimestamp(transitionEnd(transitionTags(item.id)))"
|
||||
>
|
||||
<span>{{ transitionTags(item.id)[1].desc }}</span>
|
||||
</td>
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-transition"
|
||||
:style="{color: transitionTextColor(item.transition)}"
|
||||
@click="
|
||||
setCurrentTimestamp(transitionStart(transitionTags(item.id)))
|
||||
"
|
||||
>
|
||||
{{ transitionDesc(item.transition) }}
|
||||
</td>
|
||||
|
||||
<td
|
||||
v-if="!isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="setCurrentTimestamp(item.timestamp)"
|
||||
>
|
||||
{{ errorDesc(item.timestamp) }}
|
||||
</td>
|
||||
<td v-if="!isTransition(item)">-</td>
|
||||
<td
|
||||
v-if="!isTransition(item)"
|
||||
class="inline-error"
|
||||
@click="setCurrentTimestamp(item.timestamp)"
|
||||
>
|
||||
Error: {{item.message}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<md-field class="search-input">
|
||||
<label
|
||||
>Filter by transition or error message. Click to navigate to closest
|
||||
timestamp in active timeline.</label
|
||||
<div class="tabs">
|
||||
<div class="search-timestamp" v-if="isTimestampSearch()">
|
||||
<md-field md-inline class="search-input">
|
||||
<label>Enter timestamp</label>
|
||||
<md-input
|
||||
v-model="searchInput"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
@keyup.enter.native="updateSearchForTimestamp"
|
||||
/>
|
||||
</md-field>
|
||||
<md-button
|
||||
class="md-dense md-primary search-timestamp-button"
|
||||
@click="updateSearchForTimestamp"
|
||||
>
|
||||
<md-input v-model="searchInput"></md-input>
|
||||
</md-field>
|
||||
Go to timestamp
|
||||
</md-button>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-content" v-if="isTransitionSearch()">
|
||||
<table>
|
||||
<tr class="header">
|
||||
<th style="width: 10%">Global Start</th>
|
||||
<th style="width: 10%">Global End</th>
|
||||
<th style="width: 80%">Transition</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="item in filteredTransitionsAndErrors" :key="item.id">
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="
|
||||
setCurrentTimestamp(transitionStart(transitionTags(item.id)))
|
||||
"
|
||||
>
|
||||
<span>{{ transitionTags(item.id)[0].desc }}</span>
|
||||
</td>
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="setCurrentTimestamp(transitionEnd(transitionTags(item.id)))"
|
||||
>
|
||||
<span>{{ transitionTags(item.id)[1].desc }}</span>
|
||||
</td>
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-transition"
|
||||
:style="{color: transitionTextColor(item.transition)}"
|
||||
@click="setCurrentTimestamp(transitionStart(transitionTags(item.id)))"
|
||||
>
|
||||
{{ transitionDesc(item.transition) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<md-field md-inline class="search-input">
|
||||
<label>
|
||||
Filter by transition name. Click to navigate to closest
|
||||
timestamp in active timeline.
|
||||
</label>
|
||||
<md-input
|
||||
v-model="searchInput"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
/>
|
||||
</md-field>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-content" v-if="isErrorSearch()">
|
||||
<table>
|
||||
<tr class="header">
|
||||
<th style="width: 10%">Timestamp</th>
|
||||
<th style="width: 90%">Error Message</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="item in filteredTransitionsAndErrors" :key="item.id">
|
||||
<td
|
||||
v-if="!isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="setCurrentTimestamp(item.timestamp)"
|
||||
>
|
||||
{{ errorDesc(item.timestamp) }}
|
||||
</td>
|
||||
<td
|
||||
v-if="!isTransition(item)"
|
||||
class="inline-error"
|
||||
@click="setCurrentTimestamp(item.timestamp)"
|
||||
>
|
||||
{{item.message}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<md-field md-inline class="search-input">
|
||||
<label>
|
||||
Filter by error message. Click to navigate to closest
|
||||
timestamp in active timeline.
|
||||
</label>
|
||||
<md-input
|
||||
v-model="searchInput"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
/>
|
||||
</md-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-container">
|
||||
<div class="tab-container" v-if="searchTypes.length > 0">
|
||||
Search mode:
|
||||
<md-button
|
||||
v-for="searchType in searchTypes"
|
||||
:key="searchType"
|
||||
@@ -103,9 +135,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { transitionMap, SEARCH_TYPE } from "./utils/consts";
|
||||
import { nanos_to_string, string_to_nanos } from "./transform";
|
||||
|
||||
const regExpTimestampSearch = new RegExp(/^\d+$/);
|
||||
import { nanos_to_string, getClosestTimestamp } from "./transform";
|
||||
|
||||
export default {
|
||||
name: "searchbar",
|
||||
@@ -132,7 +162,8 @@ export default {
|
||||
var tags = [];
|
||||
var filter = this.searchInput.toUpperCase();
|
||||
this.presentTags.forEach((tag) => {
|
||||
if (tag.transition.includes(filter)) tags.push(tag);
|
||||
const tagTransition = tag.transition.toUpperCase();
|
||||
if (tagTransition.includes(filter)) tags.push(tag);
|
||||
});
|
||||
return tags;
|
||||
},
|
||||
@@ -141,7 +172,8 @@ export default {
|
||||
var tagsAndErrors = [...this.filteredTags()];
|
||||
var filter = this.searchInput.toUpperCase();
|
||||
this.presentErrors.forEach((error) => {
|
||||
if (error.message.includes(filter)) tagsAndErrors.push(error);
|
||||
const errorMessage = error.message.toUpperCase();
|
||||
if (errorMessage.includes(filter)) tagsAndErrors.push(error);
|
||||
});
|
||||
// sort into chronological order
|
||||
tagsAndErrors.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1));
|
||||
@@ -170,8 +202,10 @@ export default {
|
||||
var times = tags.map((tag) => tag.timestamp);
|
||||
return times[times.length - 1];
|
||||
},
|
||||
/** Upon selecting a start/end tag in the dropdown;
|
||||
* navigates to that timestamp in the timeline */
|
||||
/**
|
||||
* Upon selecting a start/end tag in the dropdown;
|
||||
* navigates to that timestamp in the timeline
|
||||
*/
|
||||
setCurrentTimestamp(timestamp) {
|
||||
this.$store.dispatch("updateTimelineTime", timestamp);
|
||||
},
|
||||
@@ -191,22 +225,15 @@ export default {
|
||||
|
||||
/** Navigates to closest timestamp in timeline to search input*/
|
||||
updateSearchForTimestamp() {
|
||||
if (regExpTimestampSearch.test(this.searchInput)) {
|
||||
var roundedTimestamp = parseInt(this.searchInput);
|
||||
} else {
|
||||
var roundedTimestamp = string_to_nanos(this.searchInput);
|
||||
}
|
||||
var closestTimestamp = this.timeline.reduce(function (prev, curr) {
|
||||
return Math.abs(curr - roundedTimestamp) <
|
||||
Math.abs(prev - roundedTimestamp)
|
||||
? curr
|
||||
: prev;
|
||||
});
|
||||
const closestTimestamp = getClosestTimestamp(this.searchInput, this.timeline);
|
||||
this.setCurrentTimestamp(closestTimestamp);
|
||||
},
|
||||
|
||||
isTagSearch() {
|
||||
return this.searchType === SEARCH_TYPE.TAG;
|
||||
isTransitionSearch() {
|
||||
return this.searchType === SEARCH_TYPE.TRANSITIONS;
|
||||
},
|
||||
isErrorSearch() {
|
||||
return this.searchType === SEARCH_TYPE.ERRORS;
|
||||
},
|
||||
isTimestampSearch() {
|
||||
return this.searchType === SEARCH_TYPE.TIMESTAMP;
|
||||
@@ -214,6 +241,11 @@ export default {
|
||||
isTransition(item) {
|
||||
return item.stacktrace === undefined;
|
||||
},
|
||||
|
||||
/** determines whether left/right arrow keys should move cursor in input field */
|
||||
updateInputMode(isInputMode) {
|
||||
this.store.isInputMode = isInputMode;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filteredTransitionsAndErrors() {
|
||||
@@ -227,9 +259,12 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
destroyed() {
|
||||
this.updateInputMode(false);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
<style scoped>
|
||||
.searchbar {
|
||||
background-color: rgb(250, 243, 233) !important;
|
||||
top: 0;
|
||||
@@ -241,8 +276,14 @@ export default {
|
||||
bottom: 1px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
padding: 0px 20px 0px 20px;
|
||||
padding-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
@@ -255,11 +296,18 @@ export default {
|
||||
|
||||
.search-timestamp {
|
||||
padding: 5px 20px 0px 20px;
|
||||
display: block;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-timestamp > .search-input {
|
||||
margin-top: -5px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.search-timestamp-button {
|
||||
left: 0;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
@@ -269,7 +317,7 @@ export default {
|
||||
|
||||
.dropdown-content table {
|
||||
overflow-y: scroll;
|
||||
height: 150px;
|
||||
max-height: 150px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -283,7 +331,7 @@ export default {
|
||||
}
|
||||
|
||||
.inline-time:hover {
|
||||
background: #9af39f;
|
||||
background: rgb(216, 250, 218);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -292,7 +340,7 @@ export default {
|
||||
}
|
||||
|
||||
.inline-transition:hover {
|
||||
background: #9af39f;
|
||||
background: rgb(216, 250, 218);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -302,7 +350,7 @@ export default {
|
||||
}
|
||||
|
||||
.inline-error:hover {
|
||||
background: #9af39f;
|
||||
background: rgb(216, 250, 218);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -57,12 +57,13 @@
|
||||
/>
|
||||
<line
|
||||
v-for="error in errorPositions"
|
||||
:key="error"
|
||||
:x1="`${error}%`"
|
||||
:x2="`${error}%`"
|
||||
:key="error.pos"
|
||||
:x1="`${error.pos}%`"
|
||||
:x2="`${error.pos}%`"
|
||||
y1="0"
|
||||
y2="18px"
|
||||
class="error"
|
||||
@click="onErrorClick(error.ts)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -139,6 +140,7 @@ export default {
|
||||
}
|
||||
.error {
|
||||
stroke: rgb(255, 0, 0);
|
||||
stroke-width: 2px;
|
||||
stroke-width: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -45,7 +45,11 @@
|
||||
<md-checkbox v-if="hasTagsOrErrors" v-model="store.flickerTraceView">Flicker</md-checkbox>
|
||||
<md-field md-inline class="filter">
|
||||
<label>Filter...</label>
|
||||
<md-input v-model="hierarchyPropertyFilterString"></md-input>
|
||||
<md-input
|
||||
v-model="hierarchyPropertyFilterString"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
/>
|
||||
</md-field>
|
||||
</md-content>
|
||||
<div class="tree-view-wrapper">
|
||||
@@ -98,7 +102,11 @@
|
||||
</md-checkbox>
|
||||
<md-field md-inline class="filter">
|
||||
<label>Filter...</label>
|
||||
<md-input v-model="propertyFilterString"></md-input>
|
||||
<md-input
|
||||
v-model="propertyFilterString"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
/>
|
||||
</md-field>
|
||||
</md-content>
|
||||
<div class="properties-content">
|
||||
@@ -138,7 +146,7 @@ import PropertiesTreeElement from './PropertiesTreeElement.vue';
|
||||
import {ObjectTransformer} from './transform.js';
|
||||
import {DiffGenerator, defaultModifiedCheck} from './utils/diff.js';
|
||||
import {TRACE_TYPES, DUMP_TYPES} from './decode.js';
|
||||
import {stableIdCompatibilityFixup} from './utils/utils.js';
|
||||
import {isPropertyMatch, stableIdCompatibilityFixup} from './utils/utils.js';
|
||||
import {CompatibleFeatures} from './utils/compatibility.js';
|
||||
import {getPropertiesForDisplay} from './flickerlib/mixin';
|
||||
import ObjectFormatter from './flickerlib/ObjectFormatter';
|
||||
@@ -318,9 +326,7 @@ export default {
|
||||
matchItems(flickerItems, entryItem) {
|
||||
var match = false;
|
||||
flickerItems.forEach(flickerItem => {
|
||||
if (flickerItem.taskId===entryItem.taskId || flickerItem.layerId===entryItem.id) {
|
||||
match = true;
|
||||
}
|
||||
if (isPropertyMatch(flickerItem, entryItem)) match = true;
|
||||
});
|
||||
return match;
|
||||
},
|
||||
@@ -328,6 +334,11 @@ export default {
|
||||
isEntryTagMatch(entryItem) {
|
||||
return this.matchItems(this.presentTags, entryItem) || this.matchItems(this.presentErrors, entryItem);
|
||||
},
|
||||
|
||||
/** determines whether left/right arrow keys should move cursor in input field */
|
||||
updateInputMode(isInputMode) {
|
||||
this.store.isInputMode = isInputMode;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.setData(this.file.data[this.file.selectedIndex ?? 0]);
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
import DefaultTreeElement from './DefaultTreeElement.vue';
|
||||
import NodeContextMenu from './NodeContextMenu.vue';
|
||||
import {DiffType} from './utils/diff.js';
|
||||
import {isPropertyMatch} from './utils/utils.js';
|
||||
|
||||
/* in px, must be kept in sync with css, maybe find a better solution... */
|
||||
const levelOffset = 24;
|
||||
@@ -220,6 +221,9 @@ export default {
|
||||
},
|
||||
toggleTree() {
|
||||
this.setCollapseValue(!this.isCollapsed);
|
||||
if (!this.isCollapsed) {
|
||||
this.openedToSeeAttributeField(this.item.name)
|
||||
}
|
||||
},
|
||||
expandTree() {
|
||||
this.setCollapseValue(false);
|
||||
@@ -446,17 +450,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/** Check if tag/error id matches entry id */
|
||||
isIdMatch(a, b) {
|
||||
return a.taskId===b.taskId || a.layerId===b.id;
|
||||
},
|
||||
/** Performs check for id match between entry and present tags/errors
|
||||
* exits once match has been found
|
||||
*/
|
||||
matchItems(flickerItems) {
|
||||
var match = false;
|
||||
flickerItems.every(flickerItem => {
|
||||
if (this.isIdMatch(flickerItem, this.item)) {
|
||||
if (isPropertyMatch(flickerItem, this.item)) {
|
||||
match = true;
|
||||
return false;
|
||||
}
|
||||
@@ -476,7 +476,7 @@ export default {
|
||||
var transitions = [];
|
||||
var ids = [];
|
||||
this.currentTags.forEach(tag => {
|
||||
if (!ids.includes(tag.id) && this.isIdMatch(tag, this.item)) {
|
||||
if (!ids.includes(tag.id) && isPropertyMatch(tag, this.item)) {
|
||||
transitions.push(tag.transition);
|
||||
ids.push(tag.id);
|
||||
}
|
||||
@@ -484,7 +484,7 @@ export default {
|
||||
return transitions;
|
||||
},
|
||||
getCurrentErrorTags() {
|
||||
return this.currentErrors.filter(error => this.isIdMatch(error, this.item));
|
||||
return this.currentErrors.filter(error => isPropertyMatch(error, this.item));
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -554,6 +554,14 @@ function decodeAndTransformProto(buffer, params, displayDefaults) {
|
||||
|
||||
function protoDecoder(buffer, params, fileName, store) {
|
||||
const transformed = decodeAndTransformProto(buffer, params, store.displayDefaults);
|
||||
|
||||
// add tagGenerationTrace to dataFile for WM/SF traces so tags can be generated
|
||||
var tagGenerationTrace = null;
|
||||
if (params.type === FILE_TYPES.WINDOW_MANAGER_TRACE ||
|
||||
params.type === FILE_TYPES.SURFACE_FLINGER_TRACE) {
|
||||
tagGenerationTrace = transformed;
|
||||
}
|
||||
|
||||
let data;
|
||||
if (params.timeline) {
|
||||
data = transformed.entries ?? transformed.children;
|
||||
@@ -561,7 +569,15 @@ function protoDecoder(buffer, params, fileName, store) {
|
||||
data = [transformed];
|
||||
}
|
||||
const blobUrl = URL.createObjectURL(new Blob([buffer], {type: params.mime}));
|
||||
return dataFile(fileName, data.map((x) => x.timestamp), data, blobUrl, params.type);
|
||||
|
||||
return dataFile(
|
||||
fileName,
|
||||
data.map((x) => x.timestamp),
|
||||
data,
|
||||
blobUrl,
|
||||
params.type,
|
||||
tagGenerationTrace
|
||||
);
|
||||
}
|
||||
|
||||
function videoDecoder(buffer, params, fileName, store) {
|
||||
@@ -570,7 +586,7 @@ function videoDecoder(buffer, params, fileName, store) {
|
||||
return dataFile(fileName, timeline, blobUrl, blobUrl, params.type);
|
||||
}
|
||||
|
||||
function dataFile(filename, timeline, data, blobUrl, type) {
|
||||
function dataFile(filename, timeline, data, blobUrl, type, tagGenerationTrace = null) {
|
||||
return {
|
||||
filename: filename,
|
||||
// Object is frozen for performance reasons
|
||||
@@ -578,6 +594,7 @@ function dataFile(filename, timeline, data, blobUrl, type) {
|
||||
timeline: Object.freeze(timeline),
|
||||
data: data,
|
||||
blobUrl: blobUrl,
|
||||
tagGenerationTrace: tagGenerationTrace,
|
||||
type: type,
|
||||
selectedIndex: 0,
|
||||
destroy() {
|
||||
@@ -678,4 +695,17 @@ function detectAndDecode(buffer, fileName, store) {
|
||||
*/
|
||||
class UndetectableFileType extends Error { }
|
||||
|
||||
export {detectAndDecode, decodeAndTransformProto, FILE_TYPES, TRACE_INFO, TRACE_TYPES, DUMP_TYPES, DUMP_INFO, FILE_DECODERS, FILE_ICONS, UndetectableFileType};
|
||||
export {
|
||||
dataFile,
|
||||
detectAndDecode,
|
||||
decodeAndTransformProto,
|
||||
TagTraceMessage,
|
||||
FILE_TYPES,
|
||||
TRACE_INFO,
|
||||
TRACE_TYPES,
|
||||
DUMP_TYPES,
|
||||
DUMP_INFO,
|
||||
FILE_DECODERS,
|
||||
FILE_ICONS,
|
||||
UndetectableFileType
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ WindowManagerState.fromProto = function (proto: any, timestamp: number = 0, wher
|
||||
proto.rootWindowContainer.pendingActivities.map(it => it.title),
|
||||
rootWindowContainer,
|
||||
keyguardControllerState,
|
||||
timestamp = timestamp
|
||||
/*timestamp */ `${timestamp}`
|
||||
);
|
||||
|
||||
addAttributes(entry, proto);
|
||||
|
||||
@@ -88,6 +88,9 @@ const Error = require('flicker').com.android.server.wm.traces.common.errors.Erro
|
||||
const ErrorState = require('flicker').com.android.server.wm.traces.common.errors.ErrorState;
|
||||
const ErrorTrace = require('flicker').com.android.server.wm.traces.common.errors.ErrorTrace;
|
||||
|
||||
// Service
|
||||
const TaggingEngine = require('flicker').com.android.server.wm.traces.common.service.TaggingEngine;
|
||||
|
||||
const EMPTY_BUFFER = new Buffer(0, 0, 0, 0);
|
||||
const EMPTY_COLOR = new Color(-1, -1, -1, 0);
|
||||
const EMPTY_RECT = new Rect(0, 0, 0, 0);
|
||||
@@ -255,6 +258,8 @@ export {
|
||||
Rect,
|
||||
RectF,
|
||||
Region,
|
||||
// Service
|
||||
TaggingEngine,
|
||||
toSize,
|
||||
toBuffer,
|
||||
toColor,
|
||||
|
||||
@@ -19,7 +19,7 @@ import Error from './Error';
|
||||
|
||||
ErrorState.fromProto = function (protos: any[], timestamp: number): ErrorState {
|
||||
const errors = protos.map(it => Error.fromProto(it));
|
||||
const state = new ErrorState(errors, timestamp);
|
||||
const state = new ErrorState(errors, `${timestamp}`);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,14 @@ const transitionTypeMap = new Map([
|
||||
['ROTATION', TransitionType.ROTATION],
|
||||
['PIP_ENTER', TransitionType.PIP_ENTER],
|
||||
['PIP_RESIZE', TransitionType.PIP_RESIZE],
|
||||
['PIP_CLOSE', TransitionType.PIP_CLOSE],
|
||||
['PIP_EXIT', TransitionType.PIP_EXIT],
|
||||
['APP_LAUNCH', TransitionType.APP_LAUNCH],
|
||||
['APP_CLOSE', TransitionType.APP_CLOSE],
|
||||
['IME_APPEAR', TransitionType.IME_APPEAR],
|
||||
['IME_DISAPPEAR', TransitionType.IME_DISAPPEAR],
|
||||
['APP_PAIRS_ENTER', TransitionType.APP_PAIRS_ENTER],
|
||||
['APP_PAIRS_EXIT', TransitionType.APP_PAIRS_EXIT],
|
||||
]);
|
||||
|
||||
Tag.fromProto = function (proto: any): Tag {
|
||||
|
||||
@@ -19,7 +19,7 @@ import Tag from './Tag';
|
||||
|
||||
TagState.fromProto = function (timestamp: number, protos: any[]): TagState {
|
||||
const tags = protos.map(it => Tag.fromProto(it));
|
||||
const state = new TagState(timestamp, tags);
|
||||
const state = new TagState(`${timestamp}`, tags);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,14 @@ enum TransitionType {
|
||||
ROTATION = 'ROTATION',
|
||||
PIP_ENTER = 'PIP_ENTER',
|
||||
PIP_RESIZE ='PIP_RESIZE',
|
||||
PIP_CLOSE = 'PIP_CLOSE',
|
||||
PIP_EXIT = 'PIP_EXIT',
|
||||
APP_LAUNCH = 'APP_LAUNCH',
|
||||
APP_CLOSE = 'APP_CLOSE',
|
||||
IME_APPEAR = 'IME_APPEAR',
|
||||
IME_DISAPPEAR = 'IME_DISAPPEAR',
|
||||
APP_PAIRS_ENTER = 'APP_PAIRS_ENTER',
|
||||
APP_PAIRS_EXIT = 'APP_PAIRS_EXIT',
|
||||
};
|
||||
|
||||
export default TransitionType;
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { shortenName } from '../mixin'
|
||||
import { Activity } from "../common"
|
||||
import { VISIBLE_CHIP } from '../treeview/Chips'
|
||||
import WindowContainer from "./WindowContainer"
|
||||
|
||||
Activity.fromProto = function (proto: any): Activity {
|
||||
@@ -50,6 +51,7 @@ function addAttributes(entry: Activity, proto: any) {
|
||||
entry.proto = proto;
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
entry.chips = entry.isVisible ? [VISIBLE_CHIP] : [];
|
||||
}
|
||||
|
||||
export default Activity;
|
||||
|
||||
@@ -58,6 +58,7 @@ WindowContainer.fromProto = function (
|
||||
name,
|
||||
token,
|
||||
proto.orientation,
|
||||
proto.surfaceControl?.layerId ?? 0,
|
||||
proto.visible,
|
||||
config,
|
||||
children
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import VueMaterial from 'vue-material'
|
||||
import VueGtag from "vue-gtag";
|
||||
|
||||
import App from './App.vue'
|
||||
import { TRACE_TYPES, DUMP_TYPES, TRACE_INFO, DUMP_INFO } from './decode.js'
|
||||
@@ -109,6 +110,12 @@ const store = new Vuex.Store({
|
||||
video(state) {
|
||||
return state.traces[TRACE_TYPES.SCREEN_RECORDING];
|
||||
},
|
||||
tagGenerationWmTrace(state, getters) {
|
||||
return state.traces[TRACE_TYPES.WINDOW_MANAGER].tagGenerationTrace;
|
||||
},
|
||||
tagGenerationSfTrace(state, getters) {
|
||||
return state.traces[TRACE_TYPES.SURFACE_FLINGER].tagGenerationTrace;
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
setCurrentTimestamp(state, timestamp) {
|
||||
@@ -352,6 +359,61 @@ const store = new Vuex.Store({
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Make Google analytics functionalities available for recording events.
|
||||
*/
|
||||
Vue.use(VueGtag, {
|
||||
config: { id: 'G-RRV0M08Y76'}
|
||||
})
|
||||
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
buttonClicked(button) {
|
||||
const string = "Clicked " + button + " Button";
|
||||
this.$gtag.event(string, {
|
||||
'event_category': 'Button Clicked',
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': button,
|
||||
});
|
||||
},
|
||||
draggedAndDropped(val) {
|
||||
this.$gtag.event("Dragged And DroppedFile", {
|
||||
'event_category': 'Uploaded file',
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': val,
|
||||
});
|
||||
},
|
||||
uploadedFileThroughFilesystem(val) {
|
||||
this.$gtag.event("Uploaded File From Filesystem", {
|
||||
'event_category': 'Uploaded file',
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': val,
|
||||
});
|
||||
},
|
||||
newEventOccurred(event) {
|
||||
this.$gtag.event(event, {
|
||||
'event_category': event,
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': 1,
|
||||
});
|
||||
},
|
||||
seeingNewScreen(screenname) {
|
||||
this.$gtag.screenview({
|
||||
app_name: "Winscope",
|
||||
screen_name: screenname,
|
||||
})
|
||||
},
|
||||
openedToSeeAttributeField(field) {
|
||||
const string = "Opened field " + field;
|
||||
this.$gtag.event(string, {
|
||||
'event_category': "Opened attribute field",
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': field,
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
store, // inject the Vuex store into all components
|
||||
|
||||
@@ -57,6 +57,7 @@ export default {
|
||||
},
|
||||
async downloadAsZip(traces) {
|
||||
const zip = new JSZip();
|
||||
this.buttonClicked("Download All")
|
||||
|
||||
for (const trace of traces) {
|
||||
const traceFolder = zip.folder(trace.type);
|
||||
|
||||
@@ -142,10 +142,8 @@ export default {
|
||||
timelineTransitions() {
|
||||
const transitions = [];
|
||||
|
||||
//group tags by transition 'id' property
|
||||
const groupedTags = _.mapValues(
|
||||
_.groupBy(this.tags, 'id'), clist => clist.map(tag => _.omit(tag, 'id')))
|
||||
;
|
||||
//group tags by transition and 'id' property
|
||||
const groupedTags = _.groupBy(this.tags, tag => `"${tag.transition} ${tag.id}"`);
|
||||
|
||||
for (const transitionId in groupedTags) {
|
||||
const id = groupedTags[transitionId];
|
||||
@@ -154,46 +152,56 @@ export default {
|
||||
const startTimes = id.filter(tag => tag.isStartTag).map(tag => tag.timestamp);
|
||||
const endTimes = id.filter(tag => !tag.isStartTag).map(tag => tag.timestamp);
|
||||
|
||||
const transitionStartTime = Math.min(startTimes);
|
||||
const transitionEndTime = Math.max(endTimes);
|
||||
const transitionStartTime = Math.min(...startTimes);
|
||||
const transitionEndTime = Math.max(...endTimes);
|
||||
|
||||
//do not freeze new transition, as overlap still to be handled (defaulted to 0)
|
||||
const transition = this.generateTransition(
|
||||
transitionStartTime,
|
||||
transitionEndTime,
|
||||
id[0].transition,
|
||||
0
|
||||
0,
|
||||
id[0].layerId,
|
||||
id[0].taskId,
|
||||
id[0].windowToken
|
||||
);
|
||||
transitions.push(transition);
|
||||
}
|
||||
|
||||
//sort transitions in ascending start position in order to handle overlap
|
||||
transitions.sort((a, b) => (a.startPos > b.startPos) ? 1: -1);
|
||||
transitions.sort((a, b) => (a.startPos > b.startPos) ? 1 : -1);
|
||||
|
||||
//compare each transition to the ones that came before
|
||||
for (let curr=0; curr<transitions.length; curr++) {
|
||||
let overlapStore = [];
|
||||
let processedTransitions = [];
|
||||
|
||||
for (let prev=0; prev<curr; prev++) {
|
||||
overlapStore.push(transitions[prev].overlap);
|
||||
processedTransitions.push(transitions[prev]);
|
||||
|
||||
if (transitions[prev].startPos <= transitions[curr].startPos
|
||||
&& transitions[curr].startPos <= transitions[prev].startPos+transitions[prev].width
|
||||
&& transitions[curr].overlap === transitions[prev].overlap) {
|
||||
if (this.isSimultaneousTransition(transitions[curr], transitions[prev])) {
|
||||
transitions[curr].overlap++;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlapStore.length>0
|
||||
&& transitions[curr].overlap === Math.max(overlapStore)
|
||||
) transitions[curr].overlap++;
|
||||
let overlapStore = processedTransitions.map(transition => transition.overlap);
|
||||
|
||||
if (transitions[curr].overlap === Math.max(...overlapStore)) {
|
||||
let previousTransition = processedTransitions.find(transition => {
|
||||
return transition.overlap===transitions[curr].overlap;
|
||||
});
|
||||
if (this.isSimultaneousTransition(transitions[curr], previousTransition)) {
|
||||
transitions[curr].overlap++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(transitions);
|
||||
},
|
||||
errorPositions() {
|
||||
if (!this.flickerMode) return [];
|
||||
const errorPositions = this.errors.map(error => this.position(error.timestamp));
|
||||
const errorPositions = this.errors.map(
|
||||
error => ({ pos: this.position(error.timestamp), ts: error.timestamp })
|
||||
);
|
||||
return Object.freeze(errorPositions);
|
||||
},
|
||||
},
|
||||
@@ -244,6 +252,12 @@ export default {
|
||||
return this.position(endTs) - this.position(startTs) + this.pointWidth;
|
||||
},
|
||||
|
||||
isSimultaneousTransition(currTransition, prevTransition) {
|
||||
return prevTransition.startPos <= currTransition.startPos
|
||||
&& currTransition.startPos <= prevTransition.startPos+prevTransition.width
|
||||
&& currTransition.overlap === prevTransition.overlap;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a position as a percentage of the timeline width to a timestamp.
|
||||
* @param {number} position - target position as a percentage of the
|
||||
@@ -341,6 +355,16 @@ export default {
|
||||
this.$store.dispatch('updateTimelineTime', timestamp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the error click event.
|
||||
* When an error in the timeline is clicked this function will update the timeline
|
||||
* to match the error timestamp.
|
||||
* @param {number} errorTimestamp
|
||||
*/
|
||||
onErrorClick(errorTimestamp) {
|
||||
this.$store.dispatch('updateTimelineTime', errorTimestamp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a block object that can be used by the timeline SVG to render
|
||||
* a transformed block that starts at `startTs` and ends at `endTs`.
|
||||
@@ -360,14 +384,24 @@ export default {
|
||||
* @param {number} endTs - The timestamp at which the transition ends.
|
||||
* @param {string} transitionType - The type of transition.
|
||||
* @param {number} overlap - The degree to which the transition overlaps with others.
|
||||
* @param {number} layerId - Helps determine if transition is associated with SF trace.
|
||||
* @param {number} taskId - Helps determine if transition is associated with WM trace.
|
||||
* @param {number} windowToken - Helps determine if transition is associated with WM trace.
|
||||
* @return {Transition} A transition object transformed to the timeline's crop and
|
||||
* scale parameter.
|
||||
*/
|
||||
generateTransition(startTs, endTs, transitionType, overlap) {
|
||||
generateTransition(startTs, endTs, transitionType, overlap, layerId, taskId, windowToken) {
|
||||
const transitionWidth = this.objectWidth(startTs, endTs);
|
||||
const transitionDesc = transitionMap.get(transitionType).desc;
|
||||
const transitionColor = transitionMap.get(transitionType).color;
|
||||
const tooltip = `${transitionDesc}. Start: ${nanos_to_string(startTs)}. End: ${nanos_to_string(endTs)}.`;
|
||||
var tooltip = `${transitionDesc}. Start: ${nanos_to_string(startTs)}. End: ${nanos_to_string(endTs)}.`;
|
||||
|
||||
if (layerId !== 0 && taskId === 0 && windowToken === "") {
|
||||
tooltip += " SF only.";
|
||||
} else if ((taskId !== 0 || windowToken !== "") && layerId === 0) {
|
||||
tooltip += " WM only.";
|
||||
}
|
||||
|
||||
return new Transition(this.position(startTs), startTs, endTs, transitionWidth, transitionColor, overlap, tooltip);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,11 +20,14 @@ import { LayersTrace } from '@/flickerlib';
|
||||
|
||||
export default class SurfaceFlinger extends TraceBase {
|
||||
sfTraceFile: Object;
|
||||
tagGenerationTrace: Object;
|
||||
|
||||
constructor(files) {
|
||||
const sfTraceFile = files[FILE_TYPES.SURFACE_FLINGER_TRACE];
|
||||
const tagGenerationTrace = files[FILE_TYPES.SURFACE_FLINGER_TRACE].tagGenerationTrace;
|
||||
super(sfTraceFile.data, sfTraceFile.timeline, files);
|
||||
|
||||
this.tagGenerationTrace = tagGenerationTrace;
|
||||
this.sfTraceFile = sfTraceFile;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,14 @@ import { WindowManagerTrace } from '@/flickerlib';
|
||||
|
||||
export default class WindowManager extends TraceBase {
|
||||
wmTraceFile: Object;
|
||||
tagGenerationTrace: Object;
|
||||
|
||||
constructor(files) {
|
||||
const wmTraceFile = files[FILE_TYPES.WINDOW_MANAGER_TRACE];
|
||||
const tagGenerationTrace = files[FILE_TYPES.WINDOW_MANAGER_TRACE].tagGenerationTrace;
|
||||
super(wmTraceFile.data, wmTraceFile.timeline, files);
|
||||
|
||||
this.tagGenerationTrace = tagGenerationTrace;
|
||||
this.wmTraceFile = wmTraceFile;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import {DiffType} from './utils/diff.js';
|
||||
import {regExpTimestampSearch} from './utils/consts';
|
||||
|
||||
// kind - a type used for categorization of different levels
|
||||
// name - name of the node
|
||||
@@ -400,5 +401,18 @@ function get_visible_chip() {
|
||||
return {short: 'V', long: 'visible', class: 'default'};
|
||||
}
|
||||
|
||||
// Returns closest timestamp in timeline based on search input*/
|
||||
function getClosestTimestamp(searchInput, timeline) {
|
||||
if (regExpTimestampSearch.test(searchInput)) {
|
||||
var roundedTimestamp = parseInt(searchInput);
|
||||
} else {
|
||||
var roundedTimestamp = string_to_nanos(searchInput);
|
||||
}
|
||||
const closestTimestamp = timeline.reduce((prev, curr) => {
|
||||
return Math.abs(curr-roundedTimestamp) < Math.abs(prev-roundedTimestamp) ? curr : prev;
|
||||
});
|
||||
return closestTimestamp;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export {transform, ObjectTransformer, nanos_to_string, string_to_nanos, get_visible_chip};
|
||||
export {transform, ObjectTransformer, nanos_to_string, string_to_nanos, get_visible_chip, getClosestTimestamp};
|
||||
|
||||
@@ -34,7 +34,8 @@ const NAVIGATION_STYLE = {
|
||||
};
|
||||
|
||||
const SEARCH_TYPE = {
|
||||
TAG: 'Transitions and Errors',
|
||||
TRANSITIONS: 'Transitions',
|
||||
ERRORS: 'Errors',
|
||||
TIMESTAMP: 'Timestamp',
|
||||
};
|
||||
|
||||
@@ -51,11 +52,17 @@ const transitionMap = new Map([
|
||||
[TransitionType.ROTATION, {desc: 'Rotation', color: '#9900ffff'}],
|
||||
[TransitionType.PIP_ENTER, {desc: 'Entering PIP mode', color: '#4a86e8ff'}],
|
||||
[TransitionType.PIP_RESIZE, {desc: 'Resizing PIP mode', color: '#2b9e94ff'}],
|
||||
[TransitionType.PIP_CLOSE, {desc: 'Closing PIP mode', color: 'rgb(57, 57, 182)'}],
|
||||
[TransitionType.PIP_EXIT, {desc: 'Exiting PIP mode', color: 'darkblue'}],
|
||||
[TransitionType.APP_LAUNCH, {desc: 'Launching app', color: '#ef6befff'}],
|
||||
[TransitionType.APP_CLOSE, {desc: 'Closing app', color: '#d10ddfff'}],
|
||||
[TransitionType.IME_APPEAR, {desc: 'IME appearing', color: '#ff9900ff'}],
|
||||
[TransitionType.IME_DISAPPEAR, {desc: 'IME disappearing', color: '#ad6800ff'}],
|
||||
[TransitionType.APP_PAIRS_ENTER, {desc: 'Entering app pairs mode', color: 'rgb(58, 151, 39)'}],
|
||||
[TransitionType.APP_PAIRS_EXIT, {desc: 'Exiting app pairs mode', color: 'rgb(45, 110, 32)'}],
|
||||
])
|
||||
|
||||
export { WebContentScriptMessageType, NAVIGATION_STYLE, SEARCH_TYPE, logLevel, transitionMap };
|
||||
//used to split timestamp search input by unit, to convert to nanoseconds
|
||||
const regExpTimestampSearch = new RegExp(/^\d+$/);
|
||||
|
||||
export { WebContentScriptMessageType, NAVIGATION_STYLE, SEARCH_TYPE, logLevel, transitionMap, regExpTimestampSearch };
|
||||
|
||||
@@ -84,4 +84,21 @@ function nanosToString(elapsedRealtimeNanos, precision) {
|
||||
return parts.reverse().join('');
|
||||
}
|
||||
|
||||
export { DIRECTION, findLastMatchingSorted, stableIdCompatibilityFixup, nanosToString, TimeUnits }
|
||||
/** Checks for match in window manager properties taskId, layerId, or windowToken,
|
||||
* or surface flinger property id
|
||||
*/
|
||||
function isPropertyMatch(flickerItem, entryItem) {
|
||||
return flickerItem.taskId === entryItem.taskId ||
|
||||
(flickerItem.windowToken === entryItem.windowToken) ||
|
||||
((flickerItem.layerId === entryItem.layerId) && flickerItem.layerId !== 0) ||
|
||||
flickerItem.layerId === entryItem.id;
|
||||
}
|
||||
|
||||
export {
|
||||
DIRECTION,
|
||||
findLastMatchingSorted,
|
||||
isPropertyMatch,
|
||||
stableIdCompatibilityFixup,
|
||||
nanosToString,
|
||||
TimeUnits
|
||||
}
|
||||
@@ -7694,6 +7694,11 @@ vue-github-buttons@^3.1.0:
|
||||
node-fetch "^2.3.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
vue-gtag@^1.16.1:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-gtag/-/vue-gtag-1.16.1.tgz#edb2f20ab4f6c4d4d372dfecf8c1fcc8ab890181"
|
||||
integrity sha512-5vs0pSGxdqrfXqN1Qwt0ZFXG0iTYjRMu/saddc7QIC5yp+DKgjWQRpGYVa7Pq+KbThxwzzMfo0sGi7ISa6NowA==
|
||||
|
||||
vue-hot-reload-api@^2.3.0:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||
|
||||
Reference in New Issue
Block a user