1 // Copyright (c) 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.internal;
8 import android.content.ActivityNotFoundException;
9 import android.content.ContentResolver;
10 import android.content.Intent;
11 import android.database.Cursor;
12 import android.graphics.Bitmap;
13 import android.graphics.Picture;
14 import android.net.Uri;
15 import android.net.http.SslCertificate;
16 import android.net.http.SslError;
17 import android.os.Message;
18 import android.os.Handler;
19 import android.provider.MediaStore;
20 import android.util.Log;
21 import android.view.KeyEvent;
22 import android.view.View;
23 import android.webkit.ConsoleMessage;
24 import android.webkit.ValueCallback;
25 import android.webkit.WebResourceResponse;
27 import org.chromium.base.CalledByNative;
28 import org.chromium.base.JNINamespace;
29 import org.chromium.base.ThreadUtils;
30 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
31 import org.chromium.components.navigation_interception.NavigationParams;
32 import org.chromium.content.browser.ContentVideoViewClient;
33 import org.chromium.content.browser.ContentViewDownloadDelegate;
34 import org.chromium.content.browser.DownloadInfo;
35 import org.xwalk.core.internal.XWalkUIClientInternal.LoadStatusInternal;
37 // Help bridge callback in XWalkContentsClient to XWalkViewClient and
38 // XWalkWebChromeClient; Also handle the JNI conmmunication logic.
39 @JNINamespace("xwalk")
40 class XWalkContentsClientBridge extends XWalkContentsClient
41 implements ContentViewDownloadDelegate {
42 private static final String TAG = XWalkContentsClientBridge.class.getName();
43 private static final int NEW_XWALKVIEW_CREATED = 100;
44 private static final int NEW_ICON_DOWNLOAD = 101;
46 private XWalkViewInternal mXWalkView;
47 private XWalkUIClientInternal mXWalkUIClient;
48 private XWalkResourceClientInternal mXWalkResourceClient;
49 private XWalkClient mXWalkClient;
50 private XWalkWebChromeClient mXWalkWebChromeClient;
51 private Bitmap mFavicon;
52 private DownloadListener mDownloadListener;
53 private InterceptNavigationDelegate mInterceptNavigationDelegate;
54 private PageLoadListener mPageLoadListener;
55 private XWalkNavigationHandler mNavigationHandler;
56 private XWalkNotificationService mNotificationService;
57 private Handler mUiThreadHandler;
59 /** State recording variables */
60 // For fullscreen state.
61 private boolean mIsFullscreen = false;
63 private LoadStatusInternal mLoadStatus = LoadStatusInternal.FINISHED;
64 private String mLoadingUrl = null;
66 // The native peer of the object
67 private long mNativeContentsClientBridge;
69 private float mPageScaleFactor;
71 private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
72 private XWalkContentsClient mContentsClient;
74 public InterceptNavigationDelegateImpl(XWalkContentsClient client) {
75 mContentsClient = client;
78 public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
79 final String url = navigationParams.url;
80 boolean ignoreNavigation = shouldOverrideUrlLoading(url) ||
81 (mNavigationHandler != null &&
82 mNavigationHandler.handleNavigation(navigationParams));
84 if (!ignoreNavigation) {
85 // Post a message to UI thread to notify the page is starting to load.
86 mContentsClient.getCallbackHelper().postOnPageStarted(url);
89 return ignoreNavigation;
93 public XWalkContentsClientBridge(XWalkViewInternal xwView) {
96 mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(this);
98 mUiThreadHandler = new Handler() {
100 public void handleMessage(Message msg) {
102 case NEW_XWALKVIEW_CREATED:
103 XWalkViewInternal newXWalkView = (XWalkViewInternal) msg.obj;
104 if (newXWalkView == mXWalkView) {
105 throw new IllegalArgumentException("Parent XWalkView cannot host it's own popup window");
108 if (newXWalkView != null && newXWalkView.getNavigationHistory().size() != 0) {
109 throw new IllegalArgumentException("New WebView for popup window must not have been previously navigated.");
112 mXWalkView.completeWindowCreation(newXWalkView);
114 case NEW_ICON_DOWNLOAD:
115 String url = (String) msg.obj;
116 nativeDownloadIcon(mNativeContentsClientBridge, url);
119 throw new IllegalStateException();
126 public void setUIClient(XWalkUIClientInternal client) {
127 // If it's null, use Crosswalk implementation.
128 if (client != null) {
129 mXWalkUIClient = client;
132 mXWalkUIClient = new XWalkUIClientInternal(mXWalkView);
135 public void setResourceClient(XWalkResourceClientInternal client) {
136 // If it's null, use Crosswalk implementation.
137 if (client != null) {
138 mXWalkResourceClient = client;
141 mXWalkResourceClient = new XWalkResourceClientInternal(mXWalkView);
145 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
146 // If it's null, use Crosswalk implementation.
147 if (client == null) return;
148 client.setContentsClient(this);
149 mXWalkWebChromeClient = client;
152 public XWalkWebChromeClient getXWalkWebChromeClient() {
153 return mXWalkWebChromeClient;
156 public void setXWalkClient(XWalkClient client) {
157 mXWalkClient = client;
160 public void setNavigationHandler(XWalkNavigationHandler handler) {
161 mNavigationHandler = handler;
164 void registerPageLoadListener(PageLoadListener listener) {
165 mPageLoadListener = listener;
168 public void setNotificationService(XWalkNotificationService service) {
169 if (mNotificationService != null) mNotificationService.shutdown();
170 mNotificationService = service;
171 if (mNotificationService != null) mNotificationService.setBridge(this);
174 public boolean onNewIntent(Intent intent) {
175 return mNotificationService.maybeHandleIntent(intent);
178 public InterceptNavigationDelegate getInterceptNavigationDelegate() {
179 return mInterceptNavigationDelegate;
182 private boolean isOwnerActivityRunning() {
183 if (mXWalkView != null && mXWalkView.isOwnerActivityRunning()) {
189 // TODO(Xingnan): All the empty functions need to be implemented.
191 public boolean shouldOverrideUrlLoading(String url) {
192 if (mXWalkResourceClient != null && mXWalkView != null) {
193 return mXWalkResourceClient.shouldOverrideUrlLoading(mXWalkView, url);
199 public boolean shouldOverrideKeyEvent(KeyEvent event) {
200 boolean overridden = false;
201 if (mXWalkUIClient != null && mXWalkView != null) {
202 overridden = mXWalkUIClient.shouldOverrideKeyEvent(mXWalkView, event);
205 return super.shouldOverrideKeyEvent(event);
211 public void onUnhandledKeyEvent(KeyEvent event) {
212 if (mXWalkUIClient != null && mXWalkView != null) {
213 mXWalkUIClient.onUnhandledKeyEvent(mXWalkView, event);
218 public void getVisitedHistory(ValueCallback<String[]> callback) {
222 public void doUpdateVisitedHistory(String url, boolean isReload) {
226 public void onProgressChanged(int progress) {
227 if (isOwnerActivityRunning()) {
228 mXWalkResourceClient.onProgressChanged(mXWalkView, progress);
233 public WebResourceResponse shouldInterceptRequest(String url) {
234 if (isOwnerActivityRunning()) {
235 return mXWalkResourceClient.shouldInterceptLoadRequest(mXWalkView, url);
241 public void onResourceLoadStarted(String url) {
242 if (isOwnerActivityRunning()) {
243 mXWalkResourceClient.onLoadStarted(mXWalkView, url);
248 public void onResourceLoadFinished(String url) {
249 if (isOwnerActivityRunning()) {
250 mXWalkResourceClient.onLoadFinished(mXWalkView, url);
255 public void onLoadResource(String url) {
256 if (mXWalkClient != null && isOwnerActivityRunning()) {
257 mXWalkClient.onLoadResource(mXWalkView, url);
262 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
267 public void onReceivedHttpAuthRequest(XWalkHttpAuthHandler handler, String host, String realm) {
268 if (mXWalkClient != null && isOwnerActivityRunning()) {
269 mXWalkClient.onReceivedHttpAuthRequest(mXWalkView, handler, host, realm);
274 public void onReceivedSslError(ValueCallback<Boolean> callback, SslError error) {
275 if (mXWalkResourceClient != null && isOwnerActivityRunning()) {
276 mXWalkResourceClient.onReceivedSslError(mXWalkView, callback, error);
281 public void onReceivedLoginRequest(String realm, String account, String args) {
285 public void onGeolocationPermissionsShowPrompt(String origin,
286 XWalkGeolocationPermissions.Callback callback) {
287 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
288 mXWalkWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
293 public void onGeolocationPermissionsHidePrompt() {
294 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
295 mXWalkWebChromeClient.onGeolocationPermissionsHidePrompt();
300 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
301 boolean isDoneCounting) {
305 public void onNewPicture(Picture picture) {
309 public void onPageStarted(String url) {
310 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
312 mLoadStatus = LoadStatusInternal.FINISHED;
313 mXWalkUIClient.onPageLoadStarted(mXWalkView, url);
318 public void onPageFinished(String url) {
319 if (!isOwnerActivityRunning()) return;
320 if (mPageLoadListener != null) mPageLoadListener.onPageFinished(url);
321 if (mXWalkUIClient != null) {
322 if (mLoadStatus == LoadStatusInternal.CANCELLED && mLoadingUrl != null) {
323 mXWalkUIClient.onPageLoadStopped(mXWalkView, mLoadingUrl, mLoadStatus);
325 mXWalkUIClient.onPageLoadStopped(mXWalkView, url, mLoadStatus);
330 // This isn't the accurate point to notify a resource loading is finished,
331 // but it's a workable way. We could enhance this by extending Content
332 // API in future if we have the demand.
333 onResourceLoadFinished(url);
337 protected void onStopLoading() {
338 mLoadStatus = LoadStatusInternal.CANCELLED;
342 public void onReceivedError(int errorCode, String description, String failingUrl) {
343 if (isOwnerActivityRunning()) {
344 if (mLoadingUrl != null && mLoadingUrl.equals(failingUrl)) {
345 mLoadStatus = LoadStatusInternal.FAILED;
347 mXWalkResourceClient.onReceivedLoadError(mXWalkView, errorCode, description, failingUrl);
352 public void onRendererUnresponsive() {
353 if (mXWalkClient != null && isOwnerActivityRunning()) {
354 mXWalkClient.onRendererUnresponsive(mXWalkView);
359 public void onRendererResponsive() {
360 if (mXWalkClient != null && isOwnerActivityRunning()) {
361 mXWalkClient.onRendererResponsive(mXWalkView);
366 public void onFormResubmission(Message dontResend, Message resend) {
367 dontResend.sendToTarget();
371 public void onDownloadStart(String url,
373 String contentDisposition,
375 long contentLength) {
379 public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
380 if (isDialog) return false;
382 XWalkUIClientInternal.InitiateByInternal initiator =
383 XWalkUIClientInternal.InitiateByInternal.BY_JAVASCRIPT;
385 initiator = XWalkUIClientInternal.InitiateByInternal.BY_USER_GESTURE;
388 ValueCallback<XWalkViewInternal> callback = new ValueCallback<XWalkViewInternal>() {
390 public void onReceiveValue(XWalkViewInternal newXWalkView) {
391 Message m = mUiThreadHandler.obtainMessage(NEW_XWALKVIEW_CREATED, newXWalkView);
396 return mXWalkUIClient.onCreateWindowRequested(mXWalkView, initiator, callback);
400 public void onRequestFocus() {
401 if (isOwnerActivityRunning()) {
402 mXWalkUIClient.onRequestFocus(mXWalkView);
407 public void onCloseWindow() {
408 if (isOwnerActivityRunning()) {
409 mXWalkUIClient.onJavascriptCloseWindow(mXWalkView);
414 public void onReceivedIcon(Bitmap bitmap) {
415 if (mXWalkWebChromeClient != null && mXWalkView != null) {
416 mXWalkWebChromeClient.onReceivedIcon(mXWalkView, bitmap);
422 public void onShowCustomView(View view, XWalkWebChromeClient.CustomViewCallback callback) {
423 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
424 mXWalkWebChromeClient.onShowCustomView(view, callback);
429 public void onShowCustomView(View view, int requestedOrientation,
430 XWalkWebChromeClient.CustomViewCallback callback) {
431 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
432 mXWalkWebChromeClient.onShowCustomView(view, requestedOrientation, callback);
437 public void onHideCustomView() {
438 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
439 mXWalkWebChromeClient.onHideCustomView();
444 public void onScaleChangedScaled(float oldScale, float newScale) {
445 if (isOwnerActivityRunning()) {
446 mXWalkUIClient.onScaleChanged(mXWalkView, oldScale, newScale);
451 public void didFinishLoad(String url) {
455 public void onTitleChanged(String title) {
456 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
457 mXWalkUIClient.onReceivedTitle(mXWalkView, title);
462 public void onToggleFullscreen(boolean enterFullscreen) {
463 if (isOwnerActivityRunning()) {
464 mIsFullscreen = enterFullscreen;
465 mXWalkUIClient.onFullscreenToggled(mXWalkView, enterFullscreen);
470 public boolean hasEnteredFullscreen() {
471 return mIsFullscreen;
475 public boolean shouldOpenWithDefaultBrowser(String contentUrl) {
476 Intent intent = new Intent();
477 intent.setAction("android.intent.action.VIEW");
478 Uri url = Uri.parse(contentUrl);
481 mXWalkView.getActivity().startActivity(intent);
482 } catch (ActivityNotFoundException exception) {
483 Log.w(TAG, "Activity not found for Intent:");
491 public boolean shouldOverrideRunFileChooser(
492 final int processId, final int renderId, final int modeFlags,
493 String acceptTypes, boolean capture) {
494 if (!isOwnerActivityRunning()) return false;
495 abstract class UriCallback implements ValueCallback<Uri> {
496 boolean syncNullReceived = false;
497 boolean syncCallFinished = false;
498 protected String resolveFileName(Uri uri, ContentResolver contentResolver) {
499 if (contentResolver == null || uri == null) return "";
500 Cursor cursor = null;
502 cursor = contentResolver.query(uri, null, null, null, null);
504 if (cursor != null && cursor.getCount() >= 1) {
505 cursor.moveToFirst();
506 int index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
507 if (index > -1) return cursor.getString(index);
509 } catch (NullPointerException e) {
510 // Some android models don't handle the provider call correctly.
511 // see crbug.com/345393
514 if (cursor != null) cursor.close();
519 UriCallback uploadFile = new UriCallback() {
520 boolean completed = false;
522 public void onReceiveValue(Uri value) {
524 throw new IllegalStateException("Duplicate openFileChooser result");
526 if (value == null && !syncCallFinished) {
527 syncNullReceived = true;
532 nativeOnFilesNotSelected(mNativeContentsClientBridge,
533 processId, renderId, modeFlags);
536 String displayName = null;
537 if (ContentResolver.SCHEME_FILE.equals(value.getScheme())) {
538 result = value.getSchemeSpecificPart();
539 displayName = value.getLastPathSegment();
540 } else if (ContentResolver.SCHEME_CONTENT.equals(value.getScheme())) {
541 result = value.toString();
542 displayName = resolveFileName(
543 value, mXWalkView.getActivity().getContentResolver());
545 result = value.getPath();
546 displayName = value.getLastPathSegment();
548 if (displayName == null || displayName.isEmpty()) displayName = result;
549 nativeOnFilesSelected(mNativeContentsClientBridge,
550 processId, renderId, modeFlags, result, displayName);
554 mXWalkUIClient.openFileChooser(
555 mXWalkView, uploadFile, acceptTypes, Boolean.toString(capture));
556 uploadFile.syncCallFinished = true;
557 // File chooser requires user interaction, valid derives should handle it in async process.
558 // If the ValueCallback receive a sync result with null value, it is considered the
559 // file chooser is not overridden.
560 if (uploadFile.syncNullReceived) {
561 return mXWalkView.showFileChooser(uploadFile, acceptTypes, Boolean.toString(capture));
563 return !uploadFile.syncNullReceived;
567 public ContentVideoViewClient getContentVideoViewClient() {
568 return new XWalkContentVideoViewClient(this, mXWalkView.getActivity(), mXWalkView);
571 // Used by the native peer to set/reset a weak ref to the native peer.
573 private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
574 mNativeContentsClientBridge = nativeContentsClientBridge;
577 // If returns false, the request is immediately canceled, and any call to proceedSslError
578 // has no effect. If returns true, the request should be canceled or proceeded using
579 // proceedSslError().
580 // Unlike the webview classic, we do not keep keep a database of certificates that
581 // are allowed by the user, because this functionality is already handled via
582 // ssl_policy in native layers.
584 private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
586 final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
588 // if the certificate or the client is null, cancel the request
591 final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
592 ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
594 public void onReceiveValue(Boolean value) {
595 proceedSslError(value.booleanValue(), id);
598 onReceivedSslError(callback, sslError);
602 private void proceedSslError(boolean proceed, int id) {
603 if (mNativeContentsClientBridge == 0) return;
604 nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
608 private void handleJsAlert(String url, String message, int id) {
609 if (isOwnerActivityRunning()) {
610 XWalkJavascriptResultHandlerInternal result =
611 new XWalkJavascriptResultHandlerInternal(this, id);
612 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
613 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_ALERT,
614 url, message, "", result);
619 private void handleJsConfirm(String url, String message, int id) {
620 if (isOwnerActivityRunning()) {
621 XWalkJavascriptResultHandlerInternal result =
622 new XWalkJavascriptResultHandlerInternal(this, id);
623 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
624 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_CONFIRM,
625 url, message, "", result);
630 private void handleJsPrompt(String url, String message, String defaultValue, int id) {
631 if (isOwnerActivityRunning()) {
632 XWalkJavascriptResultHandlerInternal result =
633 new XWalkJavascriptResultHandlerInternal(this, id);
634 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
635 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_PROMPT,
636 url, message, defaultValue, result);
641 private void handleJsBeforeUnload(String url, String message, int id) {
642 if (isOwnerActivityRunning()) {
643 XWalkJavascriptResultHandlerInternal result =
644 new XWalkJavascriptResultHandlerInternal(this, id);
645 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
646 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_BEFOREUNLOAD,
647 url, message, "", result);
652 private void updateNotificationIcon(int notificationId, Bitmap icon) {
653 mNotificationService.updateNotificationIcon(notificationId, icon);
657 private void showNotification(String title, String message, String replaceId,
658 int notificationId) {
659 mNotificationService.showNotification(
660 title, message, replaceId, notificationId);
664 private void cancelNotification(int notificationId) {
665 mNotificationService.cancelNotification(notificationId);
668 void confirmJsResult(int id, String prompt) {
669 if (mNativeContentsClientBridge == 0) return;
670 nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
673 void cancelJsResult(int id) {
674 if (mNativeContentsClientBridge == 0) return;
675 nativeCancelJsResult(mNativeContentsClientBridge, id);
678 void exitFullscreen(long nativeWebContents) {
679 if (mNativeContentsClientBridge == 0) return;
680 nativeExitFullscreen(mNativeContentsClientBridge, nativeWebContents);
683 public void notificationDisplayed(int id) {
684 if (mNativeContentsClientBridge == 0) return;
685 nativeNotificationDisplayed(mNativeContentsClientBridge, id);
688 public void notificationError(int id) {
689 if (mNativeContentsClientBridge == 0) return;
690 nativeNotificationError(mNativeContentsClientBridge, id);
693 public void notificationClicked(int id) {
694 if (mNativeContentsClientBridge == 0) return;
695 nativeNotificationClicked(mNativeContentsClientBridge, id);
698 public void notificationClosed(int id, boolean byUser) {
699 if (mNativeContentsClientBridge == 0) return;
700 nativeNotificationClosed(mNativeContentsClientBridge, id, byUser);
703 void setDownloadListener(DownloadListener listener) {
704 mDownloadListener = listener;
707 // Implement ContentViewDownloadDelegate methods.
708 public void requestHttpGetDownload(DownloadInfo downloadInfo) {
709 if (mDownloadListener != null) {
710 mDownloadListener.onDownloadStart(downloadInfo.getUrl(), downloadInfo.getUserAgent(),
711 downloadInfo.getContentDisposition(), downloadInfo.getMimeType(), downloadInfo.getContentLength());
715 public void onDownloadStarted(String filename, String mimeType) {
718 public void onDangerousDownload(String filename, int downloadId) {
722 public void onWebLayoutPageScaleFactorChanged(float pageScaleFactor) {
723 if (mPageScaleFactor == pageScaleFactor) return;
725 float oldPageScaleFactor = mPageScaleFactor;
726 mPageScaleFactor = pageScaleFactor;
727 onScaleChanged(oldPageScaleFactor, mPageScaleFactor);
731 public void onIconAvailable(String url) {
732 Message m = mUiThreadHandler.obtainMessage(NEW_ICON_DOWNLOAD, url);
733 mXWalkUIClient.onIconAvailable(mXWalkView, url, m);
737 public void onReceivedIcon(String url, Bitmap icon) {
738 mXWalkUIClient.onReceivedIcon(mXWalkView, url, icon);
741 //--------------------------------------------------------------------------------------------
743 //--------------------------------------------------------------------------------------------
744 private native void nativeProceedSslError(long nativeXWalkContentsClientBridge,
745 boolean proceed, int id);
747 private native void nativeConfirmJsResult(long nativeXWalkContentsClientBridge, int id,
749 private native void nativeCancelJsResult(long nativeXWalkContentsClientBridge, int id);
750 private native void nativeExitFullscreen(long nativeXWalkContentsClientBridge, long nativeWebContents);
751 private native void nativeNotificationDisplayed(long nativeXWalkContentsClientBridge, int id);
752 private native void nativeNotificationError(long nativeXWalkContentsClientBridge, int id);
753 private native void nativeNotificationClicked(long nativeXWalkContentsClientBridge, int id);
754 private native void nativeNotificationClosed(long nativeXWalkContentsClientBridge, int id, boolean byUser);
755 private native void nativeOnFilesSelected(long nativeXWalkContentsClientBridge,
756 int processId, int renderId, int mode_flags, String filepath, String displayName);
757 private native void nativeOnFilesNotSelected(long nativeXWalkContentsClientBridge,
758 int processId, int renderId, int mode_flags);
759 private native void nativeDownloadIcon(long nativeXWalkContentsClientBridge, String url);