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