Allow launching inspector from dev menu

Reviewed By: davidaurelio

Differential Revision: D4095356

fbshipit-source-id: 46e43578cdcd663316efb82dffde27b77294c5c0
This commit is contained in:
Alexander Blom 2016-11-15 08:55:39 -08:00 committed by Facebook Github Bot
parent 18184a83f1
commit f571d28e68
7 changed files with 102 additions and 26 deletions

View File

@ -19,6 +19,16 @@ public class Inspector {
private final HybridData mHybridData; private final HybridData mHybridData;
public static boolean isSupported() {
try {
// This isn't a very nice way to do this but it works :|
instance().getPagesNative();
return true;
} catch (UnsatisfiedLinkError e) {
return false;
}
}
public static List<Page> getPages() { public static List<Page> getPages() {
try { try {
return Arrays.asList(instance().getPagesNative()); return Arrays.asList(instance().getPagesNative());

View File

@ -162,6 +162,12 @@ public class DevServerHelper {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
public void openInspector(String id) {
if (mInspectorPackagerConnection != null) {
mInspectorPackagerConnection.sendOpenEvent(id);
}
}
public void closeInspectorConnection() { public void closeInspectorConnection() {
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override

View File

@ -42,6 +42,7 @@ import com.facebook.infer.annotation.Assertions;
import com.facebook.react.R; import com.facebook.react.R;
import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler; import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
import com.facebook.react.bridge.Inspector;
import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
@ -358,6 +359,19 @@ public class DevSupportManagerImpl implements DevSupportManager, PackagerCommand
handleReloadJS(); handleReloadJS();
} }
}); });
if (Inspector.isSupported()) {
options.put(
"Debug JS on-device (experimental)", new DevOptionHandler() {
@Override
public void onOptionSelected() {
List<Inspector.Page> pages = Inspector.getPages();
if (pages.size() > 0) {
// TODO: We should get the actual page id instead of the first one.
mDevServerHelper.openInspector(String.valueOf(pages.get(0).getId()));
}
}
});
}
options.put( options.put(
mDevSettings.isReloadOnJSChangeEnabled() mDevSettings.isReloadOnJSChangeEnabled()
? mApplicationContext.getString(R.string.catalyst_live_reload_off) ? mApplicationContext.getString(R.string.catalyst_live_reload_off)

View File

@ -48,6 +48,15 @@ public class InspectorPackagerConnection {
mConnection.close(); mConnection.close();
} }
public void sendOpenEvent(String pageId) {
try {
JSONObject payload = makePageIdPayload(pageId);
sendEvent("open", payload);
} catch (JSONException | IOException e) {
FLog.e(TAG, "Failed to open page", e);
}
}
void handleProxyMessage(JSONObject message) void handleProxyMessage(JSONObject message)
throws JSONException, IOException { throws JSONException, IOException {
String event = message.getString("event"); String event = message.getString("event");

View File

@ -8,31 +8,16 @@
*/ */
'use strict'; 'use strict';
var child_process = require('child_process'); const child_process = require('child_process');
var execFile = require('child_process').execFile; const execFile = require('child_process').execFile;
var fs = require('fs'); const fs = require('fs');
var opn = require('opn'); const path = require('path');
var path = require('path'); const launchChrome = require('../util/launchChrome');
function getChromeAppName() {
switch (process.platform) {
case 'darwin':
return 'google chrome';
case 'win32':
return 'chrome';
default:
return 'google-chrome';
}
}
function launchChromeDevTools(port) { function launchChromeDevTools(port) {
var debuggerURL = 'http://localhost:' + port + '/debugger-ui'; var debuggerURL = 'http://localhost:' + port + '/debugger-ui';
console.log('Launching Dev Tools...'); console.log('Launching Dev Tools...');
opn(debuggerURL, {app: [getChromeAppName()]}, function(err) { launchChrome(debuggerURL);
if (err) {
console.error('Google Chrome exited with error:', err);
}
});
} }
function escapePath(path) { function escapePath(path) {
@ -77,7 +62,7 @@ module.exports = function(options, isChromeConnected) {
// TODO: Remove this case in the future // TODO: Remove this case in the future
console.log( console.log(
'The method /launch-chrome-devtools is deprecated. You are ' + 'The method /launch-chrome-devtools is deprecated. You are ' +
' probably using an application created with an older CLI with the ' + ' probably using an application created with an older CLI with the ' +
' packager of a newer CLI. Please upgrade your application: ' + ' packager of a newer CLI. Please upgrade your application: ' +
'https://facebook.github.io/react-native/docs/upgrading.html'); 'https://facebook.github.io/react-native/docs/upgrading.html');
launchDevTools(options, isChromeConnected); launchDevTools(options, isChromeConnected);

View File

@ -46,6 +46,7 @@ const parseUrl = require('url').parse;
const WebSocket = require('ws'); const WebSocket = require('ws');
const debug = require('debug')('ReactNativePackager:InspectorProxy'); const debug = require('debug')('ReactNativePackager:InspectorProxy');
const launchChrome = require('./launchChrome');
type DevicePage = { type DevicePage = {
id: string, id: string,
@ -80,6 +81,10 @@ type DisconnectEvent = Message<'disconnect', {
pageId?: string, pageId?: string,
}>; }>;
type OpenEvent = Message<'open', {
pageId?: string,
}>;
type GetPages = Message<'getPages', ?Array<DevicePage>>; type GetPages = Message<'getPages', ?Array<DevicePage>>;
type Event = WrappedEvent | ConnectEvent | DisconnectEvent | GetPages; type Event = WrappedEvent | ConnectEvent | DisconnectEvent | GetPages;
@ -178,6 +183,8 @@ class Device {
this._handleWrappedEvent(message); this._handleWrappedEvent(message);
} else if (message.event === 'disconnect') { } else if (message.event === 'disconnect') {
this._handleDisconnect(message); this._handleDisconnect(message);
} else if (message.event === 'open') {
this._handleOpen(message);
} }
} }
@ -197,6 +204,13 @@ class Device {
this._removeConnection(pageId); this._removeConnection(pageId);
} }
_handleOpen(event: OpenEvent) {
const payload = nullthrows(event.payload);
const pageId = nullthrows(payload.pageId);
const url = DEVTOOLS_URL_BASE + makeInspectorPageUrl(this._id, pageId);
launchChrome(url);
}
_removeConnection(pageId: string) { _removeConnection(pageId: string) {
const socket = this._connections.get(pageId); const socket = this._connections.get(pageId);
if (socket) { if (socket) {
@ -229,10 +243,7 @@ class InspectorProxy {
} }
_makePage(server: Address, deviceId: string, deviceName: string, devicePage: DevicePage): Page { _makePage(server: Address, deviceId: string, deviceName: string, devicePage: DevicePage): Page {
// The inspector frontend doesn't handle urlencoded params so we const wsUrl = makeInspectorPageUrl(deviceId, devicePage.id);
// manually urlencode it and decode it on the other side in _createPageHandler
const query = querystring.escape(`device=${deviceId}&page=${devicePage.id}`);
const wsUrl = `localhost:${server.port}/inspector/page?${query}`;
return { return {
id: `${deviceId}-${devicePage.id}`, id: `${deviceId}-${devicePage.id}`,
title: devicePage.title, title: devicePage.title,
@ -393,6 +404,13 @@ function escapeHtmlSpecialChar(char: string): string {
); );
} }
function makeInspectorPageUrl(deviceId: string, pageId: string): string {
// The inspector frontend doesn't handle urlencoded params so we
// manually urlencode it and decode it on the other side in _createPageHandler
const query = querystring.escape(`device=${deviceId}&page=${pageId}`);
return `localhost:8081/inspector/page?${query}`;
}
function attachToServer(server: http.Server, pathPrefix: string): InspectorProxy { function attachToServer(server: http.Server, pathPrefix: string): InspectorProxy {
const proxy = new InspectorProxy(); const proxy = new InspectorProxy();
proxy.attachToServer(server, pathPrefix); proxy.attachToServer(server, pathPrefix);

View File

@ -0,0 +1,34 @@
/**
* 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.
*
* @flow
*/
'use strict';
const opn = require('opn');
function getChromeAppName(): string {
switch (process.platform) {
case 'darwin':
return 'google chrome';
case 'win32':
return 'chrome';
default:
return 'google-chrome';
}
}
function launchChrome(url: string) {
opn(url, {app: [getChromeAppName()]}, function(err) {
if (err) {
console.error('Google Chrome exited with error:', err);
}
});
}
module.exports = launchChrome;