1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Copyright (c) 2013-2014 Intel Corporation. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
6 package org.xwalk.core;
8 import android.app.Activity;
9 import android.content.Context;
10 import android.content.Intent;
11 import android.content.SharedPreferences;
12 import android.graphics.Rect;
13 import android.os.Bundle;
14 import android.text.TextUtils;
15 import android.util.AttributeSet;
16 import android.util.Log;
17 import android.view.ViewGroup;
18 import android.webkit.ValueCallback;
19 import android.webkit.WebResourceResponse;
20 import android.widget.FrameLayout;
22 import java.io.IOException;
23 import java.io.InputStream;
25 import org.chromium.base.CalledByNative;
26 import org.chromium.base.JNINamespace;
27 import org.chromium.base.ThreadUtils;
28 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
29 import org.chromium.content.browser.ContentView;
30 import org.chromium.content.browser.ContentViewCore;
31 import org.chromium.content.browser.ContentViewRenderView;
32 import org.chromium.content.browser.ContentViewStatics;
33 import org.chromium.content.browser.LoadUrlParams;
34 import org.chromium.content.browser.NavigationHistory;
35 import org.chromium.media.MediaPlayerBridge;
36 import org.chromium.ui.base.ActivityWindowAndroid;
38 @JNINamespace("xwalk")
40 * This class is the implementation class for XWalkView by calling internal
43 class XWalkContent extends FrameLayout implements XWalkPreferences.KeyValueChangeListener {
44 private static String TAG = "XWalkContent";
45 private ContentViewCore mContentViewCore;
46 private ContentView mContentView;
47 private ContentViewRenderView mContentViewRenderView;
48 private ActivityWindowAndroid mWindow;
49 private XWalkDevToolsServer mDevToolsServer;
50 private XWalkView mXWalkView;
51 private XWalkContentsClientBridge mContentsClientBridge;
52 private XWalkContentsIoThreadClient mIoThreadClient;
53 private XWalkWebContentsDelegateAdapter mXWalkContentsDelegateAdapter;
54 private XWalkSettings mSettings;
55 private XWalkGeolocationPermissions mGeolocationPermissions;
56 private XWalkLaunchScreenManager mLaunchScreenManager;
60 boolean mReadyToLoad = false;
61 String mPendingUrl = null;
62 String mPendingData = null;
64 public XWalkContent(Context context, AttributeSet attrs, XWalkView xwView) {
65 super(context, attrs);
67 // Initialize the WebContensDelegate.
69 mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
70 mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
71 mContentsClientBridge);
72 mIoThreadClient = new XWalkIoThreadClientImpl();
74 // Initialize mWindow which is needed by content
75 mWindow = new ActivityWindowAndroid(xwView.getActivity());
77 // Initialize ContentViewRenderView
78 mContentViewRenderView = new ContentViewRenderView(context, mWindow) {
79 protected void onReadyToRender() {
80 if (mPendingUrl != null) {
81 doLoadUrl(mPendingUrl, mPendingData);
89 mLaunchScreenManager = new XWalkLaunchScreenManager(context, mXWalkView);
90 mContentViewRenderView.registerFirstRenderedFrameListener(mLaunchScreenManager);
91 addView(mContentViewRenderView,
92 new FrameLayout.LayoutParams(
93 FrameLayout.LayoutParams.MATCH_PARENT,
94 FrameLayout.LayoutParams.MATCH_PARENT));
96 mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
97 mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
98 mContentsClientBridge.getInterceptNavigationDelegate());
100 // Initialize ContentView.
101 mContentView = ContentView.newInstance(getContext(), mWebContents, mWindow);
102 addView(mContentView,
103 new FrameLayout.LayoutParams(
104 FrameLayout.LayoutParams.MATCH_PARENT,
105 FrameLayout.LayoutParams.MATCH_PARENT));
106 mContentView.setContentViewClient(mContentsClientBridge);
108 mContentViewRenderView.setCurrentContentView(mContentView);
110 // For addJavascriptInterface
111 mContentViewCore = mContentView.getContentViewCore();
112 mContentsClientBridge.installWebContentsObserver(mContentViewCore);
114 mContentView.getContentViewCore().setDownloadDelegate(mContentsClientBridge);
116 // Set the third argument isAccessFromFileURLsGrantedByDefault to false, so that
117 // the members mAllowUniversalAccessFromFileURLs and mAllowFileAccessFromFileURLs
118 // won't be changed from false to true at the same time in the constructor of
119 // XWalkSettings class.
120 mSettings = new XWalkSettings(getContext(), mWebContents, false);
121 // Enable AllowFileAccessFromFileURLs, so that files under file:// path could be
122 // loaded by XMLHttpRequest.
123 mSettings.setAllowFileAccessFromFileURLs(true);
125 SharedPreferences sharedPreferences = new InMemorySharedPreferences();
126 mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
128 MediaPlayerBridge.setResourceLoadingFilter(
129 new XWalkMediaPlayerResourceLoadingFilter());
131 XWalkPreferences.load(this);
134 void doLoadUrl(String url, String content) {
135 // Handle the same url loading by parameters.
136 if (url != null && !url.isEmpty() &&
137 TextUtils.equals(url, mContentView.getUrl())) {
138 mContentView.getContentViewCore().reload(true);
140 LoadUrlParams params = null;
141 if (content == null || content.isEmpty()) {
142 params = new LoadUrlParams(url);
144 params = LoadUrlParams.createLoadDataParamsWithBaseUrl(
145 content, "text/html", false, url, null);
147 params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
148 mContentView.loadUrl(params);
151 mContentView.requestFocus();
154 public void loadUrl(String url, String data) {
155 if ((url == null || url.isEmpty()) &&
156 (data == null || data.isEmpty())) {
161 doLoadUrl(url, data);
168 public void reload(int mode) {
171 case XWalkView.RELOAD_IGNORE_CACHE:
172 mContentView.getContentViewCore().reloadIgnoringCache(true);
174 case XWalkView.RELOAD_NORMAL:
176 mContentView.getContentViewCore().reload(true);
182 public String getUrl() {
183 String url = mContentView.getUrl();
184 if (url == null || url.trim().isEmpty()) return null;
188 public String getTitle() {
189 String title = mContentView.getTitle().trim();
190 if (title == null) title = "";
194 public void addJavascriptInterface(Object object, String name) {
195 mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name,
196 JavascriptInterface.class);
199 public void evaluateJavascript(String script, ValueCallback<String> callback) {
200 final ValueCallback<String> fCallback = callback;
201 ContentViewCore.JavaScriptCallback coreCallback = new ContentViewCore.JavaScriptCallback() {
203 public void handleJavaScriptResult(String jsonResult) {
204 fCallback.onReceiveValue(jsonResult);
207 mContentViewCore.evaluateJavaScript(script, coreCallback);
210 public void setUIClient(XWalkUIClient client) {
211 mContentsClientBridge.setUIClient(client);
214 public void setResourceClient(XWalkResourceClient client) {
215 mContentsClientBridge.setResourceClient(client);
218 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
219 mContentsClientBridge.setXWalkWebChromeClient(client);
222 public XWalkWebChromeClient getXWalkWebChromeClient() {
223 return mContentsClientBridge.getXWalkWebChromeClient();
226 public void setXWalkClient(XWalkClient client) {
227 mContentsClientBridge.setXWalkClient(client);
230 public void setDownloadListener(DownloadListener listener) {
231 mContentsClientBridge.setDownloadListener(listener);
234 public void setNavigationHandler(XWalkNavigationHandler handler) {
235 mContentsClientBridge.setNavigationHandler(handler);
238 public void setNotificationService(XWalkNotificationService service) {
239 mContentsClientBridge.setNotificationService(service);
242 public void onPause() {
243 mContentView.onHide();
246 public void onResume() {
247 mContentView.onShow();
250 public void onActivityResult(int requestCode, int resultCode, Intent data) {
251 mWindow.onActivityResult(requestCode, resultCode, data);
254 public boolean onNewIntent(Intent intent) {
255 return mContentsClientBridge.onNewIntent(intent);
258 public void clearCache(boolean includeDiskFiles) {
259 if (mXWalkContent == 0) return;
260 nativeClearCache(mXWalkContent, includeDiskFiles);
263 public void clearHistory() {
264 mContentView.getContentViewCore().clearHistory();
267 public boolean canGoBack() {
268 return mContentView.canGoBack();
271 public void goBack() {
272 mContentView.goBack();
275 public boolean canGoForward() {
276 return mContentView.canGoForward();
279 public void goForward() {
280 mContentView.goForward();
283 void navigateTo(int offset) {
284 mContentView.getContentViewCore().goToOffset(offset);
287 public void stopLoading() {
288 mContentView.getContentViewCore().stopLoading();
291 // TODO(Guangzhen): ContentViewStatics will be removed in upstream,
292 // details in content_view_statics.cc.
293 // We need follow up after upstream updates that.
294 public void pauseTimers() {
295 ContentViewStatics.setWebKitSharedTimersSuspended(true);
298 public void resumeTimers() {
299 ContentViewStatics.setWebKitSharedTimersSuspended(false);
302 public String getOriginalUrl() {
303 NavigationHistory history = mContentViewCore.getNavigationHistory();
304 int currentIndex = history.getCurrentEntryIndex();
305 if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
306 return history.getEntryAtIndex(currentIndex).getOriginalUrl();
311 public String getXWalkVersion() {
312 if (mXWalkContent == 0) return "";
313 return nativeGetVersion(mXWalkContent);
316 public void setNetworkAvailable(boolean networkUp) {
317 if (mXWalkContent == 0) return;
318 nativeSetJsOnlineProperty(mXWalkContent, networkUp);
321 // For instrumentation test.
322 public ContentViewCore getContentViewCoreForTest() {
323 return mContentViewCore;
326 // For instrumentation test.
327 public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
328 contentClient.installWebContentsObserver(mContentViewCore);
331 public String devToolsAgentId() {
332 if (mXWalkContent == 0) return "";
333 return nativeDevToolsAgentId(mXWalkContent);
336 public XWalkSettings getSettings() {
340 public void loadAppFromManifest(String url, String data) {
341 if (mXWalkContent == 0 ||
342 ((url == null || url.isEmpty()) &&
343 (data == null || data.isEmpty()))) {
347 String content = data;
348 // If the data of manifest.json is not set, try to load it.
349 if (data == null || data.isEmpty()) {
351 content = AndroidProtocolHandler.getUrlContent(mXWalkView.getActivity(), url);
352 } catch (IOException e) {
353 throw new RuntimeException("Failed to read the manifest: " + url);
357 // Calculate the base url of manifestUrl. Used by native side.
358 // TODO(yongsheng): It's from runtime side. Need to find a better way
360 String baseUrl = url;
361 int position = url.lastIndexOf("/");
362 if (position != -1) {
363 baseUrl = url.substring(0, position + 1);
365 Log.w(TAG, "The url of manifest.json is probably not set correctly.");
368 if (!nativeSetManifest(mXWalkContent, baseUrl, content)) {
369 throw new RuntimeException("Failed to parse the manifest file: " + url);
373 public XWalkNavigationHistory getNavigationHistory() {
374 return new XWalkNavigationHistory(mXWalkView, mContentViewCore.getNavigationHistory());
377 public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
379 public XWalkNavigationHistory saveState(Bundle outState) {
380 if (outState == null) return null;
382 byte[] state = nativeGetState(mXWalkContent);
383 if (state == null) return null;
385 outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
386 return getNavigationHistory();
389 public XWalkNavigationHistory restoreState(Bundle inState) {
390 if (inState == null) return null;
392 byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
393 if (state == null) return null;
395 boolean result = nativeSetState(mXWalkContent, state);
397 // The onUpdateTitle callback normally happens when a page is loaded,
398 // but is optimized out in the restoreState case because the title is
399 // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
400 // call the callback explicitly here.
402 mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
405 return result ? getNavigationHistory() : null;
408 boolean hasEnteredFullscreen() {
409 return mContentsClientBridge.hasEnteredFullscreen();
412 void exitFullscreen() {
413 if (hasEnteredFullscreen()) {
414 mContentsClientBridge.exitFullscreen(mWebContents);
419 public void onGetUrlFromManifest(String url) {
420 if (url != null && !url.isEmpty()) {
426 public void onGetUrlAndLaunchScreenFromManifest(String url, String readyWhen, String imageBorder) {
427 if (url == null || url.isEmpty()) return;
428 mLaunchScreenManager.displayLaunchScreen(readyWhen, imageBorder);
429 mContentsClientBridge.registerPageLoadListener(mLaunchScreenManager);
434 public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
435 if (enterFullscreen) mContentsClientBridge.onToggleFullscreen(true);
438 public void destroy() {
439 if (mXWalkContent == 0) return;
441 XWalkPreferences.unload(this);
442 // Reset existing notification service in order to destruct it.
443 setNotificationService(null);
444 // Remove its children used for page rendering from view hierarchy.
445 removeView(mContentView);
446 removeView(mContentViewRenderView);
447 mContentViewRenderView.setCurrentContentView(null);
449 // Destroy the native resources.
450 mContentViewRenderView.destroy();
451 mContentView.destroy();
453 nativeDestroy(mXWalkContent);
457 public int getRoutingID() {
458 return nativeGetRoutingID(mXWalkContent);
461 //--------------------------------------------------------------------------------------------
462 private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
463 // All methods are called on the IO thread.
466 public int getCacheMode() {
467 return mSettings.getCacheMode();
471 public InterceptedRequestData shouldInterceptRequest(final String url,
472 boolean isMainFrame) {
474 // Notify a resource load is started. This is not the best place to start the callback
475 // but it's a workable way.
476 mContentsClientBridge.getCallbackHelper().postOnResourceLoadStarted(url);
478 WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
479 InterceptedRequestData interceptedRequestData = null;
481 if (webResourceResponse == null) {
482 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
484 if (isMainFrame && webResourceResponse.getData() == null) {
485 mContentsClientBridge.getCallbackHelper().postOnReceivedError(
486 XWalkResourceClient.ERROR_UNKNOWN, null, url);
488 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
489 webResourceResponse.getEncoding(),
490 webResourceResponse.getData());
492 return interceptedRequestData;
496 public boolean shouldBlockContentUrls() {
497 return !mSettings.getAllowContentAccess();
501 public boolean shouldBlockFileUrls() {
502 return !mSettings.getAllowFileAccess();
506 public boolean shouldBlockNetworkLoads() {
507 return mSettings.getBlockNetworkLoads();
511 public void onDownloadStart(String url,
513 String contentDisposition,
515 long contentLength) {
516 mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
517 contentDisposition, mimeType, contentLength);
521 public void newLoginRequest(String realm, String account, String args) {
522 mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
526 private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
528 public void invoke(final String origin, final boolean allow, final boolean retain) {
529 ThreadUtils.runOnUiThread(new Runnable() {
534 mGeolocationPermissions.allow(origin);
536 mGeolocationPermissions.deny(origin);
539 nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
546 private void onGeolocationPermissionsShowPrompt(String origin) {
547 // Reject if geolocation is disabled, or the origin has a retained deny.
548 if (!mSettings.getGeolocationEnabled()) {
549 nativeInvokeGeolocationCallback(mXWalkContent, false, origin);
552 // Allow if the origin has a retained allow.
553 if (mGeolocationPermissions.hasOrigin(origin)) {
554 nativeInvokeGeolocationCallback(mXWalkContent,
555 mGeolocationPermissions.isOriginAllowed(origin),
559 mContentsClientBridge.onGeolocationPermissionsShowPrompt(
560 origin, new XWalkGeolocationCallback());
564 public void onGeolocationPermissionsHidePrompt() {
565 mContentsClientBridge.onGeolocationPermissionsHidePrompt();
568 public String enableRemoteDebugging(int allowedUid) {
569 // Chrome looks for "devtools_remote" pattern in the name of a unix domain socket
570 // to identify a debugging page
571 final String socketName = getContext().getApplicationContext().getPackageName() + "_devtools_remote";
572 if (mDevToolsServer == null) {
573 mDevToolsServer = new XWalkDevToolsServer(socketName);
574 mDevToolsServer.allowConnectionFromUid(allowedUid);
575 mDevToolsServer.setRemoteDebuggingEnabled(true);
577 // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
578 return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
581 // Enables remote debugging and returns the URL at which the dev tools server is listening
582 // for commands. Only the current process is allowed to connect to the server.
583 String enableRemoteDebugging() {
584 return enableRemoteDebugging(getContext().getApplicationInfo().uid);
587 void disableRemoteDebugging() {
588 if (mDevToolsServer == null) return;
590 if (mDevToolsServer.isRemoteDebuggingEnabled()) {
591 mDevToolsServer.setRemoteDebuggingEnabled(false);
593 mDevToolsServer.destroy();
594 mDevToolsServer = null;
598 public void onKeyValueChanged(String key, boolean value) {
599 if (key == XWalkPreferences.REMOTE_DEBUGGING) {
600 if (value) enableRemoteDebugging();
601 else disableRemoteDebugging();
605 public void setOverlayVideoMode(boolean enabled) {
606 if (mContentViewRenderView != null) {
607 mContentViewRenderView.setOverlayVideoMode(enabled);
611 private native long nativeInit(XWalkWebContentsDelegate webViewContentsDelegate,
612 XWalkContentsClientBridge bridge);
613 private static native void nativeDestroy(long nativeXWalkContent);
614 private native long nativeGetWebContents(long nativeXWalkContent,
615 XWalkContentsIoThreadClient ioThreadClient,
616 InterceptNavigationDelegate delegate);
617 private native void nativeClearCache(long nativeXWalkContent, boolean includeDiskFiles);
618 private native String nativeDevToolsAgentId(long nativeXWalkContent);
619 private native String nativeGetVersion(long nativeXWalkContent);
620 private native void nativeSetJsOnlineProperty(long nativeXWalkContent, boolean networkUp);
621 private native boolean nativeSetManifest(long nativeXWalkContent, String path, String manifest);
622 private native int nativeGetRoutingID(long nativeXWalkContent);
623 private native void nativeInvokeGeolocationCallback(
624 long nativeXWalkContent, boolean value, String requestingFrame);
625 private native byte[] nativeGetState(long nativeXWalkContent);
626 private native boolean nativeSetState(long nativeXWalkContent, byte[] state);