From f571d28e68494ff7968a4dcefaba29d89617be36 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Tue, 15 Nov 2016 08:55:39 -0800 Subject: [PATCH] Allow launching inspector from dev menu Reviewed By: davidaurelio Differential Revision: D4095356 fbshipit-source-id: 46e43578cdcd663316efb82dffde27b77294c5c0 --- .../com/facebook/react/bridge/Inspector.java | 10 ++++++ .../react/devsupport/DevServerHelper.java | 6 ++++ .../devsupport/DevSupportManagerImpl.java | 14 ++++++++ .../InspectorPackagerConnection.java | 9 +++++ .../middleware/getDevToolsMiddleware.js | 29 ++++------------ local-cli/server/util/inspectorProxy.js | 26 +++++++++++--- local-cli/server/util/launchChrome.js | 34 +++++++++++++++++++ 7 files changed, 102 insertions(+), 26 deletions(-) create mode 100644 local-cli/server/util/launchChrome.js diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/Inspector.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/Inspector.java index 18e9cfa98..6abe166fe 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/Inspector.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/Inspector.java @@ -19,6 +19,16 @@ public class Inspector { 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 getPages() { try { return Arrays.asList(instance().getPagesNative()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 407612ea5..011a4d324 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -162,6 +162,12 @@ public class DevServerHelper { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + public void openInspector(String id) { + if (mInspectorPackagerConnection != null) { + mInspectorPackagerConnection.sendOpenEvent(id); + } + } + public void closeInspectorConnection() { new AsyncTask() { @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index 9a86c15e8..c7288d5f0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -42,6 +42,7 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.react.R; import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler; +import com.facebook.react.bridge.Inspector; import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; @@ -358,6 +359,19 @@ public class DevSupportManagerImpl implements DevSupportManager, PackagerCommand handleReloadJS(); } }); + if (Inspector.isSupported()) { + options.put( + "Debug JS on-device (experimental)", new DevOptionHandler() { + @Override + public void onOptionSelected() { + List 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( mDevSettings.isReloadOnJSChangeEnabled() ? mApplicationContext.getString(R.string.catalyst_live_reload_off) diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java index 9a35e0f67..8f786b8c5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java @@ -48,6 +48,15 @@ public class InspectorPackagerConnection { 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) throws JSONException, IOException { String event = message.getString("event"); diff --git a/local-cli/server/middleware/getDevToolsMiddleware.js b/local-cli/server/middleware/getDevToolsMiddleware.js index 6c4f7ab82..515e11ed8 100644 --- a/local-cli/server/middleware/getDevToolsMiddleware.js +++ b/local-cli/server/middleware/getDevToolsMiddleware.js @@ -8,31 +8,16 @@ */ 'use strict'; -var child_process = require('child_process'); -var execFile = require('child_process').execFile; -var fs = require('fs'); -var opn = require('opn'); -var path = require('path'); - -function getChromeAppName() { - switch (process.platform) { - case 'darwin': - return 'google chrome'; - case 'win32': - return 'chrome'; - default: - return 'google-chrome'; - } -} +const child_process = require('child_process'); +const execFile = require('child_process').execFile; +const fs = require('fs'); +const path = require('path'); +const launchChrome = require('../util/launchChrome'); function launchChromeDevTools(port) { var debuggerURL = 'http://localhost:' + port + '/debugger-ui'; console.log('Launching Dev Tools...'); - opn(debuggerURL, {app: [getChromeAppName()]}, function(err) { - if (err) { - console.error('Google Chrome exited with error:', err); - } - }); + launchChrome(debuggerURL); } function escapePath(path) { @@ -77,7 +62,7 @@ module.exports = function(options, isChromeConnected) { // TODO: Remove this case in the future console.log( '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: ' + 'https://facebook.github.io/react-native/docs/upgrading.html'); launchDevTools(options, isChromeConnected); diff --git a/local-cli/server/util/inspectorProxy.js b/local-cli/server/util/inspectorProxy.js index 855ac1bef..e10d98e2c 100644 --- a/local-cli/server/util/inspectorProxy.js +++ b/local-cli/server/util/inspectorProxy.js @@ -46,6 +46,7 @@ const parseUrl = require('url').parse; const WebSocket = require('ws'); const debug = require('debug')('ReactNativePackager:InspectorProxy'); +const launchChrome = require('./launchChrome'); type DevicePage = { id: string, @@ -80,6 +81,10 @@ type DisconnectEvent = Message<'disconnect', { pageId?: string, }>; +type OpenEvent = Message<'open', { + pageId?: string, +}>; + type GetPages = Message<'getPages', ?Array>; type Event = WrappedEvent | ConnectEvent | DisconnectEvent | GetPages; @@ -178,6 +183,8 @@ class Device { this._handleWrappedEvent(message); } else if (message.event === 'disconnect') { this._handleDisconnect(message); + } else if (message.event === 'open') { + this._handleOpen(message); } } @@ -197,6 +204,13 @@ class Device { 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) { const socket = this._connections.get(pageId); if (socket) { @@ -229,10 +243,7 @@ class InspectorProxy { } _makePage(server: Address, deviceId: string, deviceName: string, devicePage: DevicePage): Page { - // 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=${devicePage.id}`); - const wsUrl = `localhost:${server.port}/inspector/page?${query}`; + const wsUrl = makeInspectorPageUrl(deviceId, devicePage.id); return { id: `${deviceId}-${devicePage.id}`, 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 { const proxy = new InspectorProxy(); proxy.attachToServer(server, pathPrefix); diff --git a/local-cli/server/util/launchChrome.js b/local-cli/server/util/launchChrome.js new file mode 100644 index 000000000..aaa383e8f --- /dev/null +++ b/local-cli/server/util/launchChrome.js @@ -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;