diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactSettingsForTests.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactSettingsForTests.java index a605b268643f39..7990c398208eb7 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactSettingsForTests.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactSettingsForTests.java @@ -37,6 +37,11 @@ public boolean isElementInspectorEnabled() { return false; } + @Override + public boolean isNuclideJSDebugEnabled() { + return false; + } + @Override public boolean isRemoteJSDebugEnabled() { return false; diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevInternalSettings.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevInternalSettings.java index bcce3618b948ec..dec05232797a03 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevInternalSettings.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevInternalSettings.java @@ -14,6 +14,7 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.modules.debug.interfaces.DeveloperSettings; import com.facebook.react.packagerconnection.PackagerConnectionSettings; @@ -124,6 +125,11 @@ public void setBundleDeltasEnabled(boolean enabled) { mPreferences.edit().putBoolean(PREFS_JS_BUNDLE_DELTAS_KEY, enabled).apply(); } + @Override + public boolean isNuclideJSDebugEnabled() { + return ReactBuildConfig.IS_INTERNAL_BUILD && ReactBuildConfig.DEBUG; + } + @Override public boolean isRemoteJSDebugEnabled() { return mPreferences.getBoolean(PREFS_REMOTE_JS_DEBUG_KEY, false); 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 c214dd57c7f387..48dff5fc9a7bee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -12,8 +12,10 @@ import android.content.Context; import android.os.AsyncTask; import android.os.Handler; +import android.widget.Toast; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.R; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.network.OkHttpCallUtil; @@ -73,6 +75,7 @@ public class DevServerHelper { private static final String PACKAGER_STATUS_URL_FORMAT = "http://%s/status"; private static final String HEAP_CAPTURE_UPLOAD_URL_FORMAT = "http://%s/jscheapcaptureupload"; private static final String INSPECTOR_DEVICE_URL_FORMAT = "http://%s/inspector/device?name=%s&app=%s"; + private static final String INSPECTOR_ATTACH_URL_FORMAT = "http://%s/nuclide/attach-debugger-nuclide?title=%s&app=%s&device=%s"; private static final String SYMBOLICATE_URL_FORMAT = "http://%s/symbolicate"; private static final String OPEN_STACK_FRAME_URL_FORMAT = "http://%s/open-stack-frame"; @@ -224,6 +227,36 @@ protected Void doInBackground(Void... params) { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + public void attachDebugger(final Context context, final String title) { + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... ignore) { + return doSync(); + } + + public boolean doSync() { + try { + String attachToNuclideUrl = getInspectorAttachUrl(title); + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder().url(attachToNuclideUrl).build(); + client.newCall(request).execute(); + return true; + } catch (IOException e) { + FLog.e(ReactConstants.TAG, "Failed to send attach request to Inspector", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + if (!result) { + String message = context.getString(R.string.catalyst_debugjs_nuclide_failure); + Toast.makeText(context, message, Toast.LENGTH_LONG).show(); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + public void symbolicateStackTrace( Iterable stackFrames, final SymbolicationListener listener) { @@ -321,6 +354,16 @@ public String getInspectorDeviceUrl() { mPackageName); } + public String getInspectorAttachUrl(String title) { + return String.format( + Locale.US, + INSPECTOR_ATTACH_URL_FORMAT, + AndroidInfoHelpers.getServerHost(), + title, + mPackageName, + AndroidInfoHelpers.getFriendlyDeviceName()); + } + public BundleDownloader getBundleDownloader() { return mBundleDownloader; } 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 e4776c8ada4ea9..d00be1f2bb34d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -110,6 +110,9 @@ private static enum ErrorType { private static final String EXOPACKAGE_LOCATION_FORMAT = "/data/local/tmp/exopackage/%s//secondary-dex"; + public static final String EMOJI_HUNDRED_POINTS_SYMBOL = " \uD83D\uDCAF"; + public static final String EMOJI_FACE_WITH_NO_GOOD_GESTURE = " \uD83D\uDE45"; + private final Context mApplicationContext; private final ShakeDetector mShakeDetector; private final BroadcastReceiver mReloadAppBroadcastReceiver; @@ -395,16 +398,36 @@ public void showDevOptionsDialog() { LinkedHashMap options = new LinkedHashMap<>(); /* register standard options */ options.put( - mApplicationContext.getString(R.string.catalyst_reloadjs), new DevOptionHandler() { + mApplicationContext.getString(R.string.catalyst_reloadjs), + new DevOptionHandler() { @Override public void onOptionSelected() { handleReloadJS(); } }); + if (mDevSettings.isNuclideJSDebugEnabled()) { + // The concatenation is applied directly here because XML isn't emoji-friendly + String nuclideJsDebugMenuItemTitle = + mApplicationContext.getString(R.string.catalyst_debugjs_nuclide) + + EMOJI_HUNDRED_POINTS_SYMBOL; + options.put( + nuclideJsDebugMenuItemTitle, + new DevOptionHandler() { + @Override + public void onOptionSelected() { + mDevServerHelper.attachDebugger(mApplicationContext, "ReactNative"); + } + }); + } + String remoteJsDebugMenuItemTitle = + mDevSettings.isRemoteJSDebugEnabled() + ? mApplicationContext.getString(R.string.catalyst_debugjs_off) + : mApplicationContext.getString(R.string.catalyst_debugjs); + if (mDevSettings.isNuclideJSDebugEnabled()) { + remoteJsDebugMenuItemTitle += EMOJI_FACE_WITH_NO_GOOD_GESTURE; + } options.put( - mDevSettings.isRemoteJSDebugEnabled() ? - mApplicationContext.getString(R.string.catalyst_debugjs_off) : - mApplicationContext.getString(R.string.catalyst_debugjs), + remoteJsDebugMenuItemTitle, new DevOptionHandler() { @Override public void onOptionSelected() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/interfaces/DeveloperSettings.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/interfaces/DeveloperSettings.java index 73cec8d26a44ab..6c51bc6891798d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/interfaces/DeveloperSettings.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/interfaces/DeveloperSettings.java @@ -39,6 +39,11 @@ public interface DeveloperSettings { */ boolean isElementInspectorEnabled(); + /** + * @return Whether Nuclide JS debugging is enabled. + */ + boolean isNuclideJSDebugEnabled(); + /** * @return Whether remote JS debugging is enabled. */ diff --git a/ReactAndroid/src/main/res/devsupport/values/strings.xml b/ReactAndroid/src/main/res/devsupport/values/strings.xml index b6811017a125f6..594be948cb3f7a 100644 --- a/ReactAndroid/src/main/res/devsupport/values/strings.xml +++ b/ReactAndroid/src/main/res/devsupport/values/strings.xml @@ -1,6 +1,8 @@ Reload + Debug JS in Nuclide + The request to attach Nuclide could not reach Metro Bundler! Debug JS Remotely Stop Remote JS Debugging Enable Hot Reloading