Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions OpacityCore/src/main/cpp/OpacityCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,27 @@ extern "C" const char *android_get_browser_cookies_for_current_url() {
return val_str;
}

extern "C" const char *android_eval_js(const char *js,
double timeout_in_seconds) {
JNIEnv *env = GetJniEnv();
jclass jOpacityCore = env->GetObjectClass(java_object);
jmethodID method = env->GetMethodID(
jOpacityCore, "evalJs", "(Ljava/lang/String;J)Ljava/lang/String;");
jstring jjs = env->NewStringUTF(js);
jlong timeout_ms = (jlong)(timeout_in_seconds * 1000.0);
auto result =
(jstring)env->CallObjectMethod(java_object, method, jjs, timeout_ms);
env->DeleteLocalRef(jjs);
if (result == nullptr) {
return strdup("{\"result\":null}");
}
const char *str = env->GetStringUTFChars(result, nullptr);
char *copy = strdup(str);
env->ReleaseStringUTFChars(result, str);
env->DeleteLocalRef(result);
return copy;
}

extern "C" const char *
android_get_browser_cookies_for_domain(const char *domain) {
JNIEnv *env = GetJniEnv();
Expand Down
7 changes: 6 additions & 1 deletion OpacityCore/src/main/jni/include/sdk.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ typedef const char *(*GetDeviceCodenameFn)(void);

typedef void (*IosWebviewChangeUrlFn)(const char*);

typedef const char *(*IosEvalJsFn)(const char*, double);




Expand Down Expand Up @@ -240,7 +242,8 @@ void register_ios_callbacks(IosPrepareRequestFn ios_prepare_request,
GetScreenDpiFn get_screen_dpi,
GetDeviceCpuFn get_device_cpu,
GetDeviceCodenameFn get_device_codename,
IosWebviewChangeUrlFn ios_webview_change_url);
IosWebviewChangeUrlFn ios_webview_change_url,
IosEvalJsFn ios_eval_js);

extern void secure_set(const char *key, const char *value);

Expand All @@ -262,6 +265,8 @@ extern const char *android_get_browser_cookies_for_domain(const char *domain);

extern void android_webview_change_url(const char *url);

extern const char *android_eval_js(const char *js, double timeout_in_seconds);

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Gravity
import android.view.ViewGroup
Expand Down Expand Up @@ -126,6 +128,11 @@ class InAppBrowserActivity : AppCompatActivity() {
Log.d("Opacity SDK", "storePostBody: url=$url (${body.length} bytes)")
pendingPostBodies[url] = body
}

@JavascriptInterface
fun notifyEvalResult(id: String, json: String) {
OpacityCore.notifyWebViewEvalResult(id, json)
}
}

@SuppressLint("WrongThread", "SetJavaScriptEnabled")
Expand Down Expand Up @@ -182,6 +189,7 @@ class InAppBrowserActivity : AppCompatActivity() {
supportActionBar?.setCustomView(closeButton, actionBarLayoutParams)
supportActionBar?.setDisplayShowCustomEnabled(true)

OpacityCore.setActiveWebViewActivity(this)
interceptExtensionEnabled = intent.getBooleanExtra("enableInterceptRequests", false)
// Clear cookies for private-mode-like behavior
CookieManager.getInstance().removeAllCookies(null)
Expand Down Expand Up @@ -593,9 +601,36 @@ class InAppBrowserActivity : AppCompatActivity() {

CookieManager.getInstance().removeAllCookies(null)
webView.destroy()
OpacityCore.setActiveWebViewActivity(null)
OpacityCore.onBrowserDestroyed()
}

/**
* Injects [js] into the standard WebView as an AsyncFunction (mirrors the GeckoView eval
* extension behaviour). When [fireAndForget] is false the result is delivered back via
* [OpacityJsBridge.notifyEvalResult] → [OpacityCore.notifyWebViewEvalResult].
*/
fun dispatchWebViewEval(id: String, js: String, fireAndForget: Boolean) {
val escapedJs = org.json.JSONObject.quote(js)
val wrappedJs = if (fireAndForget) {
"(function(){var AF=Object.getPrototypeOf(async function(){}).constructor;new AF($escapedJs)();})()"
} else {
val escapedId = org.json.JSONObject.quote(id)
"(function(){" +
"var AF=Object.getPrototypeOf(async function(){}).constructor;" +
"new AF($escapedJs)().then(function(__r){" +
"var __val=(__r!==undefined&&__r!==null)?__r:null;" +
"OpacityNative.notifyEvalResult($escapedId,JSON.stringify({result:__val}));" +
"}).catch(function(e){" +
"OpacityNative.notifyEvalResult($escapedId,JSON.stringify({error:String(e)}));" +
"});" +
"})()"
}
Handler(Looper.getMainLooper()).post {
webView.evaluateJavascript(wrappedJs, null)
}
}

companion object {
private const val INTERCEPT_SCRIPT = """
(function() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import com.opacitylabs.opacitycore.JsonConverter.Companion.mapToJsonElement
import com.opacitylabs.opacitycore.JsonConverter.Companion.parseJsonElementToAny
import kotlinx.coroutines.Dispatchers
Expand All @@ -28,6 +34,16 @@ object OpacityCore {
private var pendingCookies: MutableList<Pair<String, String>> = mutableListOf()
private var isBrowserActive = false

// --- eval state ---
private val pendingEvals = ConcurrentHashMap<String, PendingEval>()
private val evalIdCounter = AtomicInteger(0)
private var activeWebViewActivity: java.lang.ref.WeakReference<InAppBrowserActivity>? = null

private data class PendingEval(
val latch: CountDownLatch = CountDownLatch(1),
var result: String = "{\"result\":null}"
)

init {
System.loadLibrary("OpacityCore")
}
Expand Down Expand Up @@ -192,6 +208,37 @@ object OpacityCore {
isBrowserActive = false
}

fun setActiveWebViewActivity(activity: InAppBrowserActivity?) {
activeWebViewActivity = activity?.let { java.lang.ref.WeakReference(it) }
}

fun notifyWebViewEvalResult(id: String, json: String) {
val pending = pendingEvals[id] ?: return
pending.result = json
pending.latch.countDown()
}

fun evalJs(js: String, timeoutMs: Long): String {
val activity = activeWebViewActivity?.get()
if (activity == null || activity.isFinishing || activity.isDestroyed) {
return "{\"error\":\"no active webview\"}"
}

val id = "eval_${evalIdCounter.incrementAndGet()}"

if (timeoutMs == 0L) {
activity.dispatchWebViewEval(id, js, fireAndForget = true)
return "{\"result\":null}"
}

val pending = PendingEval()
pendingEvals[id] = pending
activity.dispatchWebViewEval(id, js, fireAndForget = false)
pending.latch.await(timeoutMs, TimeUnit.MILLISECONDS)
pendingEvals.remove(id)
return pending.result
}

private fun parseOpacityError(error: String?): OpacityError {
if (error == null) {
return OpacityError("UnknownError", "No Message")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class MainActivity : ComponentActivity() {
requireNotNull(opacityApiKey) { "Opacity API key is null" }

OpacityCore.setContext(this)
OpacityCore.initialize(opacityApiKey, false, OpacityCore.Environment.PRODUCTION, false)
OpacityCore.initialize(opacityApiKey, false, OpacityCore.Environment.LOCAL, false)

Log.d("MainActivity", "Opacity SDK initialized and MainActivity loaded")

Expand Down Expand Up @@ -177,7 +177,7 @@ class MainActivity : ComponentActivity() {
OpacityCore.initialize(
opacityApiKey,
false,
OpacityCore.Environment.PRODUCTION,
OpacityCore.Environment.LOCAL,
true
)
Log.d("MainActivity", "Opacity SDK re-initialized")
Expand Down