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