// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "android_webview/js_sandbox/service/js_sandbox_isolate_callback.h"
#include <jni.h>
#include <sstream>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/files/file_util.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "android_webview/js_sandbox/js_sandbox_jni_headers/JsSandboxIsolateCallback_jni.h"
#include "android_webview/js_sandbox/js_sandbox_jni_headers/JsSandboxIsolateFdCallback_jni.h"
namespace android_webview {
JsSandboxIsolateCallback::JsSandboxIsolateCallback(
base::android::ScopedJavaGlobalRef<jobject>&& callback,
bool use_fd)
: callback_(std::move(callback)), use_fd(use_fd) {
CHECK(callback_) << "JsSandboxIsolateCallback java object is null";
}
JsSandboxIsolateCallback::~JsSandboxIsolateCallback() = default;
void JsSandboxIsolateCallback::ReportResult(const std::string& result) {
JNIEnv* env = base::android::AttachCurrentThread();
if (use_fd) {
base::ScopedFD read_fd, write_fd;
CHECK(base::CreatePipe(&read_fd, &write_fd));
Java_JsSandboxIsolateFdCallback_onResult(
env, UseCallback(), static_cast<jint>(read_fd.release()),
static_cast<jint>(result.length()));
// This might return false due to EPIPE if the client closes the fd without
// reading from it. That is not an error for our use case.
base::WriteFileDescriptor(write_fd.get(), std::move(result));
} else {
base::android::ScopedJavaLocalRef<jstring> java_string_result =
base::android::ConvertUTF8ToJavaString(env, result);
Java_JsSandboxIsolateCallback_onResult(env, UseCallback(),
java_string_result);
}
}
void JsSandboxIsolateCallback::ReportJsEvaluationError(
const std::string& error) {
ReportError(ErrorType::kJsEvaluationError, error);
}
void JsSandboxIsolateCallback::ReportFileDescriptorIOFailedError(
const std::string& error) {
ReportError(ErrorType::kFileDescriptorIOFailedError, error);
}
void JsSandboxIsolateCallback::ReportMemoryLimitExceededError(
const uint64_t memory_limit,
const uint64_t v8_heap_usage,
const uint64_t non_v8_heap_usage) {
std::ostringstream details;
details << "Memory limit exceeded.\n";
if (memory_limit > 0) {
details << "Memory limit: " << memory_limit << " bytes\n";
} else {
details << "Memory limit not explicitly configured\n";
}
details << "V8 heap usage: " << v8_heap_usage << " bytes\n";
details << "Non-V8 heap usage: " << non_v8_heap_usage << " bytes\n";
ReportError(ErrorType::kMemoryLimitExceeded, details.str());
}
void JsSandboxIsolateCallback::ReportError(const ErrorType error_type,
const std::string& error) {
JNIEnv* env = base::android::AttachCurrentThread();
if (use_fd) {
base::ScopedFD read_fd, write_fd;
CHECK(base::CreatePipe(&read_fd, &write_fd));
Java_JsSandboxIsolateFdCallback_onError(
env, UseCallback(), static_cast<jint>(error_type),
static_cast<jint>(read_fd.release()),
static_cast<jint>(error.length()));
// This might return false due to EPIPE if the client closes the fd without
// reading from it. That is not an error for our use case.
base::WriteFileDescriptor(write_fd.get(), std::move(error));
} else {
base::android::ScopedJavaLocalRef<jstring> java_string_error =
base::android::ConvertUTF8ToJavaString(env, error);
Java_JsSandboxIsolateCallback_onError(
env, UseCallback(), static_cast<jint>(error_type), java_string_error);
}
}
base::android::ScopedJavaGlobalRef<jobject>
JsSandboxIsolateCallback::UseCallback() {
CHECK(callback_) << "Double use of isolate callback";
// Move resets callback_ to null
return std::move(callback_);
}
} // namespace android_webview