WebViewChromiumFactoryProvider.java revision 2de7a52b3eeaa7b77570d8c0ce01b80ba38a754a
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.webview.chromium;
18
19import android.app.ActivityThread;
20import android.content.Context;
21import android.content.SharedPreferences;
22import android.os.Build;
23import android.os.Looper;
24import android.util.Log;
25import android.webkit.CookieManager;
26import android.webkit.GeolocationPermissions;
27import android.webkit.WebIconDatabase;
28import android.webkit.WebStorage;
29import android.webkit.WebView;
30import android.webkit.WebViewDatabase;
31import android.webkit.WebViewFactoryProvider;
32import android.webkit.WebViewProvider;
33
34import org.chromium.android_webview.AwBrowserContext;
35import org.chromium.android_webview.AwBrowserProcess;
36import org.chromium.android_webview.AwContents;
37import org.chromium.android_webview.AwCookieManager;
38import org.chromium.android_webview.AwDevToolsServer;
39import org.chromium.android_webview.AwFormDatabase;
40import org.chromium.android_webview.AwGeolocationPermissions;
41import org.chromium.android_webview.AwQuotaManagerBridge;
42import org.chromium.android_webview.AwSettings;
43import org.chromium.base.CommandLine;
44import org.chromium.base.PathService;
45import org.chromium.base.ThreadUtils;
46import org.chromium.content.app.ContentMain;
47import org.chromium.content.app.LibraryLoader;
48import org.chromium.content.browser.ContentViewStatics;
49import org.chromium.content.browser.ResourceExtractor;
50import org.chromium.content.common.ProcessInitException;
51
52import java.lang.ref.WeakReference;
53import java.util.ArrayList;
54
55public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
56
57    private final String TAG = "WebViewChromiumFactoryProvider";
58
59    private static final String CHROMIUM_PREFS_NAME = "WebViewChromiumPrefs";
60    private static final String COMMAND_LINE_FILE = "/data/local/tmp/webview-command-line";
61
62    // Guards accees to the other members, and is notifyAll() signalled on the UI thread
63    // when the chromium process has been started.
64    private final Object mLock = new Object();
65
66    // Initialization guarded by mLock.
67    private AwBrowserContext mBrowserContext;
68    private Statics mStaticMethods;
69    private GeolocationPermissionsAdapter mGeolocationPermissions;
70    private CookieManagerAdapter mCookieManager;
71    private WebIconDatabaseAdapter mWebIconDatabase;
72    private WebStorageAdapter mWebStorage;
73    private WebViewDatabaseAdapter mWebViewDatabase;
74    private AwDevToolsServer mDevToolsServer;
75
76    private ArrayList<WeakReference<WebViewChromium>> mWebViewsToStart =
77              new ArrayList<WeakReference<WebViewChromium>>();
78
79    // Read/write protected by mLock.
80    private boolean mStarted;
81
82    public WebViewChromiumFactoryProvider() {
83        // Load chromium library.
84        AwBrowserProcess.loadLibrary();
85        // Load glue-layer support library.
86        System.loadLibrary("webviewchromium_plat_support");
87        ThreadUtils.setWillOverrideUiThread();
88    }
89
90    private void initPlatSupportLibrary() {
91        DrawGLFunctor.setChromiumAwDrawGLFunction(AwContents.getAwDrawGLFunction());
92        AwContents.setAwDrawSWFunctionTable(GraphicsUtils.getDrawSWFunctionTable());
93        AwContents.setAwDrawGLFunctionTable(GraphicsUtils.getDrawGLFunctionTable());
94    }
95
96    private void ensureChromiumStartedLocked(boolean onMainThread) {
97        assert Thread.holdsLock(mLock);
98
99        if (mStarted) {  // Early-out for the common case.
100            return;
101        }
102
103        Looper looper = !onMainThread ? Looper.myLooper() : Looper.getMainLooper();
104        Log.v(TAG, "Binding Chromium to " +
105                (Looper.getMainLooper().equals(looper) ? "main":"background") +
106                " looper " + looper);
107        ThreadUtils.setUiThread(looper);
108
109        if (ThreadUtils.runningOnUiThread()) {
110            startChromiumLocked();
111            return;
112        }
113
114        // We must post to the UI thread to cover the case that the user has invoked Chromium
115        // startup by using the (thread-safe) CookieManager rather than creating a WebView.
116        ThreadUtils.postOnUiThread(new Runnable() {
117            @Override
118            public void run() {
119                synchronized (mLock) {
120                    startChromiumLocked();
121                }
122            }
123        });
124        while (!mStarted) {
125            try {
126                // Important: wait() releases |mLock| so the UI thread can take it :-)
127                mLock.wait();
128            } catch (InterruptedException e) {
129                // Keep trying... eventually the UI thread will process the task we sent it.
130            }
131        }
132    }
133
134    private void startChromiumLocked() {
135        assert Thread.holdsLock(mLock) && ThreadUtils.runningOnUiThread();
136
137        // The post-condition of this method is everything is ready, so notify now to cover all
138        // return paths. (Other threads will not wake-up until we release |mLock|, whatever).
139        mLock.notifyAll();
140
141        if (mStarted) {
142            return;
143        }
144
145        if (Build.IS_DEBUGGABLE) {
146            CommandLine.initFromFile(COMMAND_LINE_FILE);
147        } else {
148            CommandLine.init(null);
149        }
150
151        CommandLine cl = CommandLine.getInstance();
152        // TODO: currently in a relase build the DCHECKs only log. We either need to insall
153        // a report handler with SetLogReportHandler to make them assert, or else compile
154        // them out of the build altogether (b/8284203). Either way, so long they're
155        // compiled in, we may as unconditionally enable them here.
156        cl.appendSwitch("enable-dcheck");
157
158        // TODO: Remove when GL is supported by default in the upstream code.
159        if (!cl.hasSwitch("disable-webview-gl-mode")) {
160            cl.appendSwitch("testing-webview-gl-mode");
161        }
162
163        Context context = ActivityThread.currentApplication();
164        if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) {
165            cl.appendSwitch("enable-webview-classic-workarounds");
166        }
167
168        // We don't need to extract any paks because for WebView, they are
169        // in the system image.
170        ResourceExtractor.setMandatoryPaksToExtract("");
171
172        try {
173            LibraryLoader.ensureInitialized();
174        } catch(ProcessInitException e) {
175            throw new RuntimeException("Error initializing WebView library", e);
176        }
177
178        PathService.override(PathService.DIR_MODULE, "/system/lib/");
179        // TODO: DIR_RESOURCE_PAKS_ANDROID needs to live somewhere sensible,
180        // inlined here for simplicity setting up the HTMLViewer demo. Unfortunately
181        // it can't go into base.PathService, as the native constant it refers to
182        // lives in the ui/ layer. See ui/base/ui_base_paths.h
183        final int DIR_RESOURCE_PAKS_ANDROID = 3003;
184        PathService.override(DIR_RESOURCE_PAKS_ANDROID,
185                "/system/framework/webview/paks");
186
187        AwBrowserProcess.start(ActivityThread.currentApplication());
188        initPlatSupportLibrary();
189
190        if (Build.IS_DEBUGGABLE) {
191            setWebContentsDebuggingEnabled(true);
192        }
193        mStarted = true;
194
195        for (WeakReference<WebViewChromium> wvc : mWebViewsToStart) {
196            WebViewChromium w = wvc.get();
197            if (w != null) {
198                w.startYourEngine();
199            }
200        }
201        mWebViewsToStart.clear();
202        mWebViewsToStart = null;
203    }
204
205    boolean hasStarted() {
206        return mStarted;
207    }
208
209    void startYourEngines(boolean onMainThread) {
210        synchronized (mLock) {
211            ensureChromiumStartedLocked(onMainThread);
212
213        }
214    }
215
216    AwBrowserContext getBrowserContext() {
217        synchronized (mLock) {
218            return getBrowserContextLocked();
219        }
220    }
221
222    private AwBrowserContext getBrowserContextLocked() {
223        assert Thread.holdsLock(mLock);
224        assert mStarted;
225        if (mBrowserContext == null) {
226            mBrowserContext = new AwBrowserContext(
227                    ActivityThread.currentApplication().getSharedPreferences(
228                            CHROMIUM_PREFS_NAME, Context.MODE_PRIVATE));
229        }
230        return mBrowserContext;
231    }
232
233    private void setWebContentsDebuggingEnabled(boolean enable) {
234        if (Looper.myLooper() != ThreadUtils.getUiThreadLooper()) {
235            throw new RuntimeException(
236                    "Toggling of Web Contents Debugging must be done on the UI thread");
237        }
238        if (mDevToolsServer == null) {
239            if (!enable) return;
240            mDevToolsServer = new AwDevToolsServer();
241        }
242        mDevToolsServer.setRemoteDebuggingEnabled(enable);
243    }
244
245
246    @Override
247    public Statics getStatics() {
248        synchronized (mLock) {
249            if (mStaticMethods == null) {
250                // TODO: Optimization potential: most these methods only need the native library
251                // loaded and initialized, not the entire browser process started.
252                // See also http://b/7009882
253                ensureChromiumStartedLocked(true);
254                mStaticMethods = new WebViewFactoryProvider.Statics() {
255                    @Override
256                    public String findAddress(String addr) {
257                        return ContentViewStatics.findAddress(addr);
258                    }
259
260                    @Override
261                    public void setPlatformNotificationsEnabled(boolean enable) {
262                        // noop
263                    }
264
265                    @Override
266                    public String getDefaultUserAgent(Context context) {
267                        return AwSettings.getDefaultUserAgent();
268                    }
269
270                    @Override
271                    public void setWebContentsDebuggingEnabled(boolean enable) {
272                        // Web Contents debugging is always enabled on debug builds.
273                        if (!Build.IS_DEBUGGABLE) {
274                            WebViewChromiumFactoryProvider.this.
275                                    setWebContentsDebuggingEnabled(enable);
276                        }
277                    }
278                };
279            }
280        }
281        return mStaticMethods;
282    }
283
284    @Override
285    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
286        WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);
287
288        synchronized (mLock) {
289            if (mWebViewsToStart != null) {
290                mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc));
291            }
292        }
293        ResourceProvider.registerResources(webView.getContext());
294        return wvc;
295    }
296
297    @Override
298    public GeolocationPermissions getGeolocationPermissions() {
299        synchronized (mLock) {
300            if (mGeolocationPermissions == null) {
301                ensureChromiumStartedLocked(true);
302                mGeolocationPermissions = new GeolocationPermissionsAdapter(
303                        getBrowserContextLocked().getGeolocationPermissions());
304            }
305        }
306        return mGeolocationPermissions;
307    }
308
309    @Override
310    public CookieManager getCookieManager() {
311        synchronized (mLock) {
312            if (mCookieManager == null) {
313                if (!mStarted) {
314                    // We can use CookieManager without starting Chromium; the native code
315                    // will bring up just the parts it needs to make this work on a temporary
316                    // basis until Chromium is started for real. The temporary cookie manager
317                    // needs the application context to have been set.
318                    ContentMain.initApplicationContext(ActivityThread.currentApplication());
319                }
320                mCookieManager = new CookieManagerAdapter(new AwCookieManager());
321            }
322        }
323        return mCookieManager;
324    }
325
326    @Override
327    public WebIconDatabase getWebIconDatabase() {
328        synchronized (mLock) {
329            if (mWebIconDatabase == null) {
330                ensureChromiumStartedLocked(true);
331                mWebIconDatabase = new WebIconDatabaseAdapter();
332            }
333        }
334        return mWebIconDatabase;
335    }
336
337    @Override
338    public WebStorage getWebStorage() {
339        synchronized (mLock) {
340            if (mWebStorage == null) {
341                ensureChromiumStartedLocked(true);
342                mWebStorage = new WebStorageAdapter(AwQuotaManagerBridge.getInstance());
343            }
344        }
345        return mWebStorage;
346    }
347
348    @Override
349    public WebViewDatabase getWebViewDatabase(Context context) {
350        synchronized (mLock) {
351            if (mWebViewDatabase == null) {
352                ensureChromiumStartedLocked(true);
353                AwBrowserContext browserContext = getBrowserContextLocked();
354                mWebViewDatabase = new WebViewDatabaseAdapter(
355                        browserContext.getFormDatabase(),
356                        browserContext.getHttpAuthDatabase(context));
357            }
358        }
359        return mWebViewDatabase;
360    }
361}
362