diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java index 1cc874e9c..c2ab78571 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java @@ -9,10 +9,20 @@ package com.facebook.react.bridge.webworkers; +import java.io.File; +import java.io.IOException; + import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.queue.MessageQueueThread; import com.facebook.react.bridge.queue.MessageQueueThreadImpl; import com.facebook.react.bridge.queue.ProxyQueueThreadExceptionHandler; +import com.facebook.react.common.build.ReactBuildConfig; + +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import okio.Okio; +import okio.Sink; @DoNotStrip public class WebWorkers { @@ -21,9 +31,47 @@ public class WebWorkers { * Creates a new MessageQueueThread for a background web worker owned by the JS thread with the * given MessageQueueThread. */ + @DoNotStrip public static MessageQueueThread createWebWorkerThread(int id, MessageQueueThread ownerThread) { return MessageQueueThreadImpl.startNewBackgroundThread( "web-worker-" + id, new ProxyQueueThreadExceptionHandler(ownerThread)); } + + /** + * Utility method used to help develop web workers on debug builds. In release builds, worker + * scripts need to be packaged with the app, but in dev mode we want to fetch/reload the worker + * script on the fly from the packager. This method fetches the given URL *synchronously* and + * writes it to the specified temp file. + * + * This is exposed from Java only because we don't want to add a C++ networking library dependency + * + * NB: The caller is responsible for deleting the file specified by outFileName when they're done + * with it. + * NB: We write to a temp file instead of returning a String because, depending on the size of the + * worker script, allocating the full script string on the Java heap can cause an OOM. + */ + @DoNotStrip + public static void downloadScriptToFileSync(String url, String outFileName) { + if (!ReactBuildConfig.DEBUG) { + throw new RuntimeException( + "For security reasons, downloading scripts is only allowed in debug builds."); + } + + OkHttpClient client = new OkHttpClient(); + final File out = new File(outFileName); + + Request request = new Request.Builder() + .url(url) + .build(); + + try { + Response response = client.newCall(request).execute(); + + Sink output = Okio.sink(out); + Okio.buffer(response.body().source()).readAll(output); + } catch (IOException e) { + throw new RuntimeException("Exception downloading web worker script to file", e); + } + } } diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 0341677d9..048758ab6 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -118,8 +118,18 @@ JSCExecutor::JSCExecutor( setGlobalVariable(it.first, it.second); } - // TODO(9604438): Protect against script does not exist - std::string scriptSrc = WebWorkerUtil::loadScriptFromAssets(script); + // Try to load the script from the network if script is a URL + // NB: For security, this will only work in debug builds + std::string scriptSrc; + if (script.find("http://") == 0 || script.find("https://") == 0) { + std::stringstream outfileBuilder; + outfileBuilder << m_deviceCacheDir << "/workerScript" << m_workerId << ".js"; + scriptSrc = WebWorkerUtil::loadScriptFromNetworkSync(script, outfileBuilder.str()); + } else { + // TODO(9604438): Protect against script does not exist + scriptSrc = WebWorkerUtil::loadScriptFromAssets(script); + } + // TODO(9994180): Throw on error loadApplicationScript(scriptSrc, script); }); diff --git a/ReactAndroid/src/main/jni/react/Platform.cpp b/ReactAndroid/src/main/jni/react/Platform.cpp index ace85bd92..270764440 100644 --- a/ReactAndroid/src/main/jni/react/Platform.cpp +++ b/ReactAndroid/src/main/jni/react/Platform.cpp @@ -16,6 +16,7 @@ GetCurrentMessageQueueThread getCurrentMessageQueueThread; namespace WebWorkerUtil { WebWorkerQueueFactory createWebWorkerThread; LoadScriptFromAssets loadScriptFromAssets; +LoadScriptFromNetworkSync loadScriptFromNetworkSync; }; namespace PerfLogging { diff --git a/ReactAndroid/src/main/jni/react/Platform.h b/ReactAndroid/src/main/jni/react/Platform.h index 608fbc1a8..da214ee85 100644 --- a/ReactAndroid/src/main/jni/react/Platform.h +++ b/ReactAndroid/src/main/jni/react/Platform.h @@ -29,6 +29,9 @@ extern WebWorkerQueueFactory createWebWorkerThread; using LoadScriptFromAssets = std::function; extern LoadScriptFromAssets loadScriptFromAssets; + +using LoadScriptFromNetworkSync = std::function; +extern LoadScriptFromNetworkSync loadScriptFromNetworkSync; }; namespace PerfLogging { diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 4748d825c..6dc5a0ca1 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -914,6 +914,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { [] (const std::string& assetName) { return loadScriptFromAssets(assetName); }; + WebWorkerUtil::loadScriptFromNetworkSync = WebWorkers::loadScriptFromNetworkSync; MessageQueues::getCurrentMessageQueueThread = [] { return std::unique_ptr( diff --git a/ReactAndroid/src/main/jni/react/jni/WebWorkers.h b/ReactAndroid/src/main/jni/react/jni/WebWorkers.h index e243fa8bf..a37df26c6 100644 --- a/ReactAndroid/src/main/jni/react/jni/WebWorkers.h +++ b/ReactAndroid/src/main/jni/react/jni/WebWorkers.h @@ -2,7 +2,10 @@ #pragma once +#include #include +#include +#include #include #include @@ -24,6 +27,24 @@ public: auto res = method(WebWorkers::javaClassStatic(), id, static_cast(ownerMessageQueueThread)->jobj()); return folly::make_unique(res); } + + static std::string loadScriptFromNetworkSync(const std::string& url, const std::string& tempfileName) { + static auto method = WebWorkers::javaClassStatic()-> + getStaticMethod("downloadScriptToFileSync"); + method( + WebWorkers::javaClassStatic(), + jni::make_jstring(url).get(), + jni::make_jstring(tempfileName).get()); + + std::ifstream tempFile(tempfileName); + if (!tempFile.good()) { + throw std::runtime_error("Didn't find worker script file at " + tempfileName); + } + std::stringstream buffer; + buffer << tempFile.rdbuf(); + std::remove(tempfileName.c_str()); + return buffer.str(); + } }; } }