KaiOS device plugin for recording memory allocations
Summary:
This adds basic plugin for recording memory allocations.
{F224005997}
Reviewed By: passy
Differential Revision: D18837209
fbshipit-source-id: aa29680b3a44c51135b760fe4ee76edc6d0ec109
This commit is contained in:
committed by
Facebook Github Bot
parent
cf512e5b83
commit
c7af0b53e6
@@ -10,7 +10,7 @@
|
|||||||
export {default as styled} from '@emotion/styled';
|
export {default as styled} from '@emotion/styled';
|
||||||
export {keyframes} from 'emotion';
|
export {keyframes} from 'emotion';
|
||||||
export * from './ui/index';
|
export * from './ui/index';
|
||||||
export {getStringFromErrorLike, textContent} from './utils/index';
|
export {getStringFromErrorLike, textContent, sleep} from './utils/index';
|
||||||
export {serialize, deserialize} from './utils/serialization';
|
export {serialize, deserialize} from './utils/serialization';
|
||||||
export * from './utils/jsonTypes';
|
export * from './utils/jsonTypes';
|
||||||
export {default as GK} from './fb-stubs/GK';
|
export {default as GK} from './fb-stubs/GK';
|
||||||
|
|||||||
442
src/plugins/kaios-allocations/index.tsx
Normal file
442
src/plugins/kaios-allocations/index.tsx
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FlipperDevicePlugin, Device, KaiOSDevice} from 'flipper';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Toolbar,
|
||||||
|
ManagedTable,
|
||||||
|
Panel,
|
||||||
|
Label,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
} from 'flipper';
|
||||||
|
|
||||||
|
import {sleep} from 'flipper';
|
||||||
|
|
||||||
|
import util from 'util';
|
||||||
|
import {exec} from 'child-process-es6-promise';
|
||||||
|
|
||||||
|
import FirefoxClient from 'firefox-client';
|
||||||
|
import BaseClientMethods from 'firefox-client/lib/client-methods';
|
||||||
|
import extend from 'firefox-client/lib/extend';
|
||||||
|
|
||||||
|
// This uses legacy `extend` from `firefox-client`, since this seems to be what the implementation expects
|
||||||
|
// It's probably possible to rewrite this in a modern way and properly type it, but for now leaving this as it is
|
||||||
|
const ClientMethods: any = extend(BaseClientMethods, {
|
||||||
|
initialize: function(client: any, actor: any) {
|
||||||
|
this.client = client;
|
||||||
|
this.actor = actor;
|
||||||
|
|
||||||
|
this.cb = function(this: typeof ClientMethods, message: any) {
|
||||||
|
if (message.from === this.actor) {
|
||||||
|
this.emit(message.type, message);
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
this.client.on('message', this.cb);
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnect: function() {
|
||||||
|
this.client.removeListener('message', this.cb);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function Memory(this: typeof ClientMethods, client: any, actor: any): any {
|
||||||
|
this.initialize(client, actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repetitive, it is probably better to refactor this
|
||||||
|
// to use API like `runCommand(commandName, params): Promise`
|
||||||
|
Memory.prototype = extend(ClientMethods, {
|
||||||
|
attach: function(cb: any) {
|
||||||
|
this.request('attach', function(err: any, resp: any) {
|
||||||
|
cb(err, resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getState: function(cb: any) {
|
||||||
|
this.request('getState', function(err: any, resp: any) {
|
||||||
|
cb(err, resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
takeCensus: function(cb: any) {
|
||||||
|
this.request('takeCensus', function(err: any, resp: any) {
|
||||||
|
cb(err, resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getAllocations: function(cb: any) {
|
||||||
|
this.request('getAllocations', function(err: any, resp: any) {
|
||||||
|
cb(err, resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
startRecordingAllocations: function(options: any, cb: any) {
|
||||||
|
this.request('startRecordingAllocations', {options}, function(
|
||||||
|
err: any,
|
||||||
|
resp: any,
|
||||||
|
) {
|
||||||
|
cb(err, resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stopRecordingAllocations: function(cb: any) {
|
||||||
|
this.request('stopRecordingAllocations', function(err: any, resp: any) {
|
||||||
|
cb(err, resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
measure: function(cb: any) {
|
||||||
|
this.request('measure', function(err: any, resp: any) {
|
||||||
|
cb(err, resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getAllocationsSettings: function(cb: any) {
|
||||||
|
this.request('getAllocationsSettings', function(err: any, resp: any) {
|
||||||
|
cb(err, resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ffPromisify = (o: {[key: string]: any}, m: string) =>
|
||||||
|
util.promisify(o[m].bind(o));
|
||||||
|
|
||||||
|
const ColumnSizes = {
|
||||||
|
timestamp: 'flex',
|
||||||
|
freeMem: 'flex',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Columns = {
|
||||||
|
timestamp: {
|
||||||
|
value: 'time',
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
allocationSize: {
|
||||||
|
value: 'Allocation bytes',
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
functionName: {
|
||||||
|
value: 'Function',
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type Allocation = {
|
||||||
|
timestamp: number;
|
||||||
|
allocationSize: number;
|
||||||
|
functionName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
apps: {[key: string]: string};
|
||||||
|
runningAppName: null | string;
|
||||||
|
allocationData: Array<Allocation>;
|
||||||
|
totalAllocations: number;
|
||||||
|
totalAllocatedBytes: number;
|
||||||
|
monitoring: boolean;
|
||||||
|
allocationsBySize: {[key: string]: number};
|
||||||
|
minAllocationSizeInTable: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LOCALSTORAGE_APP_NAME_KEY = '__KAIOS_ALLOCATIONS_PLUGIN_CACHED_APP_NAME';
|
||||||
|
const LOCALSTORAGE_MIN_ALLOCATION_SIZE_KEY =
|
||||||
|
'__KAIOS_ALLOCATIONS_PLUGIN_MIN_ALLOCATION_SIZE';
|
||||||
|
const DEFAULT_MIN_ALLOCATION_SIZE = 128;
|
||||||
|
|
||||||
|
function getMinAllocationSizeFromLocalStorage(): number {
|
||||||
|
const ls = localStorage.getItem(LOCALSTORAGE_MIN_ALLOCATION_SIZE_KEY);
|
||||||
|
if (!ls) {
|
||||||
|
return DEFAULT_MIN_ALLOCATION_SIZE;
|
||||||
|
}
|
||||||
|
const parsed = parseInt(ls, 10);
|
||||||
|
return !isNaN(parsed) ? parsed : DEFAULT_MIN_ALLOCATION_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AllocationsPlugin extends FlipperDevicePlugin<
|
||||||
|
State,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
> {
|
||||||
|
currentApp: any = null;
|
||||||
|
memory: any = null;
|
||||||
|
client: any = null;
|
||||||
|
webApps: any = null;
|
||||||
|
|
||||||
|
state: State = {
|
||||||
|
apps: {},
|
||||||
|
runningAppName: null,
|
||||||
|
monitoring: false,
|
||||||
|
allocationData: [],
|
||||||
|
totalAllocations: 0,
|
||||||
|
totalAllocatedBytes: 0,
|
||||||
|
allocationsBySize: {},
|
||||||
|
minAllocationSizeInTable: getMinAllocationSizeFromLocalStorage(),
|
||||||
|
};
|
||||||
|
|
||||||
|
static supportsDevice(device: Device) {
|
||||||
|
return device instanceof KaiOSDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStartMonitor = async () => {
|
||||||
|
if (this.state.monitoring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: try to reconnect in case of failure
|
||||||
|
await ffPromisify(
|
||||||
|
this.memory,
|
||||||
|
'startRecordingAllocations',
|
||||||
|
)({
|
||||||
|
probability: 1.0,
|
||||||
|
maxLogLength: 20000,
|
||||||
|
drainAllocationsTimeout: 1500,
|
||||||
|
trackingAllocationSites: true,
|
||||||
|
});
|
||||||
|
this.setState({monitoring: true});
|
||||||
|
};
|
||||||
|
|
||||||
|
onStopMonitor = async () => {
|
||||||
|
if (!this.state.monitoring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.tearDownApp();
|
||||||
|
this.setState({monitoring: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
// reloads the list of apps every two seconds
|
||||||
|
reloadAppListWhenNotMonitoring = async () => {
|
||||||
|
while (true) {
|
||||||
|
if (!this.state.monitoring) {
|
||||||
|
try {
|
||||||
|
await this.processListOfApps();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Exception, attempting to reconnect', e);
|
||||||
|
await this.connectToDebugApi();
|
||||||
|
// processing the list of the apps is going to be automatically retried now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async connectToDebugApi(): Promise<void> {
|
||||||
|
this.client = new FirefoxClient({log: false});
|
||||||
|
await ffPromisify(this.client, 'connect')(6000, 'localhost');
|
||||||
|
this.webApps = await ffPromisify(this.client, 'getWebapps')();
|
||||||
|
}
|
||||||
|
|
||||||
|
async processListOfApps(): Promise<void> {
|
||||||
|
const runningAppUrls = await ffPromisify(this.webApps, 'listRunningApps')();
|
||||||
|
|
||||||
|
const lastUsedAppName = localStorage.getItem(LOCALSTORAGE_APP_NAME_KEY);
|
||||||
|
let runningAppName = null;
|
||||||
|
const appTitleToUrl: {[key: string]: string} = {};
|
||||||
|
for (const runningAppUrl of runningAppUrls) {
|
||||||
|
const app = await ffPromisify(this.webApps, 'getApp')(runningAppUrl);
|
||||||
|
appTitleToUrl[app.title] = runningAppUrl;
|
||||||
|
if (app.title === lastUsedAppName) {
|
||||||
|
runningAppName = app.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningAppName && this.state.runningAppName !== runningAppName) {
|
||||||
|
this.setUpApp(appTitleToUrl[runningAppName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
apps: appTitleToUrl,
|
||||||
|
runningAppName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await exec(
|
||||||
|
'adb forward tcp:6000 localfilesystem:/data/local/debugger-socket',
|
||||||
|
);
|
||||||
|
await this.connectToDebugApi();
|
||||||
|
await this.processListOfApps();
|
||||||
|
// no await because reloading runs in the background
|
||||||
|
this.reloadAppListWhenNotMonitoring();
|
||||||
|
}
|
||||||
|
|
||||||
|
async teardown() {
|
||||||
|
if (this.state.monitoring) {
|
||||||
|
await this.onStopMonitor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUpApp(appUrl: string) {
|
||||||
|
this.currentApp = await ffPromisify(this.webApps, 'getApp')(appUrl);
|
||||||
|
if (!this.currentApp) {
|
||||||
|
// TODO: notify user?
|
||||||
|
throw new Error('Cannot connect to app');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
tab: {memoryActor},
|
||||||
|
} = this.currentApp;
|
||||||
|
this.memory = new (Memory as any)(this.currentApp.client, memoryActor);
|
||||||
|
await ffPromisify(this.memory, 'attach')();
|
||||||
|
this.currentApp.client.on('message', this.processAllocationsMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async tearDownApp() {
|
||||||
|
if (!this.currentApp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.currentApp.client.off('message', this.processAllocationsMsg);
|
||||||
|
await ffPromisify(this.memory, 'stopRecordingAllocations')();
|
||||||
|
this.currentApp = null;
|
||||||
|
this.memory = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
processAllocationsMsg = (msg: any) => {
|
||||||
|
if (msg.type !== 'allocations') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updateAllocations(msg.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateAllocations = (data: any) => {
|
||||||
|
const {allocations, allocationsTimestamps, allocationSizes, frames} = data;
|
||||||
|
const newAllocationData = [...this.state.allocationData];
|
||||||
|
let newTotalAllocations = this.state.totalAllocations;
|
||||||
|
let newTotalAllocatedBytes = this.state.totalAllocatedBytes;
|
||||||
|
const newAllocationsBySize = {...this.state.allocationsBySize};
|
||||||
|
for (let i = 0; i < allocations.length; ++i) {
|
||||||
|
const frameId = allocations[i];
|
||||||
|
const timestamp = allocationsTimestamps[i];
|
||||||
|
const allocationSize = allocationSizes[i];
|
||||||
|
const functionName = frames[frameId]
|
||||||
|
? frames[frameId].functionDisplayName
|
||||||
|
: null;
|
||||||
|
if (allocationSize >= this.state.minAllocationSizeInTable) {
|
||||||
|
newAllocationData.push({timestamp, allocationSize, functionName});
|
||||||
|
}
|
||||||
|
newAllocationsBySize[allocationSize] =
|
||||||
|
(newAllocationsBySize[allocationSize] || 0) + 1;
|
||||||
|
newTotalAllocations++;
|
||||||
|
newTotalAllocatedBytes += allocationSize;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
allocationData: newAllocationData,
|
||||||
|
totalAllocations: newTotalAllocations,
|
||||||
|
totalAllocatedBytes: newTotalAllocatedBytes,
|
||||||
|
allocationsBySize: newAllocationsBySize,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
buildMemRows = () => {
|
||||||
|
return this.state.allocationData.map(info => {
|
||||||
|
return {
|
||||||
|
columns: {
|
||||||
|
timestamp: {
|
||||||
|
value: info.timestamp,
|
||||||
|
filterValue: info.timestamp,
|
||||||
|
},
|
||||||
|
allocationSize: {
|
||||||
|
value: info.allocationSize,
|
||||||
|
filterValue: info.allocationSize,
|
||||||
|
},
|
||||||
|
functionName: {
|
||||||
|
value: info.functionName,
|
||||||
|
filterValue: info.functionName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: `${info.timestamp} ${info.allocationSize} ${info.functionName}`,
|
||||||
|
copyText: `${info.timestamp} ${info.allocationSize} ${info.functionName}`,
|
||||||
|
filterValue: `${info.timestamp} ${info.allocationSize} ${info.functionName}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onAppChange = (newAppTitle: string) => {
|
||||||
|
localStorage[LOCALSTORAGE_APP_NAME_KEY] = newAppTitle;
|
||||||
|
this.setState({runningAppName: newAppTitle});
|
||||||
|
this.tearDownApp();
|
||||||
|
this.setUpApp(this.state.apps[newAppTitle]);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMinAllocationSizeChange = (event: any) => {
|
||||||
|
const newMinAllocationSize = event.target.value;
|
||||||
|
this.setState({
|
||||||
|
minAllocationSizeInTable: newMinAllocationSize,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const appTitlesForSelect: {[key: string]: string} = {};
|
||||||
|
|
||||||
|
for (const [appTitle] of Object.entries(this.state.apps)) {
|
||||||
|
appTitlesForSelect[appTitle] = appTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Panel
|
||||||
|
padded={false}
|
||||||
|
heading="Page allocations"
|
||||||
|
floating={false}
|
||||||
|
collapsable={false}
|
||||||
|
grow={true}>
|
||||||
|
<Toolbar position="top">
|
||||||
|
<Select
|
||||||
|
options={appTitlesForSelect}
|
||||||
|
onChangeWithKey={this.onAppChange}
|
||||||
|
selected={this.state.runningAppName}
|
||||||
|
disabled={this.state.monitoring}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{this.state.monitoring ? (
|
||||||
|
<Button onClick={this.onStopMonitor} icon="pause">
|
||||||
|
Pause
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button onClick={this.onStartMonitor} icon="play">
|
||||||
|
Start
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Label>
|
||||||
|
Min allocation size in bytes{' '}
|
||||||
|
<Input
|
||||||
|
placeholder="min bytes"
|
||||||
|
value={this.state.minAllocationSizeInTable}
|
||||||
|
type="number"
|
||||||
|
onChange={this.onMinAllocationSizeChange}
|
||||||
|
disabled={this.state.monitoring}
|
||||||
|
/>
|
||||||
|
</Label>
|
||||||
|
</Toolbar>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
Total number of allocations: {this.state.totalAllocations}
|
||||||
|
</Label>
|
||||||
|
<br />
|
||||||
|
<Label>
|
||||||
|
Total MBs allocated:{' '}
|
||||||
|
{(this.state.totalAllocatedBytes / 1024 / 1024).toFixed(3)}
|
||||||
|
</Label>
|
||||||
|
<ManagedTable
|
||||||
|
multiline={true}
|
||||||
|
columnSizes={ColumnSizes}
|
||||||
|
columns={Columns}
|
||||||
|
floating={false}
|
||||||
|
zebra={true}
|
||||||
|
rows={this.buildMemRows()}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/plugins/kaios-allocations/package.json
Normal file
23
src/plugins/kaios-allocations/package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "kaios-big-allocations",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.tsx",
|
||||||
|
"license": "MIT",
|
||||||
|
"title": "KaiOS: big allocations",
|
||||||
|
"icon": "apps",
|
||||||
|
"bugs": {
|
||||||
|
"email": "oncall+wa_kaios@xmail.facebook.com",
|
||||||
|
"url": "https://fb.workplace.com/groups/wa.kaios/"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "patch-package"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"patch-package": "^6.2.0",
|
||||||
|
"postinstall-postinstall": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"child-process-es6-promise": "^1.2.1",
|
||||||
|
"firefox-client": "0.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
diff --git a/node_modules/firefox-client/lib/client.js b/node_modules/firefox-client/lib/client.js
|
||||||
|
index 22b3179..11e8d7f 100644
|
||||||
|
--- a/node_modules/firefox-client/lib/client.js
|
||||||
|
+++ b/node_modules/firefox-client/lib/client.js
|
||||||
|
@@ -2,8 +2,6 @@ var net = require("net"),
|
||||||
|
events = require("events"),
|
||||||
|
extend = require("./extend");
|
||||||
|
|
||||||
|
-var colors = require("colors");
|
||||||
|
-
|
||||||
|
module.exports = Client;
|
||||||
|
|
||||||
|
// this is very unfortunate! and temporary. we can't
|
||||||
10
src/plugins/kaios-allocations/types/child-process-es6-promise.d.ts
vendored
Normal file
10
src/plugins/kaios-allocations/types/child-process-es6-promise.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module "child-process-es6-promise" {
|
||||||
|
export function exec(command: string, options: void): Promise<{stdout: string, stderr: string}>;
|
||||||
|
}
|
||||||
49
src/plugins/kaios-allocations/types/firefox-client.d.tsx
Normal file
49
src/plugins/kaios-allocations/types/firefox-client.d.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module 'firefox-client' {
|
||||||
|
export default class FirefoxClient {
|
||||||
|
constructor(options: any);
|
||||||
|
|
||||||
|
connect(port: any, host: any, cb: any): void;
|
||||||
|
|
||||||
|
disconnect(): void;
|
||||||
|
|
||||||
|
getDevice(cb: any): any;
|
||||||
|
|
||||||
|
getRoot(cb: any): any;
|
||||||
|
|
||||||
|
getWebapps(cb: any): any;
|
||||||
|
|
||||||
|
listTabs(cb: any): any;
|
||||||
|
|
||||||
|
onEnd(): void;
|
||||||
|
|
||||||
|
onError(error: any): void;
|
||||||
|
|
||||||
|
onTimeout(): void;
|
||||||
|
|
||||||
|
selectedTab(cb: any): any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'firefox-client/lib/client-methods' {
|
||||||
|
import FirefoxClient from 'firefox-client';
|
||||||
|
|
||||||
|
export default class ClientMethods {
|
||||||
|
initialize(client: FirefoxClient, actor: any): void;
|
||||||
|
request(type: any, message: any, transform: any, callback: Function): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'firefox-client/lib/extend' {
|
||||||
|
import FirefoxClient from 'firefox-client';
|
||||||
|
|
||||||
|
export default function extend(prototype: any, o: any): any;
|
||||||
|
}
|
||||||
1356
src/plugins/kaios-allocations/yarn.lock
Normal file
1356
src/plugins/kaios-allocations/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,9 @@ export default class Select extends Component<{
|
|||||||
label?: string;
|
label?: string;
|
||||||
/** Select box should take all available space */
|
/** Select box should take all available space */
|
||||||
grow?: boolean;
|
grow?: boolean;
|
||||||
|
|
||||||
|
/** Whether the user can interact with the select and change the selcted option */
|
||||||
|
disabled?: boolean;
|
||||||
}> {
|
}> {
|
||||||
selectID: string = Math.random().toString(36);
|
selectID: string = Math.random().toString(36);
|
||||||
|
|
||||||
@@ -64,7 +67,7 @@ export default class Select extends Component<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {className, options, selected, label, grow} = this.props;
|
const {className, options, selected, label, grow, disabled} = this.props;
|
||||||
|
|
||||||
let select = (
|
let select = (
|
||||||
<SelectMenu
|
<SelectMenu
|
||||||
@@ -72,6 +75,7 @@ export default class Select extends Component<{
|
|||||||
id={this.selectID}
|
id={this.selectID}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
className={className}
|
className={className}
|
||||||
|
disabled={disabled}
|
||||||
value={selected || ''}>
|
value={selected || ''}>
|
||||||
{Object.keys(options).map((key, index) => (
|
{Object.keys(options).map((key, index) => (
|
||||||
<option value={key} key={index}>
|
<option value={key} key={index}>
|
||||||
|
|||||||
@@ -9,3 +9,4 @@
|
|||||||
|
|
||||||
export {default as textContent} from './textContent';
|
export {default as textContent} from './textContent';
|
||||||
export {getStringFromErrorLike} from './errors';
|
export {getStringFromErrorLike} from './errors';
|
||||||
|
export {sleep} from './promiseTimeout';
|
||||||
|
|||||||
@@ -15,17 +15,23 @@ export default function promiseTimeout<T>(
|
|||||||
timeoutMessage?: string,
|
timeoutMessage?: string,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
// Create a promise that rejects in <ms> milliseconds
|
// Create a promise that rejects in <ms> milliseconds
|
||||||
const timeout: Promise<T> = new Promise((resolve, reject) => {
|
const timeout = sleep(ms).then(() => {
|
||||||
const id = setTimeout(() => {
|
throw new Error(timeoutMessage || `Timed out in ${ms} ms.`);
|
||||||
clearTimeout(id);
|
|
||||||
reject(new Error(timeoutMessage || `Timed out in ${ms} ms.`));
|
|
||||||
}, ms);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Returns a race between our timeout and the passed in promise
|
// Returns a race between our timeout and the passed in promise
|
||||||
return Promise.race([promise, timeout]);
|
return Promise.race([promise, timeout]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const id = setTimeout(() => {
|
||||||
|
clearTimeout(id);
|
||||||
|
resolve();
|
||||||
|
}, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function showStatusUpdatesForPromise<T>(
|
export function showStatusUpdatesForPromise<T>(
|
||||||
promise: Promise<T>,
|
promise: Promise<T>,
|
||||||
message: string,
|
message: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user