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
This commit is contained in:
Andy Street 2016-03-04 07:33:52 -08:00 committed by Facebook Github Bot 2
parent d6ded2f807
commit 9199c721de
6 changed files with 86 additions and 2 deletions

View File

@ -9,10 +9,20 @@
package com.facebook.react.bridge.webworkers; package com.facebook.react.bridge.webworkers;
import java.io.File;
import java.io.IOException;
import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.queue.MessageQueueThread; import com.facebook.react.bridge.queue.MessageQueueThread;
import com.facebook.react.bridge.queue.MessageQueueThreadImpl; import com.facebook.react.bridge.queue.MessageQueueThreadImpl;
import com.facebook.react.bridge.queue.ProxyQueueThreadExceptionHandler; 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 @DoNotStrip
public class WebWorkers { 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 * Creates a new MessageQueueThread for a background web worker owned by the JS thread with the
* given MessageQueueThread. * given MessageQueueThread.
*/ */
@DoNotStrip
public static MessageQueueThread createWebWorkerThread(int id, MessageQueueThread ownerThread) { public static MessageQueueThread createWebWorkerThread(int id, MessageQueueThread ownerThread) {
return MessageQueueThreadImpl.startNewBackgroundThread( return MessageQueueThreadImpl.startNewBackgroundThread(
"web-worker-" + id, "web-worker-" + id,
new ProxyQueueThreadExceptionHandler(ownerThread)); 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);
}
}
} }

View File

@ -118,8 +118,18 @@ JSCExecutor::JSCExecutor(
setGlobalVariable(it.first, it.second); setGlobalVariable(it.first, it.second);
} }
// TODO(9604438): Protect against script does not exist // Try to load the script from the network if script is a URL
std::string scriptSrc = WebWorkerUtil::loadScriptFromAssets(script); // 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 // TODO(9994180): Throw on error
loadApplicationScript(scriptSrc, script); loadApplicationScript(scriptSrc, script);
}); });

View File

@ -16,6 +16,7 @@ GetCurrentMessageQueueThread getCurrentMessageQueueThread;
namespace WebWorkerUtil { namespace WebWorkerUtil {
WebWorkerQueueFactory createWebWorkerThread; WebWorkerQueueFactory createWebWorkerThread;
LoadScriptFromAssets loadScriptFromAssets; LoadScriptFromAssets loadScriptFromAssets;
LoadScriptFromNetworkSync loadScriptFromNetworkSync;
}; };
namespace PerfLogging { namespace PerfLogging {

View File

@ -29,6 +29,9 @@ extern WebWorkerQueueFactory createWebWorkerThread;
using LoadScriptFromAssets = std::function<std::string(const std::string& assetName)>; using LoadScriptFromAssets = std::function<std::string(const std::string& assetName)>;
extern LoadScriptFromAssets loadScriptFromAssets; extern LoadScriptFromAssets loadScriptFromAssets;
using LoadScriptFromNetworkSync = std::function<std::string(const std::string& url, const std::string& tempfileName)>;
extern LoadScriptFromNetworkSync loadScriptFromNetworkSync;
}; };
namespace PerfLogging { namespace PerfLogging {

View File

@ -914,6 +914,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
[] (const std::string& assetName) { [] (const std::string& assetName) {
return loadScriptFromAssets(assetName); return loadScriptFromAssets(assetName);
}; };
WebWorkerUtil::loadScriptFromNetworkSync = WebWorkers::loadScriptFromNetworkSync;
MessageQueues::getCurrentMessageQueueThread = MessageQueues::getCurrentMessageQueueThread =
[] { [] {
return std::unique_ptr<MessageQueueThread>( return std::unique_ptr<MessageQueueThread>(

View File

@ -2,7 +2,10 @@
#pragma once #pragma once
#include <fstream>
#include <memory> #include <memory>
#include <string>
#include <sstream>
#include <jni.h> #include <jni.h>
#include <folly/Memory.h> #include <folly/Memory.h>
@ -24,6 +27,24 @@ public:
auto res = method(WebWorkers::javaClassStatic(), id, static_cast<JMessageQueueThread*>(ownerMessageQueueThread)->jobj()); auto res = method(WebWorkers::javaClassStatic(), id, static_cast<JMessageQueueThread*>(ownerMessageQueueThread)->jobj());
return folly::make_unique<JMessageQueueThread>(res); return folly::make_unique<JMessageQueueThread>(res);
} }
static std::string loadScriptFromNetworkSync(const std::string& url, const std::string& tempfileName) {
static auto method = WebWorkers::javaClassStatic()->
getStaticMethod<void(jstring, jstring)>("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();
}
}; };
} } } }