From 9199c721deed602cafe99c1d0356b922c5106500 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Fri, 4 Mar 2016 07:33:52 -0800 Subject: [PATCH] WebWorker: Allow worker script to be loaded from the network in debug builds Summary:Making buck rebundle the worker script on every JS change is insanely slow. This allows the script to be downloaded for debug builds. The plan is to couple this with an implementation of `require.resolve` which will automatically insert the correct packager network path in DEV builds and the correct local path in release builds. e.g. var worker = new Worker(require.resolve('WebWorkerSample_getPrimesBetween.js')); Reviewed By: lexs Differential Revision: D2939279 fb-gh-sync-id: fbf64bbf1df1649b44e4b98ac504d095c10104a6 shipit-source-id: fbf64bbf1df1649b44e4b98ac504d095c10104a6 --- .../react/bridge/webworkers/WebWorkers.java | 48 +++++++++++++++++++ .../src/main/jni/react/JSCExecutor.cpp | 14 +++++- ReactAndroid/src/main/jni/react/Platform.cpp | 1 + ReactAndroid/src/main/jni/react/Platform.h | 3 ++ .../src/main/jni/react/jni/OnLoad.cpp | 1 + .../src/main/jni/react/jni/WebWorkers.h | 21 ++++++++ 6 files changed, 86 insertions(+), 2 deletions(-) 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(); + } }; } }