WebViewChromiumFactoryProvider.java revision 5112943866a77868d1987617ed1320e73e2a9ce4
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.content.pm.PackageManager; 20import android.content.res.Resources; 21import android.app.ActivityManager; 22import android.app.ActivityThread; 23import android.content.ComponentCallbacks2; 24import android.content.Context; 25import android.content.Intent; 26import android.content.SharedPreferences; 27import android.net.Uri; 28import android.os.Build; 29import android.os.Looper; 30import android.os.StrictMode; 31import android.os.SystemProperties; 32import android.os.Trace; 33import android.util.Log; 34import android.webkit.CookieManager; 35import android.webkit.GeolocationPermissions; 36import android.webkit.WebIconDatabase; 37import android.webkit.WebStorage; 38import android.webkit.WebView; 39import android.webkit.WebViewDatabase; 40import android.webkit.WebViewFactory; 41import android.webkit.WebViewFactoryProvider; 42import android.webkit.WebViewProvider; 43 44import org.chromium.android_webview.AwBrowserContext; 45import org.chromium.android_webview.AwBrowserProcess; 46import org.chromium.android_webview.AwContents; 47import org.chromium.android_webview.AwContentsStatics; 48import org.chromium.android_webview.AwCookieManager; 49import org.chromium.android_webview.AwDevToolsServer; 50import org.chromium.android_webview.AwFormDatabase; 51import org.chromium.android_webview.AwGeolocationPermissions; 52import org.chromium.android_webview.AwQuotaManagerBridge; 53import org.chromium.android_webview.AwResource; 54import org.chromium.android_webview.AwSettings; 55import org.chromium.base.CommandLine; 56import org.chromium.base.MemoryPressureListener; 57import org.chromium.base.PathService; 58import org.chromium.base.PathUtils; 59import org.chromium.base.ResourceExtractor; 60import org.chromium.base.ThreadUtils; 61import org.chromium.base.TraceEvent; 62import org.chromium.base.library_loader.LibraryLoader; 63import org.chromium.base.library_loader.ProcessInitException; 64import org.chromium.content.app.ContentMain; 65import org.chromium.content.browser.ContentViewStatics; 66 67import java.io.File; 68import java.lang.ref.WeakReference; 69import java.util.ArrayList; 70 71public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider { 72 73 private static final String TAG = "WebViewChromiumFactoryProvider"; 74 75 private static final String CHROMIUM_PREFS_NAME = "WebViewChromiumPrefs"; 76 private static final String VERSION_CODE_PREF = "lastVersionCodeUsed"; 77 private static final String COMMAND_LINE_FILE = "/data/local/tmp/webview-command-line"; 78 79 // Guards accees to the other members, and is notifyAll() signalled on the UI thread 80 // when the chromium process has been started. 81 private final Object mLock = new Object(); 82 83 // Initialization guarded by mLock. 84 private AwBrowserContext mBrowserContext; 85 private Statics mStaticMethods; 86 private GeolocationPermissionsAdapter mGeolocationPermissions; 87 private CookieManagerAdapter mCookieManager; 88 private WebIconDatabaseAdapter mWebIconDatabase; 89 private WebStorageAdapter mWebStorage; 90 private WebViewDatabaseAdapter mWebViewDatabase; 91 private AwDevToolsServer mDevToolsServer; 92 93 private ArrayList<WeakReference<WebViewChromium>> mWebViewsToStart = 94 new ArrayList<WeakReference<WebViewChromium>>(); 95 96 // Read/write protected by mLock. 97 private boolean mStarted; 98 99 private SharedPreferences mWebViewPrefs; 100 101 public WebViewChromiumFactoryProvider() { 102 if (Build.IS_DEBUGGABLE) { 103 // Suppress the StrictMode violation as this codepath is only hit on debugglable builds. 104 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); 105 CommandLine.initFromFile(COMMAND_LINE_FILE); 106 StrictMode.setThreadPolicy(oldPolicy); 107 } else { 108 CommandLine.init(null); 109 } 110 111 CommandLine cl = CommandLine.getInstance(); 112 // TODO: currently in a relase build the DCHECKs only log. We either need to insall 113 // a report handler with SetLogReportHandler to make them assert, or else compile 114 // them out of the build altogether (b/8284203). Either way, so long they're 115 // compiled in, we may as unconditionally enable them here. 116 cl.appendSwitch("enable-dcheck"); 117 118 ThreadUtils.setWillOverrideUiThread(); 119 // Load chromium library. 120 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "AwBrowserProcess.loadLibrary()"); 121 AwBrowserProcess.loadLibrary(); 122 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); 123 // Load glue-layer support library. 124 System.loadLibrary("webviewchromium_plat_support"); 125 126 // Use shared preference to check for package downgrade. 127 mWebViewPrefs = ActivityThread.currentApplication().getSharedPreferences( 128 CHROMIUM_PREFS_NAME, Context.MODE_PRIVATE); 129 int lastVersion = mWebViewPrefs.getInt(VERSION_CODE_PREF, 0); 130 int currentVersion = WebViewFactory.getLoadedPackageInfo().versionCode; 131 if (lastVersion > currentVersion) { 132 // The WebView package has been downgraded since we last ran in this application. 133 // Delete the WebView data directory's contents. 134 String dataDir = PathUtils.getDataDirectory(ActivityThread.currentApplication()); 135 Log.i(TAG, "WebView package downgraded from " + lastVersion + " to " + currentVersion + 136 "; deleting contents of " + dataDir); 137 deleteContents(new File(dataDir)); 138 } 139 if (lastVersion != currentVersion) { 140 mWebViewPrefs.edit().putInt(VERSION_CODE_PREF, currentVersion).apply(); 141 } 142 // Now safe to use WebView data directory. 143 } 144 145 private static void deleteContents(File dir) { 146 File[] files = dir.listFiles(); 147 if (files != null) { 148 for (File file : files) { 149 if (file.isDirectory()) { 150 deleteContents(file); 151 } 152 if (!file.delete()) { 153 Log.w(TAG, "Failed to delete " + file); 154 } 155 } 156 } 157 } 158 159 private void initPlatSupportLibrary() { 160 DrawGLFunctor.setChromiumAwDrawGLFunction(AwContents.getAwDrawGLFunction()); 161 AwContents.setAwDrawSWFunctionTable(GraphicsUtils.getDrawSWFunctionTable()); 162 AwContents.setAwDrawGLFunctionTable(GraphicsUtils.getDrawGLFunctionTable()); 163 } 164 165 private static void initTraceEvent() { 166 syncATraceState(); 167 SystemProperties.addChangeCallback(new Runnable() { 168 @Override 169 public void run() { 170 syncATraceState(); 171 } 172 }); 173 } 174 175 private static void syncATraceState() { 176 long enabledFlags = SystemProperties.getLong("debug.atrace.tags.enableflags", 0); 177 TraceEvent.setATraceEnabled((enabledFlags & Trace.TRACE_TAG_WEBVIEW) != 0); 178 } 179 180 private void ensureChromiumStartedLocked(boolean onMainThread) { 181 assert Thread.holdsLock(mLock); 182 183 if (mStarted) { // Early-out for the common case. 184 return; 185 } 186 187 Looper looper = !onMainThread ? Looper.myLooper() : Looper.getMainLooper(); 188 Log.v(TAG, "Binding Chromium to " + 189 (Looper.getMainLooper().equals(looper) ? "main":"background") + 190 " looper " + looper); 191 ThreadUtils.setUiThread(looper); 192 193 if (ThreadUtils.runningOnUiThread()) { 194 startChromiumLocked(); 195 return; 196 } 197 198 // We must post to the UI thread to cover the case that the user has invoked Chromium 199 // startup by using the (thread-safe) CookieManager rather than creating a WebView. 200 ThreadUtils.postOnUiThread(new Runnable() { 201 @Override 202 public void run() { 203 synchronized (mLock) { 204 startChromiumLocked(); 205 } 206 } 207 }); 208 while (!mStarted) { 209 try { 210 // Important: wait() releases |mLock| the UI thread can take it :-) 211 mLock.wait(); 212 } catch (InterruptedException e) { 213 // Keep trying... eventually the UI thread will process the task we sent it. 214 } 215 } 216 } 217 218 private void startChromiumLocked() { 219 assert Thread.holdsLock(mLock) && ThreadUtils.runningOnUiThread(); 220 221 // The post-condition of this method is everything is ready, so notify now to cover all 222 // return paths. (Other threads will not wake-up until we release |mLock|, whatever). 223 mLock.notifyAll(); 224 225 if (mStarted) { 226 return; 227 } 228 229 // We don't need to extract any paks because for WebView, they are 230 // in the system image. 231 ResourceExtractor.setMandatoryPaksToExtract(""); 232 233 try { 234 LibraryLoader.ensureInitialized(); 235 } catch(ProcessInitException e) { 236 throw new RuntimeException("Error initializing WebView library", e); 237 } 238 239 PathService.override(PathService.DIR_MODULE, "/system/lib/"); 240 // TODO: DIR_RESOURCE_PAKS_ANDROID needs to live somewhere sensible, 241 // inlined here for simplicity setting up the HTMLViewer demo. Unfortunately 242 // it can't go into base.PathService, as the native constant it refers to 243 // lives in the ui/ layer. See ui/base/ui_base_paths.h 244 final int DIR_RESOURCE_PAKS_ANDROID = 3003; 245 PathService.override(DIR_RESOURCE_PAKS_ANDROID, 246 "/system/framework/webview/paks"); 247 248 // Make sure that ResourceProvider is initialized before starting the browser process. 249 setUpResources(ActivityThread.currentApplication()); 250 initPlatSupportLibrary(); 251 AwBrowserProcess.start(ActivityThread.currentApplication()); 252 253 if (Build.IS_DEBUGGABLE) { 254 setWebContentsDebuggingEnabled(true); 255 } 256 257 initTraceEvent(); 258 mStarted = true; 259 260 for (WeakReference<WebViewChromium> wvc : mWebViewsToStart) { 261 WebViewChromium w = wvc.get(); 262 if (w != null) { 263 w.startYourEngine(); 264 } 265 } 266 mWebViewsToStart.clear(); 267 mWebViewsToStart = null; 268 } 269 270 boolean hasStarted() { 271 return mStarted; 272 } 273 274 void startYourEngines(boolean onMainThread) { 275 synchronized (mLock) { 276 ensureChromiumStartedLocked(onMainThread); 277 278 } 279 } 280 281 AwBrowserContext getBrowserContext() { 282 synchronized (mLock) { 283 return getBrowserContextLocked(); 284 } 285 } 286 287 private AwBrowserContext getBrowserContextLocked() { 288 assert Thread.holdsLock(mLock); 289 assert mStarted; 290 if (mBrowserContext == null) { 291 mBrowserContext = new AwBrowserContext(mWebViewPrefs); 292 } 293 return mBrowserContext; 294 } 295 296 private void setWebContentsDebuggingEnabled(boolean enable) { 297 if (Looper.myLooper() != ThreadUtils.getUiThreadLooper()) { 298 throw new RuntimeException( 299 "Toggling of Web Contents Debugging must be done on the UI thread"); 300 } 301 if (mDevToolsServer == null) { 302 if (!enable) return; 303 mDevToolsServer = new AwDevToolsServer(); 304 } 305 mDevToolsServer.setRemoteDebuggingEnabled(enable); 306 } 307 308 private void setUpResources(Context ctx) { 309 ResourceRewriter.rewriteRValues(ctx); 310 311 AwResource.setResources(ctx.getResources()); 312 AwResource.setErrorPageResources(android.R.raw.loaderror, 313 android.R.raw.nodomain); 314 AwResource.setConfigKeySystemUuidMapping( 315 android.R.array.config_keySystemUuidMapping); 316 } 317 318 @Override 319 public Statics getStatics() { 320 synchronized (mLock) { 321 if (mStaticMethods == null) { 322 // TODO: Optimization potential: most these methods only need the native library 323 // loaded and initialized, not the entire browser process started. 324 // See also http://b/7009882 325 ensureChromiumStartedLocked(true); 326 mStaticMethods = new WebViewFactoryProvider.Statics() { 327 @Override 328 public String findAddress(String addr) { 329 return ContentViewStatics.findAddress(addr); 330 } 331 332 @Override 333 public String getDefaultUserAgent(Context context) { 334 return AwSettings.getDefaultUserAgent(); 335 } 336 337 @Override 338 public void setWebContentsDebuggingEnabled(boolean enable) { 339 // Web Contents debugging is always enabled on debug builds. 340 if (!Build.IS_DEBUGGABLE) { 341 WebViewChromiumFactoryProvider.this. 342 setWebContentsDebuggingEnabled(enable); 343 } 344 } 345 346 // TODO enable after L release to AOSP 347 //@Override 348 public void clearClientCertPreferences(Runnable onCleared) { 349 AwContentsStatics.clearClientCertPreferences(onCleared); 350 } 351 352 @Override 353 public void freeMemoryForTests() { 354 if (ActivityManager.isRunningInTestHarness()) { 355 MemoryPressureListener.maybeNotifyMemoryPresure( 356 ComponentCallbacks2.TRIM_MEMORY_COMPLETE); 357 } 358 } 359 360 // TODO: Add @Override. 361 public void enableSlowWholeDocumentDraw() { 362 WebViewChromium.enableSlowWholeDocumentDraw(); 363 } 364 365 @Override 366 public Uri[] parseFileChooserResult(int resultCode, Intent intent) { 367 return FileChooserParamsAdapter.parseFileChooserResult(resultCode, intent); 368 } 369 }; 370 } 371 } 372 return mStaticMethods; 373 } 374 375 @Override 376 public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) { 377 WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess); 378 379 synchronized (mLock) { 380 if (mWebViewsToStart != null) { 381 mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc)); 382 } 383 } 384 385 return wvc; 386 } 387 388 @Override 389 public GeolocationPermissions getGeolocationPermissions() { 390 synchronized (mLock) { 391 if (mGeolocationPermissions == null) { 392 ensureChromiumStartedLocked(true); 393 mGeolocationPermissions = new GeolocationPermissionsAdapter( 394 getBrowserContextLocked().getGeolocationPermissions()); 395 } 396 } 397 return mGeolocationPermissions; 398 } 399 400 @Override 401 public CookieManager getCookieManager() { 402 synchronized (mLock) { 403 if (mCookieManager == null) { 404 if (!mStarted) { 405 // We can use CookieManager without starting Chromium; the native code 406 // will bring up just the parts it needs to make this work on a temporary 407 // basis until Chromium is started for real. The temporary cookie manager 408 // needs the application context to have been set. 409 ContentMain.initApplicationContext(ActivityThread.currentApplication()); 410 } 411 mCookieManager = new CookieManagerAdapter(new AwCookieManager()); 412 } 413 } 414 return mCookieManager; 415 } 416 417 @Override 418 public WebIconDatabase getWebIconDatabase() { 419 synchronized (mLock) { 420 if (mWebIconDatabase == null) { 421 ensureChromiumStartedLocked(true); 422 mWebIconDatabase = new WebIconDatabaseAdapter(); 423 } 424 } 425 return mWebIconDatabase; 426 } 427 428 @Override 429 public WebStorage getWebStorage() { 430 synchronized (mLock) { 431 if (mWebStorage == null) { 432 ensureChromiumStartedLocked(true); 433 mWebStorage = new WebStorageAdapter(AwQuotaManagerBridge.getInstance()); 434 } 435 } 436 return mWebStorage; 437 } 438 439 @Override 440 public WebViewDatabase getWebViewDatabase(Context context) { 441 synchronized (mLock) { 442 if (mWebViewDatabase == null) { 443 ensureChromiumStartedLocked(true); 444 AwBrowserContext browserContext = getBrowserContextLocked(); 445 mWebViewDatabase = new WebViewDatabaseAdapter( 446 browserContext.getFormDatabase(), 447 browserContext.getHttpAuthDatabase(context)); 448 } 449 } 450 return mWebViewDatabase; 451 } 452} 453