Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 68a71d3

Browse files
committed
Merge pull request #297 from hedgerwang/banana_slug
add a plugin to trace react updates
2 parents b264086 + b816b91 commit 68a71d3

17 files changed

Lines changed: 822 additions & 6 deletions

agent/Agent.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ class Agent extends EventEmitter {
178178
}
179179
});
180180
bridge.on('scrollToNode', id => this.scrollToNode(id));
181+
bridge.on('bananaslugchange', value => this.emit('bananaslugchange', value));
181182

182183
/** Events sent to the frontend **/
183184
this.on('root', id => bridge.send('root', id));

frontend/SearchPane.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
var React = require('react');
1616
var ReactDOM = require('react-dom');
17+
var SettingsPane = require('./SettingsPane');
1718
var TreeView = require('./TreeView');
1819
var {PropTypes} = React;
1920

@@ -84,6 +85,7 @@ class SearchPane extends React.Component {
8485
}
8586
return (
8687
<div style={styles.container}>
88+
<SettingsPane />
8789
<TreeView reload={this.props.reload} />
8890
<div style={styles.searchBox}>
8991
<input

frontend/SettingsPane.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
'use strict';
11+
12+
var BananaSlugFrontendControl = require('../plugins/BananaSlug/BananaSlugFrontendControl');
13+
var React = require('react');
14+
15+
var decorate = require('./decorate');
16+
17+
class SettingsPane extends React.Component {
18+
render() {
19+
return (
20+
<div style={styles.container}>
21+
<BananaSlugFrontendControl {...this.props} />
22+
</div>
23+
);
24+
}
25+
}
26+
27+
var styles = {
28+
container: {
29+
backgroundColor: '#efefef',
30+
padding: '2px 4px',
31+
display: 'flex',
32+
flexShrink: 0,
33+
position: 'relative',
34+
},
35+
};
36+
37+
var Wrapped = decorate({
38+
listeners() {
39+
return ['bananaslugchange'];
40+
},
41+
props(store) {
42+
return {
43+
state: store.bananaslugState,
44+
onChange: state => store.changeBananaSlug(state),
45+
};
46+
},
47+
}, SettingsPane);
48+
49+
module.exports = Wrapped;

frontend/Store.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class Store extends EventEmitter {
8585
_eventTimer: ?number;
8686

8787
// Public state
88+
bananaslugState: ?Object;
8889
contextMenu: ?ContextMenu;
8990
hovered: ?ElementID;
9091
isBottomTagSelected: boolean;
@@ -117,6 +118,7 @@ class Store extends EventEmitter {
117118
this.isBottomTagSelected = false;
118119
this.searchText = '';
119120
this.capabilities = {};
121+
this.bananaslugState = null;
120122

121123
// for debugging
122124
window.store = this;
@@ -417,6 +419,12 @@ class Store extends EventEmitter {
417419
});
418420
}
419421

422+
changeBananaSlug(state: Object) {
423+
this.bananaslugState = state;
424+
this.emit('bananaslugchange');
425+
this._bridge.send('bananaslugchange', state.toJS());
426+
}
427+
420428
// Private stuff
421429
_establishConnection() {
422430
var tries = 0;
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*/
11+
'use strict';
12+
13+
const requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
14+
const immutable = require('immutable');
15+
16+
import type {Measurement} from './BananaSlugTypes';
17+
18+
// How long the measurement can be cached in ms.
19+
const DURATION = 800;
20+
21+
const {Record, Map, Set} = immutable;
22+
23+
const MeasurementRecord = Record({
24+
bottom: 0,
25+
expiration: 0,
26+
height: 0,
27+
id: '',
28+
left: 0,
29+
right: 0,
30+
scrollX: 0,
31+
scrollY: 0,
32+
top: 0,
33+
width: 0,
34+
});
35+
36+
var _id = 100;
37+
38+
class BananaSlugAbstractNodeMeasurer {
39+
_callbacks: Map<Node, (v: Measurement) => void>;
40+
_ids: Map<string, Node>;
41+
_isRequesting: boolean;
42+
_measureNodes: () => void;
43+
_measurements: Map<Node, Measurement>;
44+
_nodes: Map<string, Node>;
45+
46+
constructor() {
47+
// pending nodes to measure.
48+
this._nodes = new Map();
49+
50+
// ids of pending nodes.
51+
this._ids = new Map();
52+
53+
// cached measurements.
54+
this._measurements = new Map();
55+
56+
// callbacks for pending nodes.
57+
this._callbacks = new Map();
58+
59+
this._isRequesting = false;
60+
61+
// non-auto-binds.
62+
this._measureNodes = this._measureNodes.bind(this);
63+
}
64+
65+
request(node: Node, callback: (v: Measurement) => void): string {
66+
var requestID = this._nodes.has(node) ?
67+
this._nodes.get(node) :
68+
String(_id++);
69+
70+
this._nodes = this._nodes.set(node, requestID);
71+
this._ids = this._ids.set(requestID, node);
72+
73+
var callbacks = this._callbacks.has(node) ?
74+
this._callbacks.get(node) :
75+
new Set();
76+
77+
callbacks = callbacks.add(callback);
78+
this._callbacks = this._callbacks.set(node, callbacks);
79+
80+
if (this._isRequesting) {
81+
return requestID;
82+
}
83+
84+
this._isRequesting = true;
85+
requestAnimationFrame(this._measureNodes);
86+
return requestID;
87+
}
88+
89+
cancel(requestID: string): void {
90+
if (this._ids.has(requestID)) {
91+
var node = this._ids.get(requestID);
92+
this._ids = this._ids.delete(requestID);
93+
this._nodes = this._nodes.delete(node);
94+
this._callbacks = this._callbacks.delete(node);
95+
}
96+
}
97+
98+
measureImpl(node: Node): Measurement {
99+
// sub-class must overwrite this.
100+
return new MeasurementRecord();
101+
}
102+
103+
_measureNodes(): void {
104+
var now = Date.now();
105+
106+
this._measurements = this._measurements.withMutations(_measurements => {
107+
for (const node of this._nodes.keys()) {
108+
const measurement = this._measureNode(now, node);
109+
// cache measurement.
110+
_measurements.set(node, measurement);
111+
}
112+
});
113+
114+
// execute callbacks.
115+
for (const node of this._nodes.keys()) {
116+
const measurement = this._measurements.get(node);
117+
this._callbacks.get(node).forEach(callback => callback(measurement));
118+
}
119+
120+
// clear stale measurement.
121+
this._measurements = this._measurements.withMutations(_measurements => {
122+
for (const [node, measurement] of _measurements.entries()) {
123+
if (measurement.expiration < now) {
124+
_measurements.delete(node);
125+
}
126+
}
127+
});
128+
129+
this._ids = this._ids.clear();
130+
this._nodes = this._nodes.clear();
131+
this._callbacks = this._callbacks.clear();
132+
this._isRequesting = false;
133+
}
134+
135+
_measureNode(timestamp: number, node: Node): Measurement {
136+
var measurement;
137+
var data;
138+
139+
if (this._measurements.has(node)) {
140+
measurement = this._measurements.get(node);
141+
if (measurement.expiration < timestamp) {
142+
// measurement expires. measure again.
143+
data = this.measureImpl(node);
144+
measurement = measurement.merge({
145+
...data,
146+
expiration: timestamp + DURATION,
147+
});
148+
}
149+
} else {
150+
data = this.measureImpl(node);
151+
measurement = new MeasurementRecord({
152+
...data,
153+
expiration: timestamp + DURATION,
154+
id: 'm_' + String(_id++),
155+
});
156+
}
157+
return measurement;
158+
}
159+
}
160+
161+
module.exports = BananaSlugAbstractNodeMeasurer;

0 commit comments

Comments
 (0)