2015-03-19 19:10:41 +00:00
<!doctype html>
2015-03-23 18:48:02 +00:00
<!--
Copyright (c) 2015-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
-->
2015-03-19 19:10:41 +00:00
< html >
< head >
< meta charset = utf-8 >
<!-- Fake favicon, to avoid extra request to server -->
< link rel = "icon" href = "data:;base64,iVBORw0KGgo=" >
< title > React Native Debugger< / title >
2015-08-21 07:57:43 +00:00
< script src = "/debugger-ui/static/Chart.min.js" > < / script >
< script src = "/debugger-ui/static/react-0.13.3.min.js" > < / script >
< script src = "/debugger-ui/static/JSXTransformer-0.13.3.js" > < / script >
< script type = "text/jsx" >
2015-03-19 19:10:41 +00:00
(function() {
2015-08-21 07:57:43 +00:00
var ws;
var metricsIntervalId, metricsCollectionEnabled;
var cpuChart, memoryChart, jsCallsChart, latestLabel;
var batchedJSCalls = [];
var numJsCalls = 0;
var canvas = document.getElementById('cpu-utilization');
var ctx = canvas.getContext('2d');
var memoryCanvas = document.getElementById('memory-utilization');
var memoryCtx = memoryCanvas.getContext('2d');
var jsCallsCanvas = document.getElementById("js-calls-graph");
var jsCallsCtx = jsCallsCanvas.getContext('2d');
var numPoints = 20;
var labels = [];
var data = [];
for (var i = 1; i < numPoints + 1 ; i + + ) {
labels.push('');
data.push(0);
}
var cpuStartingData = {
labels: labels,
datasets: [
{
fillColor: "rgba(113,188,120,0.2)",
strokeColor: "rgba(113,188,120,1)",
pointColor: "rgba(113, 188,120,1)",
pointStrokeColor: "#fff",
data: data
}
]
};
var memoryStartingData = {
labels: labels,
datasets: [
{
label: "Resident Memory (MB)",
fillColor: "rgba(220,220,220,0.2)",
strokeColor: "rgba(220,220,220,1)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: data
},
{
label: "Virtual Memory (MB)",
fillColor: "rgba(151,187,205,0.2)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(151,187,205,1)",
data: data
}
]
};
var jsCallsStartingData = {
labels: labels,
datasets: [
{
fillColor: "rgba(238,210,2,0.2)",
strokeColor: "rgba(238,210,2,1)",
pointColor: "rgba(238,210,2,1)",
pointStrokeColor: "#fff",
data: data
}
]
};
latestLabel = cpuStartingData.labels[numPoints - 1];
// Reduce the animation steps for demo clarity.
cpuChart = new Chart(ctx).Line(cpuStartingData, {
scaleOverride: true,
scaleSteps: 10,
scaleStepWidth: 10,
scaleStartValue: 0,
animation: false
});
memoryChart = new Chart(memoryCtx).Line(memoryStartingData, {
animation: false
});
document.getElementById("memory-legend").innerHTML = memoryChart.generateLegend();
jsCallsChart = new Chart(jsCallsCtx).Bar(jsCallsStartingData, {
animation: false
});
function toggleMetrics(enable) {
if (metricsIntervalId) {
debuggerClearInterval(metricsIntervalId);
}
if (enable) {
// request metrics once every second
metricsIntervalId = debuggerSetInterval(function () {
ws.send(JSON.stringify({method: "requestMetrics"}));
}, 1000);
}
metricsCollectionEnabled = enable;
}
var MetricsToggle = React.createClass({
handleClick: function () {
var enabled = !this.props.metricsEnabled;
this.setProps({
metricsEnabled:enabled
});
toggleMetrics(enabled);
},
buttonText: function () {
if (this.props.metricsEnabled) {
return "Disable Metrics Collection"
} else {
return "Enable Metrics Collection"
}
},
render: function() {
return (
< input type = "button" onClick = {this.handleClick} value = {this.buttonText()} / >
);
}
});
React.render(
< MetricsToggle metricsEnabled = {true}/ > ,
document.getElementById('metrics-toggle')
);
var JSCall = React.createClass({
render: function() {
return (
< ul >
{this.props.data.map(function (subCall) {
return (
< li > {subCall.method}< em > "{subCall.args[0]}"< / em > < / li >
)
})}
< / ul >
);
}
});
var JSCallsList = React.createClass({
render: function() {
return (
< ul >
{this.props.data.map(function (batch) {
if (!batch.arguments.length) {
return (
< li >
{batch.moduleMethod}
< / li >
)
} else {
return (
< li >
{batch.moduleMethod}
< JSCall data = {batch.arguments[0]} > < / JSCall >
< / li >
)
}
})}
< / ul >
);
}
});
var jsCallsComponent = React.render(
< JSCallsList data = {[]}/ > ,
document.getElementById('js-calls')
);
function countJSCalls(batch) {
return batch.arguments.reduce(function (p, c) {
return p + c.length;
}, 0);
}
2015-03-19 19:10:41 +00:00
var sessionID = window.localStorage.getItem('sessionID');
window.localStorage.removeItem('sessionID');
window.onbeforeunload = function() {
if (sessionID) {
return 'If you reload this page, it is going to break the debugging session. ' +
2015-08-19 22:42:27 +00:00
'You should press ⌘R in simulator to reload.';
2015-03-19 19:10:41 +00:00
}
};
2015-08-07 22:01:40 +00:00
window.addEventListener('load', function () {
if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
document.getElementById('devtools-banner').style.display = 'block';
}
});
2015-07-16 21:16:50 +00:00
// Alias native implementations needed by the debugger before platform-specific
// implementations are loaded into the global namespace
var debuggerSetTimeout = window.setTimeout;
2015-08-21 07:57:43 +00:00
var debuggerSetInterval = window.setInterval;
var debuggerClearInterval = window.clearInterval;
2015-07-16 21:16:50 +00:00
var DebuggerWebSocket = window.WebSocket;
2015-03-19 19:10:41 +00:00
function setStatus(status) {
document.getElementById('status').innerHTML = status;
}
var messageHandlers = {
// This method is a bit hacky. Catalyst asks for a new clean JS runtime.
// The easiest way to do this is to reload this page. That also means that
// web socket connection will be lost. To send reply back we need to remember
// message id
'prepareJSRuntime': function(message) {
window.onbeforeunload = undefined;
window.localStorage.setItem('sessionID', message.id);
window.location.reload();
},
2015-04-20 21:35:45 +00:00
'executeApplicationScript': function(message, sendReply) {
2015-03-19 19:10:41 +00:00
for (var key in message.inject) {
window[key] = JSON.parse(message.inject[key]);
}
loadScript(message.url, sendReply.bind(null, null));
},
2015-04-20 21:35:45 +00:00
'executeJSCall': function(message, sendReply) {
2015-08-21 07:57:43 +00:00
if(metricsCollectionEnabled) {
numJsCalls += countJSCalls(message);
batchedJSCalls.unshift(message);
// show the last 5 batches
if (batchedJSCalls.length > 5) {
batchedJSCalls.pop();
}
jsCallsComponent.setProps({
data: batchedJSCalls
});
}
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 14:51:48 +00:00
var returnValue = null;
2015-03-19 19:10:41 +00:00
try {
if (window & & window.require) {
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 14:51:48 +00:00
var module = window.require(message.moduleName);
returnValue = module[message.moduleMethod].apply(module, message.arguments);
2015-03-19 19:10:41 +00:00
}
} finally {
sendReply(JSON.stringify(returnValue));
}
2015-08-21 07:57:43 +00:00
},
'usageMetrics': function (message) {
cpuChart.addData([message.deviceCPUUsage], '');
// Remove the first point so we dont just add values forever
cpuChart.removeData();
memoryChart.addData([
convertToMB(message.memoryUsage.resident_size),
convertToMB(message.memoryUsage.virtual_size)
], '');
// Remove the first point so we dont just add values forever
memoryChart.removeData();
jsCallsChart.addData([numJsCalls], '');
// Remove the first point so we dont just add values forever
jsCallsChart.removeData();
numJsCalls = 0;
2015-03-19 19:10:41 +00:00
}
2015-07-16 21:16:50 +00:00
};
2015-03-19 19:10:41 +00:00
2015-08-21 07:57:43 +00:00
function convertToMB(val) {
return ((val / 1024) / 1024);
}
2015-07-16 21:16:50 +00:00
function connectToDebuggerProxy() {
2015-08-21 07:57:43 +00:00
ws = new DebuggerWebSocket('ws://' + window.location.host + '/debugger-proxy');
2015-03-19 19:10:41 +00:00
2015-07-16 21:16:50 +00:00
ws.onopen = function() {
if (sessionID) {
2015-08-19 22:42:27 +00:00
setStatus('Debugger session #' + sessionID + ' active.');
2015-07-16 21:16:50 +00:00
ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)}));
} else {
2015-08-19 22:42:27 +00:00
setStatus('Waiting, press < span class = "shortcut" > ⌘R< / span > in simulator to reload and connect.');
2015-07-16 21:16:50 +00:00
}
2015-08-21 07:57:43 +00:00
// start collecting metrics
toggleMetrics(true);
2015-07-16 21:16:50 +00:00
};
2015-03-19 19:10:41 +00:00
2015-07-16 21:16:50 +00:00
ws.onmessage = function(message) {
var object = JSON.parse(message.data);
var sendReply = function(result) {
ws.send(JSON.stringify({replyID: object.id, result: result}));
};
var handler = messageHandlers[object.method];
if (handler) {
handler(object, sendReply);
} else {
console.warn('Unknown method: ' + object.method);
}
};
ws.onclose = function() {
setStatus('Disconnected from proxy. Attempting reconnection. Is node server running?');
2015-03-19 19:10:41 +00:00
2015-07-16 21:16:50 +00:00
sessionID = null;
window.localStorage.removeItem('sessionID');
debuggerSetTimeout(connectToDebuggerProxy, 100);
2015-08-21 07:57:43 +00:00
// stop collecting metrics
toggleMetrics(false);
2015-07-16 21:16:50 +00:00
};
2015-03-19 19:10:41 +00:00
}
2015-07-16 21:16:50 +00:00
connectToDebuggerProxy();
2015-03-19 19:10:41 +00:00
function loadScript(src, callback) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.onload = callback;
document.head.appendChild(script);
}
})();
< / script >
< style type = "text/css" >
2015-08-07 22:01:40 +00:00
body {
font-size: large;
2015-08-21 07:57:43 +00:00
margin: 20px;
2015-08-07 22:01:40 +00:00
padding: 0;
2015-08-21 07:57:43 +00:00
font-family: Helvetica Neue, Helvetica, Verdana, sans-serif;
2015-08-07 22:01:40 +00:00
font-weight: 200;
}
2015-03-19 19:10:41 +00:00
.shortcut {
2015-08-19 22:42:27 +00:00
font-family: "Monaco", monospace;
font-size: medium;
2015-03-19 19:10:41 +00:00
color: #eee;
background-color: #333;
padding: 4px;
border-radius: 4px;
letter-spacing: 3px;
}
2015-08-07 22:01:40 +00:00
#devtools-banner {
display: none;
background-color: #FDFDD5;
padding: 10px;
}
#devtools-banner h3 {
margin: 0;
font-weight: normal;
}
#devtools-banner a {
display: none;
padding: 10px 20px 10px 20px;
margin-bottom: 10px;
color: white;
text-decoration: none;
font-size: 11px;
text-shadow: 0 1px 1px rgba(0,0,0,0.1);
text-transform: uppercase;
font-weight: bold;
background-color: #4d7bd6;
border-radius: 2px;
border: 1px solid #2d53af;
display: inline-block;
}
.content {
padding: 10px;
2015-03-19 19:10:41 +00:00
}
2015-08-21 07:57:43 +00:00
.legend ul {
list-style: none;
margin: 0;
padding: 0;
font-size: 14px;
margin-top : 20px;
}
.legend li {
display: inline-block;
padding-left: 10px;
}
.legend span {
display: block;
width: 14px;
height: 14px;
border-radius: 7px;
float: left;
margin-top: 1px;
margin-right: 7px;
}
.legend {
margin-bottom: 40px;
}
.js-calls {
font-size: small;
}
.toggle-button {
margin-top: 15px;
}
.graph-column {
width: "49%";
display: inline-block;
}
.js-calls-column {
vertical-align:top;
margin-left: 20px;
}
2015-03-19 19:10:41 +00:00
< / style >
< / head >
< body >
2015-08-07 22:01:40 +00:00
< div id = "devtools-banner" >
< h3 > Install React DevTools< / h3 >
< p >
React Developer Tools is an extension that allows you to inspect the
React component hierarchies in the Chrome Developer Tools.
< / p >
< a href = "https://fb.me/react-devtools" target = "_blank" >
Install
< / a >
< / div >
< div class = "content" >
< p >
2015-08-19 22:42:27 +00:00
React Native JS code runs inside this Chrome tab.
2015-08-07 22:01:40 +00:00
< / p >
< p > Press < span class = "shortcut" > ⌘⌥J< / span > to open Developer Tools. Enable < a href = "http://stackoverflow.com/a/17324511/232122" target = "_blank" > Pause On Caught Exceptions< / a > for a better debugging experience.< / p >
2015-08-21 07:57:43 +00:00
< div > Status: < span id = "status" > Loading< / span > < / div >
< div id = "metrics-toggle" class = "toggle-button" > < / div >
< div class = "graph-column" >
< div >
< h2 > UI CPU Utilization< / h2 >
< div >
< canvas id = "cpu-utilization" width = "400" height = "250" > < / canvas >
< / div >
< / div >
< div >
< h2 > Memory Utilization< / h2 >
< div >
< canvas id = "memory-utilization" width = "400" height = "250" > < / canvas >
< / div >
< div id = "memory-legend" class = "legend" > < / div >
< / div >
< / div >
< div class = "graph-column js-calls-column" >
< h2 > JS Calls< / h2 >
< div >
< canvas id = "js-calls-graph" width = "400" height = "250" > < / canvas >
< / div >
< div id = "js-calls" class = "js-calls" >
< / div >
2015-08-07 22:01:40 +00:00
< / div >
2015-03-19 19:10:41 +00:00
< / body >
< / html >