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 (mXWalkClient != null && isOwnerActivityRunning()) {
276 mXWalkClient.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 onHideCustomView() {
430 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
431 mXWalkWebChromeClient.onHideCustomView();
436 public void onScaleChangedScaled(float oldScale, float newScale) {
437 if (isOwnerActivityRunning()) {
438 mXWalkUIClient.onScaleChanged(mXWalkView, oldScale, newScale);
443 public void didFinishLoad(String url) {
447 public void onTitleChanged(String title) {
448 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
449 mXWalkUIClient.onReceivedTitle(mXWalkView, title);
454 public void onToggleFullscreen(boolean enterFullscreen) {
455 if (isOwnerActivityRunning()) {
456 mIsFullscreen = enterFullscreen;
457 mXWalkUIClient.onFullscreenToggled(mXWalkView, enterFullscreen);
462 public boolean hasEnteredFullscreen() {
463 return mIsFullscreen;
467 public boolean shouldOpenWithDefaultBrowser(String contentUrl) {
468 Intent intent = new Intent();
469 intent.setAction("android.intent.action.VIEW");
470 Uri url = Uri.parse(contentUrl);
473 mXWalkView.getActivity().startActivity(intent);
474 } catch (ActivityNotFoundException exception) {
475 Log.w(TAG, "Activity not found for Intent:");
483 public boolean shouldOverrideRunFileChooser(
484 final int processId, final int renderId, final int modeFlags,
485 String acceptTypes, boolean capture) {
486 if (!isOwnerActivityRunning()) return false;
487 abstract class UriCallback implements ValueCallback<Uri> {
488 boolean syncNullReceived = false;
489 boolean syncCallFinished = false;
490 protected String resolveFileName(Uri uri, ContentResolver contentResolver) {
491 if (contentResolver == null || uri == null) return "";
492 Cursor cursor = null;
494 cursor = contentResolver.query(uri, null, null, null, null);
496 if (cursor != null && cursor.getCount() >= 1) {
497 cursor.moveToFirst();
498 int index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
499 if (index > -1) return cursor.getString(index);
501 } catch (NullPointerException e) {
502 // Some android models don't handle the provider call correctly.
503 // see crbug.com/345393
506 if (cursor != null) cursor.close();
511 UriCallback uploadFile = new UriCallback() {
512 boolean completed = false;
514 public void onReceiveValue(Uri value) {
516 throw new IllegalStateException("Duplicate openFileChooser result");
519 if (value == null && !syncCallFinished) {
520 syncNullReceived = true;
524 nativeOnFilesNotSelected(mNativeContentsClientBridge,
525 processId, renderId, modeFlags);
528 String displayName = null;
529 if (ContentResolver.SCHEME_FILE.equals(value.getScheme())) {
530 result = value.getSchemeSpecificPart();
531 displayName = value.getLastPathSegment();
532 } else if (ContentResolver.SCHEME_CONTENT.equals(value.getScheme())) {
533 result = value.toString();
534 displayName = resolveFileName(
535 value, mXWalkView.getActivity().getContentResolver());
537 result = value.getPath();
538 displayName = value.getLastPathSegment();
540 if (displayName == null || displayName.isEmpty()) displayName = result;
541 nativeOnFilesSelected(mNativeContentsClientBridge,
542 processId, renderId, modeFlags, result, displayName);
546 mXWalkUIClient.openFileChooser(
547 mXWalkView, uploadFile, acceptTypes, Boolean.toString(capture));
548 uploadFile.syncCallFinished = true;
549 // File chooser requires user interaction, valid derives should handle it in async process.
550 // If the ValueCallback receive a sync result with null value, it is considered the
551 // file chooser is not overridden.
552 return !uploadFile.syncNullReceived;
556 public ContentVideoViewClient getContentVideoViewClient() {
557 return new XWalkContentVideoViewClient(this, mXWalkView.getActivity(), mXWalkView);
560 // Used by the native peer to set/reset a weak ref to the native peer.
562 private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
563 mNativeContentsClientBridge = nativeContentsClientBridge;
566 // If returns false, the request is immediately canceled, and any call to proceedSslError
567 // has no effect. If returns true, the request should be canceled or proceeded using
568 // proceedSslError().
569 // Unlike the webview classic, we do not keep keep a database of certificates that
570 // are allowed by the user, because this functionality is already handled via
571 // ssl_policy in native layers.
573 private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
575 final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
577 // if the certificate or the client is null, cancel the request
580 final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
581 ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
583 public void onReceiveValue(Boolean value) {
584 proceedSslError(value.booleanValue(), id);
587 onReceivedSslError(callback, sslError);
591 private void proceedSslError(boolean proceed, int id) {
592 if (mNativeContentsClientBridge == 0) return;
593 nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
597 private void handleJsAlert(String url, String message, int id) {
598 if (isOwnerActivityRunning()) {
599 XWalkJavascriptResultHandlerInternal result =
600 new XWalkJavascriptResultHandlerInternal(this, id);
601 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
602 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_ALERT,
603 url, message, "", result);
608 private void handleJsConfirm(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_CONFIRM,
614 url, message, "", result);
619 private void handleJsPrompt(String url, String message, String defaultValue, int id) {
620 if (isOwnerActivityRunning()) {
621 XWalkJavascriptResultHandlerInternal result =
622 new XWalkJavascriptResultHandlerInternal(this, id);
623 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
624 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_PROMPT,
625 url, message, defaultValue, result);
630 private void handleJsBeforeUnload(String url, String message, int id) {
631 if (isOwnerActivityRunning()) {
632 XWalkJavascriptResultHandlerInternal result =
633 new XWalkJavascriptResultHandlerInternal(this, id);
634 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
635 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_BEFOREUNLOAD,
636 url, message, "", result);
641 private void updateNotificationIcon(int notificationId, Bitmap icon) {
642 mNotificationService.updateNotificationIcon(notificationId, icon);
646 private void showNotification(String title, String message, String replaceId,
647 int notificationId) {
648 mNotificationService.showNotification(
649 title, message, replaceId, notificationId);
653 private void cancelNotification(int notificationId) {
654 mNotificationService.cancelNotification(notificationId);
657 void confirmJsResult(int id, String prompt) {
658 if (mNativeContentsClientBridge == 0) return;
659 nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
662 void cancelJsResult(int id) {
663 if (mNativeContentsClientBridge == 0) return;
664 nativeCancelJsResult(mNativeContentsClientBridge, id);
667 void exitFullscreen(long nativeWebContents) {
668 if (mNativeContentsClientBridge == 0) return;
669 nativeExitFullscreen(mNativeContentsClientBridge, nativeWebContents);
672 public void notificationDisplayed(int id) {
673 if (mNativeContentsClientBridge == 0) return;
674 nativeNotificationDisplayed(mNativeContentsClientBridge, id);
677 public void notificationError(int id) {
678 if (mNativeContentsClientBridge == 0) return;
679 nativeNotificationError(mNativeContentsClientBridge, id);
682 public void notificationClicked(int id) {
683 if (mNativeContentsClientBridge == 0) return;
684 nativeNotificationClicked(mNativeContentsClientBridge, id);
687 public void notificationClosed(int id, boolean byUser) {
688 if (mNativeContentsClientBridge == 0) return;
689 nativeNotificationClosed(mNativeContentsClientBridge, id, byUser);
692 void setDownloadListener(DownloadListener listener) {
693 mDownloadListener = listener;
696 // Implement ContentViewDownloadDelegate methods.
697 public void requestHttpGetDownload(DownloadInfo downloadInfo) {
698 if (mDownloadListener != null) {
699 mDownloadListener.onDownloadStart(downloadInfo.getUrl(), downloadInfo.getUserAgent(),
700 downloadInfo.getContentDisposition(), downloadInfo.getMimeType(), downloadInfo.getContentLength());
704 public void onDownloadStarted(String filename, String mimeType) {
707 public void onDangerousDownload(String filename, int downloadId) {
711 public void onWebLayoutPageScaleFactorChanged(float pageScaleFactor) {
712 if (mPageScaleFactor == pageScaleFactor) return;
714 float oldPageScaleFactor = mPageScaleFactor;
715 mPageScaleFactor = pageScaleFactor;
716 onScaleChanged(oldPageScaleFactor, mPageScaleFactor);
720 public void onIconAvailable(String url) {
721 Message m = mUiThreadHandler.obtainMessage(NEW_ICON_DOWNLOAD, url);
722 mXWalkUIClient.onIconAvailable(mXWalkView, url, m);
726 public void onReceivedIcon(String url, Bitmap icon) {
727 mXWalkUIClient.onReceivedIcon(mXWalkView, url, icon);
730 //--------------------------------------------------------------------------------------------
732 //--------------------------------------------------------------------------------------------
733 private native void nativeProceedSslError(long nativeXWalkContentsClientBridge,
734 boolean proceed, int id);
736 private native void nativeConfirmJsResult(long nativeXWalkContentsClientBridge, int id,
738 private native void nativeCancelJsResult(long nativeXWalkContentsClientBridge, int id);
739 private native void nativeExitFullscreen(long nativeXWalkContentsClientBridge, long nativeWebContents);
740 private native void nativeNotificationDisplayed(long nativeXWalkContentsClientBridge, int id);
741 private native void nativeNotificationError(long nativeXWalkContentsClientBridge, int id);
742 private native void nativeNotificationClicked(long nativeXWalkContentsClientBridge, int id);
743 private native void nativeNotificationClosed(long nativeXWalkContentsClientBridge, int id, boolean byUser);
744 private native void nativeOnFilesSelected(long nativeXWalkContentsClientBridge,
745 int processId, int renderId, int mode_flags, String filepath, String displayName);
746 private native void nativeOnFilesNotSelected(long nativeXWalkContentsClientBridge,
747 int processId, int renderId, int mode_flags);
748 private native void nativeDownloadIcon(long nativeXWalkContentsClientBridge, String url);