webview 源码追踪addJavascriptInterface(android10)

参考

记一次印象深刻的Bug追踪过程 - 简书

WebView 源码分析 - 简书

我们在androidstudio里面点击addJavascriptInterface方法进入源代码:

 public void addJavascriptInterface(Object object, String name) {
        checkThread();
        mProvider.addJavascriptInterface(object, name);
    }

发现是mProvider调用了方法,接下来我们就寻找mProvider的实例化地方

webview.java关键代码

private void ensureProviderCreated() {
        checkThread();
        if (mProvider == null) {
            // As this can get called during the base class constructor chain, pass the minimum
            // number of dependencies here; the rest are deferred to init().
            mProvider = getFactory().createWebView(this, new PrivateAccess());
        }
    }

    @UnsupportedAppUsage
    private static WebViewFactoryProvider getFactory() {
        return WebViewFactory.getProvider();
    }

    @UnsupportedAppUsage
    private final Looper mWebViewThread = Looper.myLooper();

WebViewFactory.java 关键代码

/** @hide */
    private static final String CHROMIUM_WEBVIEW_FACTORY =
            "com.android.webview.chromium.WebViewChromiumFactoryProviderForQ";

    private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";

    private static final String LOGTAG = "WebViewFactory";

static WebViewFactoryProvider getProvider() {
        synchronized (sProviderLock) {
            //......省略
            try {
                Class<WebViewFactoryProvider> providerClass = getProviderClass();
                Method staticFactory = null;
                try {
                    staticFactory = providerClass.getMethod(
                        CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
                } catch (Exception e) {
                    if (DEBUG) {
                        Log.w(LOGTAG, "error instantiating provider with static factory method", e);
                    }
                }

                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation");
                try {
                    sProviderInstance = (WebViewFactoryProvider)
                            staticFactory.invoke(null, new WebViewDelegate());
                    if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
                    return sProviderInstance;
                } catch (Exception e) {
                    Log.e(LOGTAG, "error instantiating provider", e);
                    throw new AndroidRuntimeException(e);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
            }
        }
    }
private static Class<WebViewFactoryProvider> getProviderClass() {
    Context webViewContext = null;
    Application initialApplication = AppGlobals.getInitialApplication();
    try {
        try {
            webViewContext = getWebViewContextAndSetProvider();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
        }
        try {
            initialApplication.getAssets().addAssetPathAsSharedLibrary(
                    webViewContext.getApplicationInfo().sourceDir);
            ClassLoader clazzLoader = webViewContext.getClassLoader();

            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
            WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
                    getWebViewLibrary(sPackageInfo.applicationInfo));
            try {
                return getWebViewProviderClass(clazzLoader);
            } finally {
            }
        } catch (ClassNotFoundException e) {
           
            throw new AndroidRuntimeException(e);
        } finally {
            
        }
    } catch (MissingWebViewPackageException e) {
        
        throw new AndroidRuntimeException(e);
    }
}
/**
 * @hide
 */
public static Class<WebViewFactoryProvider> getWebViewProviderClass(ClassLoader clazzLoader)
        throws ClassNotFoundException {
    return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
            true, clazzLoader);
}

发现调用com.android.webview.chromium.WebViewChromiumFactoryProviderForQ的create方法

package com.android.webview.chromium;
class WebViewChromiumFactoryProviderForQ extends WebViewChromiumFactoryProvider {
    public static WebViewChromiumFactoryProvider create(android.webkit.WebViewDelegate delegate) {
        return new WebViewChromiumFactoryProviderForQ(delegate);
    }
    protected WebViewChromiumFactoryProviderForQ(android.webkit.WebViewDelegate delegate) {
        super(delegate);
    }
}

上文中一开始 我们发现webview的ensureProviderCreated()方法调用了

mProvider =getFactory().createWebView(this, new PrivateAccess());

进入父类代码WebViewChromiumFactoryProvider类查看createWebView方法,由此我们发现了

WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);具体代码如下:

/**
     * Constructor called by the API 22 version of {@link WebViewFactory} and later.
     */
    public WebViewChromiumFactoryProvider(android.webkit.WebViewDelegate delegate) {
        initialize(WebViewDelegateFactory.createProxyDelegate(delegate));
    }

........省略...
    @Override
    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
        WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);

        synchronized (mLock) {
            if (mWebViewsToStart != null) {
                mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc));
            }
        }

        return wvc;
    }

接下来进入WebViewChromium 我们查看具体的webview 方法实现,webview方法都是WebViewChromium代理实现的,下面我们看下最常用的addJavascriptInterface方法调用了mAwContents.addPossiblyUnsafeJavascriptInterface

@Override
    public void addJavascriptInterface(final Object obj, final String interfaceName) {
        if (checkNeedsPost()) {
            mRunQueue.addTask(new Runnable() {
                @Override
                public void run() {
                    addJavascriptInterface(obj, interfaceName);
                }
            });
            return;
        }
        Class<? extends Annotation> requiredAnnotation = null;
        if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
           requiredAnnotation = JavascriptInterface.class;
        }
        mAwContents.addPossiblyUnsafeJavascriptInterface(obj, interfaceName, requiredAnnotation);
    }

AwContent在哪里创建的呢?

仔细查找WebViewChromium这个类,我们发现AwContent是在initForReal方法中被创建的。而initForReal调用来自init方法。可是,init方法是在哪里调用的呢?答案是:WebView。看下面代码倒数第三行:mProvider.init(javaScriptInterfaces, privateBrowsing);即是调用WebViewChromium的init()方法 然后在init()方法里面调用initForReal();

protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
        super(context, attrs, defStyleAttr, defStyleRes);

        // WebView is important by default, unless app developer overrode attribute.
        if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
            setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
        }

        if (context == null) {
            throw new IllegalArgumentException("Invalid context argument");
        }
        if (mWebViewThread == null) {
            throw new RuntimeException(
                "WebView cannot be initialized on a thread that has no Looper.");
        }
        sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
                Build.VERSION_CODES.JELLY_BEAN_MR2;
        checkThread();

        ensureProviderCreated();
        mProvider.init(javaScriptInterfaces, privateBrowsing);
        // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
        CookieSyncManager.setGetInstanceIsAllowed();
    }

OK,继续往下,看AwContent是怎么创建的。

private void initForReal() {
        AwContentsStatics.setRecordFullDocument(sRecordWholeDocumentEnabledByApi
                || mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP);
        mAwContents = new AwContents(mFactory.getBrowserContextOnUiThread(), mWebView, mContext,
                new InternalAccessAdapter(), new WebViewNativeDrawGLFunctorFactory(),
                mContentsClientAdapter, mWebSettings.getAwSettings(),
                new AwContents.DependencyFactory() {
                    @Override
                    public AutofillProvider createAutofillProvider(
                            Context context, ViewGroup containerView) {
                        return mFactory.createAutofillProvider(context, mWebView);
                    }
                });
        if (mAppTargetSdkVersion >= Build.VERSION_CODES.KITKAT) {
            // On KK and above, favicons are automatically downloaded as the method
            // old apps use to enable that behavior is deprecated.
            AwContents.setShouldDownloadFavicons();
        }
        if (mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
            // Prior to Lollipop, JavaScript objects injected via addJavascriptInterface
            // were not inspectable.
            mAwContents.disableJavascriptInterfacesInspection();
        }
        // TODO: This assumes AwContents ignores second Paint param.
        mAwContents.setLayerType(mWebView.getLayerType(), null);
    }

接下来我们继续看Awcontents类中的addPossiblyUnsafeJavascriptInterface方法具体实现

private void setNewAwContents(long newAwContentsPtr) {
        if (mNativeAwContents != 0) {
            destroyNatives();
            mContentViewCore = null;
            mWebContents = null;
            mNavigationController = null;
        }

        .....
        mContentViewCore = createAndInitializeContentViewCore(
                mContainerView, mContext, mInternalAccessAdapter, nativeWebContents,
                new AwGestureStateListener(), mContentViewClient, mZoomControls, mWindowAndroid);
        nativeSetJavaPeers(mNativeAwContents, this, mWebContentsDelegate, mContentsClientBridge,
                mIoThreadClient, mInterceptNavigationDelegate);
        mWebContents = mContentViewCore.getWebContents();
        mNavigationController = mWebContents.getNavigationController();
        installWebContentsObserver();
        mSettings.setWebContents(nativeWebContents);
        nativeSetDipScale(mNativeAwContents, (float) mDIPScale);
        mContentViewCore.onShow();
    }
private static ContentViewCore createAndInitializeContentViewCore(ViewGroup containerView,
            Context context, InternalAccessDelegate internalDispatcher, long nativeWebContents,
            GestureStateListener gestureStateListener,
            ContentViewClient contentViewClient,
            ContentViewCore.ZoomControlsDelegate zoomControlsDelegate,
            WindowAndroid windowAndroid) {
        ContentViewCore contentViewCore = new ContentViewCore(context);
        contentViewCore.initialize(containerView, internalDispatcher, nativeWebContents,
                windowAndroid);
        contentViewCore.addGestureStateListener(gestureStateListener);
        contentViewCore.setContentViewClient(contentViewClient);
        contentViewCore.setZoomControlsDelegate(zoomControlsDelegate);
        return contentViewCore;
    }
/**
     * @see ContentViewCore#addPossiblyUnsafeJavascriptInterface(Object, String, Class)
     */
    public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
            Class<? extends Annotation> requiredAnnotation) {
        if (isDestroyed()) return;
        mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name, requiredAnnotation);
    }

继续进入ContentViewCore.java查看addPossiblyUnsafeJavascriptInterface方法

public void addJavascriptInterface(Object object, String name) {
        addPossiblyUnsafeJavascriptInterface(object, name, JavascriptInterface.class);
    }

    
    public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
            Class<? extends Annotation> requiredAnnotation) {
        if (mNativeContentViewCore != 0 && object != null) {
            mJavaScriptInterfaces.put(name, new Pair<Object, Class>(object, requiredAnnotation));
            nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation);
        }
    }

看方法名,nativeAddJavascriptInterface看起来最终调用来自于Native,继续往下看:

private native void nativeAddJavascriptInterface(int nativeContentViewCoreImpl, Object object,
            String name, Class requiredAnnotation, HashSet<Object> retainedObjectSet);

接下来看C++代码,这里的中间调用过程没有深究,但最终应该是来到了这里:

static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer,
        jobject javascriptObj, jstring interfaceName)
{
#ifdef ANDROID_INSTRUMENT
    TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter);
#endif
    WebCore::Frame* pFrame = 0;
    if (nativeFramePointer == 0)
        pFrame = GET_NATIVE_FRAME(env, obj);
    else
        pFrame = (WebCore::Frame*)nativeFramePointer;
    LOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!");
    JavaVM* vm;
    env->GetJavaVM(&vm);
    LOGV("::WebCore:: addJSInterface: %p", pFrame);
#if USE(JSC)
    // Copied from qwebframe.cpp
    JSC::JSLock lock(false);
    WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame);
    if (window) {
        JSC::Bindings::RootObject *root = pFrame->script()->bindingRootObject();
        JSC::Bindings::setJavaVM(vm);
        // Add the binding to JS environment
        JSC::ExecState* exec = window->globalExec();
        JSC::JSObject *addedObject = WeakJavaInstance::create(javascriptObj,
                root)->createRuntimeObject(exec);
        const jchar* s = env->GetStringChars(interfaceName, NULL);
        if (s) {
            // Add the binding name to the window's table of child objects.
            JSC::PutPropertySlot slot;
            window->put(exec, JSC::Identifier(exec, (const UChar *)s, 
                    env->GetStringLength(interfaceName)), addedObject, slot);
            env->ReleaseStringChars(interfaceName, s);
            checkException(env);
        }
    }
#endif  // USE(JSC)
#if USE(V8)
    if (pFrame) {
        const char* name = JSC::Bindings::getCharactersFromJStringInEnv(env, interfaceName);
        NPObject* obj = JSC::Bindings::JavaInstanceToNPObject(new JSC::Bindings::JavaInstance(javascriptObj));
        pFrame->script()->bindToWindowObject(pFrame, name, obj);
        // JavaInstanceToNPObject calls NPN_RetainObject on the
        // returned one (see CreateV8ObjectForNPObject in V8NPObject.cpp).
        // BindToWindowObject also increases obj's ref count and decrease
        // the ref count when the object is not reachable from JavaScript
        // side. Code here must release the reference count increased by
        // JavaInstanceToNPObject.
        _NPN_ReleaseObject(obj);
        JSC::Bindings::releaseCharactersForJString(interfaceName, name);
    }
#endif
}

这里的代码量较大,如果使用的是JSC引擎 ,我们主要关注下面这一行代码:

window->put(exec, JSC::Identifier(exec, (const UChar *)s, env->GetStringLength(interfaceName)), addedObject, slot);

最终数据的处理原来来自于C++端的window对象,这又是什么呢?继续看:

WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame);

这是在WebCore命名空间下面的JSDOMWindow对象,至此我们将一个android的java对象加入到了javascript的dom对象 完成了原生到js对象的注入全部过程

如果是V8引擎

pFrame->script()->bindToWindowObject(pFrame, name, obj);

将android的java对象转换为NPObject 并绑定到javascript的window对象中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值