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