// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser;
import android.util.Pair;
import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;
import org.chromium.base.UserData;
import org.chromium.build.annotations.DoNotInline;
import org.chromium.content.browser.webcontents.WebContentsImpl;
import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
import org.chromium.content_public.browser.JavascriptInjector;
import org.chromium.content_public.browser.WebContents;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/** Implementation class of the interface {@link JavascriptInjector}. */
@JNINamespace("content")
public class JavascriptInjectorImpl implements JavascriptInjector, UserData {
private static final class UserDataFactoryLazyHolder {
private static final UserDataFactory<JavascriptInjectorImpl> INSTANCE =
JavascriptInjectorImpl::new;
}
// The set is passed to native and stored in a weak reference, so ensure this
// strong reference is not optimized away by R8.
@DoNotInline private final Set<Object> mRetainedObjects = new HashSet<>();
private final Map<String, Pair<Object, Class>> mInjectedObjects = new HashMap<>();
private long mNativePtr;
/**
* @param webContents {@link WebContents} object.
* @return {@link JavascriptInjector} object used for the give WebContents. Creates one if not
* present.
*/
public static JavascriptInjector fromWebContents(WebContents webContents) {
JavascriptInjectorImpl javascriptInjector =
((WebContentsImpl) webContents)
.getOrSetUserData(
JavascriptInjectorImpl.class, UserDataFactoryLazyHolder.INSTANCE);
return javascriptInjector;
}
public JavascriptInjectorImpl(WebContents webContents) {
mNativePtr =
JavascriptInjectorImplJni.get()
.init(JavascriptInjectorImpl.this, webContents, mRetainedObjects);
}
@CalledByNative
private void onDestroy() {
mNativePtr = 0;
}
@Override
public Map<String, Pair<Object, Class>> getInterfaces() {
return mInjectedObjects;
}
@Override
public void setAllowInspection(boolean allow) {
if (mNativePtr != 0) {
JavascriptInjectorImplJni.get()
.setAllowInspection(mNativePtr, JavascriptInjectorImpl.this, allow);
}
}
@Override
public void addPossiblyUnsafeInterface(
Object object, String name, Class<? extends Annotation> requiredAnnotation) {
if (object == null) return;
if (mNativePtr != 0) {
mInjectedObjects.put(name, new Pair<Object, Class>(object, requiredAnnotation));
JavascriptInjectorImplJni.get()
.addInterface(
mNativePtr,
JavascriptInjectorImpl.this,
object,
name,
requiredAnnotation);
}
}
@Override
public void removeInterface(String name) {
mInjectedObjects.remove(name);
if (mNativePtr != 0) {
JavascriptInjectorImplJni.get()
.removeInterface(mNativePtr, JavascriptInjectorImpl.this, name);
}
}
@NativeMethods
interface Natives {
long init(JavascriptInjectorImpl caller, WebContents webContents, Object retainedObjects);
void setAllowInspection(
long nativeJavascriptInjector, JavascriptInjectorImpl caller, boolean allow);
void addInterface(
long nativeJavascriptInjector,
JavascriptInjectorImpl caller,
Object object,
String name,
Class requiredAnnotation);
void removeInterface(
long nativeJavascriptInjector, JavascriptInjectorImpl caller, String name);
}
}