3987d0777b25f15753c1fdd6be0acb498a560cb6
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / core / src / org / xwalk / core / XWalkContent.java
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.
5
6 package org.xwalk.core;
7
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.view.ViewGroup;
17 import android.webkit.ValueCallback;
18 import android.webkit.WebResourceResponse;
19 import android.widget.FrameLayout;
20
21 import org.chromium.base.CalledByNative;
22 import org.chromium.base.JNINamespace;
23 import org.chromium.base.ThreadUtils;
24 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
25 import org.chromium.content.browser.ContentVideoView;
26 import org.chromium.content.browser.ContentView;
27 import org.chromium.content.browser.ContentViewCore;
28 import org.chromium.content.browser.ContentViewRenderView;
29 import org.chromium.content.browser.ContentViewStatics;
30 import org.chromium.content.browser.LoadUrlParams;
31 import org.chromium.content.browser.NavigationHistory;
32 import org.chromium.media.MediaPlayerBridge;
33 import org.chromium.ui.base.ActivityWindowAndroid;
34
35 @JNINamespace("xwalk")
36 /**
37  * This class is the implementation class for XWalkView by calling internal
38  * various classes.
39  */
40 public class XWalkContent extends FrameLayout implements XWalkPreferences.KeyValueChangeListener {
41     private ContentViewCore mContentViewCore;
42     private ContentView mContentView;
43     private ContentViewRenderView mContentViewRenderView;
44     private ActivityWindowAndroid mWindow;
45     private XWalkDevToolsServer mDevToolsServer;
46     private XWalkView mXWalkView;
47     private XWalkContentsClientBridge mContentsClientBridge;
48     private XWalkContentsIoThreadClient mIoThreadClient;
49     private XWalkWebContentsDelegateAdapter mXWalkContentsDelegateAdapter;
50     private XWalkSettings mSettings;
51     private XWalkGeolocationPermissions mGeolocationPermissions;
52     private XWalkLaunchScreenManager mLaunchScreenManager;
53
54     int mXWalkContent;
55     int mWebContents;
56     boolean mReadyToLoad = false;
57     String mPendingUrl = null;
58
59     public XWalkContent(Context context, AttributeSet attrs, XWalkView xwView) {
60         super(context, attrs);
61
62         // Initialize the WebContensDelegate.
63         mXWalkView = xwView;
64         mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
65         mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
66             mContentsClientBridge);
67         mIoThreadClient = new XWalkIoThreadClientImpl();
68
69         // Initialize mWindow which is needed by content
70         mWindow = new ActivityWindowAndroid(xwView.getActivity());
71
72         // Initialize ContentViewRenderView
73         mContentViewRenderView = new ContentViewRenderView(context, mWindow) {
74             protected void onReadyToRender() {
75                 if (mPendingUrl != null) {
76                     doLoadUrl(mPendingUrl);
77                     mPendingUrl = null;
78                 }
79
80                 mReadyToLoad = true;
81             }
82         };
83         mLaunchScreenManager = new XWalkLaunchScreenManager(context, mXWalkView);
84         mContentViewRenderView.registerFirstRenderedFrameListener(mLaunchScreenManager);
85         addView(mContentViewRenderView,
86                 new FrameLayout.LayoutParams(
87                         FrameLayout.LayoutParams.MATCH_PARENT,
88                         FrameLayout.LayoutParams.MATCH_PARENT));
89
90         mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
91         mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
92                 mContentsClientBridge.getInterceptNavigationDelegate());
93
94         // Initialize ContentView.
95         mContentView = ContentView.newInstance(getContext(), mWebContents, mWindow);
96         addView(mContentView,
97                 new FrameLayout.LayoutParams(
98                         FrameLayout.LayoutParams.MATCH_PARENT,
99                         FrameLayout.LayoutParams.MATCH_PARENT));
100         mContentView.setContentViewClient(mContentsClientBridge);
101
102         mContentViewRenderView.setCurrentContentView(mContentView);
103
104         // For addJavascriptInterface
105         mContentViewCore = mContentView.getContentViewCore();
106         mContentsClientBridge.installWebContentsObserver(mContentViewCore);
107
108         mContentView.getContentViewCore().setDownloadDelegate(mContentsClientBridge);
109
110         // Set the third argument isAccessFromFileURLsGrantedByDefault to false, so that
111         // the members mAllowUniversalAccessFromFileURLs and mAllowFileAccessFromFileURLs
112         // won't be changed from false to true at the same time in the constructor of
113         // XWalkSettings class.
114         mSettings = new XWalkSettings(getContext(), mWebContents, false);
115         // Enable AllowFileAccessFromFileURLs, so that files under file:// path could be
116         // loaded by XMLHttpRequest.
117         mSettings.setAllowFileAccessFromFileURLs(true);
118
119         SharedPreferences sharedPreferences = new InMemorySharedPreferences();
120         mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
121
122         MediaPlayerBridge.setResourceLoadingFilter(
123                 new XWalkMediaPlayerResourceLoadingFilter());
124
125         XWalkPreferences.load(this);
126     }
127
128     void doLoadUrl(String url) {
129         //TODO(Xingnan): Configure appropriate parameters here.
130         // Handle the same url loading by parameters.
131         if (TextUtils.equals(url, mContentView.getUrl())) {
132             mContentView.getContentViewCore().reload(true);
133         } else {
134             LoadUrlParams params = new LoadUrlParams(url);
135             params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
136             mContentView.loadUrl(params);
137         }
138
139         mContentView.requestFocus();
140     }
141
142     public void loadUrl(String url) {
143         if (url == null)
144             return;
145
146         if (mReadyToLoad)
147             doLoadUrl(url);
148         else
149             mPendingUrl = url;
150     }
151
152     public void reload() {
153         if (mReadyToLoad) {
154             mContentView.getContentViewCore().reload(true);
155         }
156     }
157
158     public String getUrl() {
159         String url = mContentView.getUrl();
160         if (url == null || url.trim().isEmpty()) return null;
161         return url;
162     }
163
164     public String getTitle() {
165         String title = mContentView.getTitle().trim();
166         if (title == null) title = "";
167         return title;
168     }
169
170     public void addJavascriptInterface(Object object, String name) {
171         mContentViewCore.addJavascriptInterface(object, name);
172     }
173
174     public void evaluateJavascript(String script, ValueCallback<String> callback) {
175         final ValueCallback<String>  fCallback = callback;
176         ContentViewCore.JavaScriptCallback coreCallback = new ContentViewCore.JavaScriptCallback() {
177             @Override
178             public void handleJavaScriptResult(String jsonResult) {
179                 fCallback.onReceiveValue(jsonResult);
180             }
181         };
182         mContentViewCore.evaluateJavaScript(script, coreCallback);
183     }
184
185     public void setUIClient(XWalkUIClient client) {
186         mContentsClientBridge.setUIClient(client);
187     }
188
189     public void setResourceClient(XWalkResourceClient client) {
190         mContentsClientBridge.setResourceClient(client);
191     }
192
193     public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
194         mContentsClientBridge.setXWalkWebChromeClient(client);
195     }
196
197     public XWalkWebChromeClient getXWalkWebChromeClient() {
198         return mContentsClientBridge.getXWalkWebChromeClient();
199     }
200
201     public void setXWalkClient(XWalkClient client) {
202         mContentsClientBridge.setXWalkClient(client);
203     }
204
205     public void setDownloadListener(DownloadListener listener) {
206         mContentsClientBridge.setDownloadListener(listener);
207     }
208
209     public void setNavigationHandler(XWalkNavigationHandler handler) {
210         mContentsClientBridge.setNavigationHandler(handler);
211     }
212
213     public void setNotificationService(XWalkNotificationService service) {
214         mContentsClientBridge.setNotificationService(service);
215     }
216
217     public void onPause() {
218         mContentView.onHide();
219     }
220
221     public void onResume() {
222         mContentView.onShow();
223     }
224
225     public void onActivityResult(int requestCode, int resultCode, Intent data) {
226         mWindow.onActivityResult(requestCode, resultCode, data);
227     }
228
229     public boolean onNewIntent(Intent intent) {
230         return mContentsClientBridge.onNewIntent(intent);
231     }
232
233     public void clearCache(boolean includeDiskFiles) {
234         if (mXWalkContent == 0) return;
235         nativeClearCache(mXWalkContent, includeDiskFiles);
236     }
237
238     public void clearHistory() {
239         mContentView.getContentViewCore().clearHistory();
240     }
241
242     public boolean canGoBack() {
243         return mContentView.canGoBack();
244     }
245
246     public void goBack() {
247         mContentView.goBack();
248     }
249
250     public boolean canGoForward() {
251         return mContentView.canGoForward();
252     }
253
254     public void goForward() {
255         mContentView.goForward();
256     }
257
258     void navigateTo(int offset)  {
259         mContentView.getContentViewCore().goToOffset(offset);
260     }
261
262     public void stopLoading() {
263         mContentView.getContentViewCore().stopLoading();
264     }
265
266     // TODO(Guangzhen): ContentViewStatics will be removed in upstream,
267     // details in content_view_statics.cc.
268     // We need follow up after upstream updates that.
269     public void pauseTimers() {
270         ContentViewStatics.setWebKitSharedTimersSuspended(true);
271     }
272
273     public void resumeTimers() {
274         ContentViewStatics.setWebKitSharedTimersSuspended(false);
275     }
276
277     public String getOriginalUrl() {
278         NavigationHistory history = mContentViewCore.getNavigationHistory();
279         int currentIndex = history.getCurrentEntryIndex();
280         if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
281             return history.getEntryAtIndex(currentIndex).getOriginalUrl();
282         }
283         return null;
284     }
285
286     public String getXWalkVersion() {
287         if (mXWalkContent == 0) return "";
288         return nativeGetVersion(mXWalkContent);
289     }
290
291     public void setNetworkAvailable(boolean networkUp) {
292         if (mXWalkContent == 0) return;
293         nativeSetJsOnlineProperty(mXWalkContent, networkUp);
294     }
295
296     // For instrumentation test.
297     public ContentViewCore getContentViewCoreForTest() {
298         return mContentViewCore;
299     }
300
301     // For instrumentation test.
302     public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
303         contentClient.installWebContentsObserver(mContentViewCore);
304     }
305
306     public String devToolsAgentId() {
307         if (mXWalkContent == 0) return "";
308         return nativeDevToolsAgentId(mXWalkContent);
309     }
310
311     public XWalkSettings getSettings() {
312         return mSettings;
313     }
314
315     public void loadAppFromManifest(String path, String manifest) {
316         if (path == null || manifest == null || mXWalkContent == 0) {
317             return;
318         }
319
320         if (!nativeSetManifest(mXWalkContent, path, manifest)) {
321             throw new RuntimeException("Failed to parse the manifest file.");
322         }
323     }
324
325     public XWalkNavigationHistory getNavigationHistory() {
326         return new XWalkNavigationHistory(mXWalkView, mContentViewCore.getNavigationHistory());
327     }
328
329     public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
330
331     public XWalkNavigationHistory saveState(Bundle outState) {
332         if (outState == null) return null;
333
334         byte[] state = nativeGetState(mXWalkContent);
335         if (state == null) return null;
336
337         outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
338         return getNavigationHistory();
339     }
340
341     public XWalkNavigationHistory restoreState(Bundle inState) {
342         if (inState == null) return null;
343
344         byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
345         if (state == null) return null;
346
347         boolean result = nativeSetState(mXWalkContent, state);
348
349         // The onUpdateTitle callback normally happens when a page is loaded,
350         // but is optimized out in the restoreState case because the title is
351         // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
352         // call the callback explicitly here.
353         if (result) {
354             mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
355         }
356
357         return result ? getNavigationHistory() : null;
358     }
359
360     boolean hasEnteredFullscreen() {
361         return mContentsClientBridge.hasEnteredFullscreen();
362     }
363
364     void exitFullscreen() {
365         if (hasEnteredFullscreen()) {
366             mContentsClientBridge.exitFullscreen(mWebContents);
367         }
368     }
369
370     @CalledByNative
371     public void onGetUrlFromManifest(String url) {
372         if (url != null && !url.isEmpty()) {
373             loadUrl(url);
374         }
375     }
376
377     @CalledByNative
378     public void onGetUrlAndLaunchScreenFromManifest(String url, String readyWhen) {
379         if (url == null || url.isEmpty()) return;
380         mLaunchScreenManager.displayLaunchScreen(readyWhen);
381         mContentsClientBridge.registerPageLoadListener(mLaunchScreenManager);
382         loadUrl(url);
383     }
384
385     @CalledByNative
386     public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
387         if (enterFullscreen) mContentsClientBridge.onToggleFullscreen(true);
388     }
389
390     public void destroy() {
391         if (mXWalkContent == 0) return;
392
393         // Reset existing notification service in order to destruct it.
394         setNotificationService(null);
395         // Remove its children used for page rendering from view hierarchy.
396         removeView(mContentView);
397         removeView(mContentViewRenderView);
398         mContentViewRenderView.setCurrentContentView(null);
399
400         // Destroy the native resources.
401         mContentViewRenderView.destroy();
402         mContentView.destroy();
403
404         nativeDestroy(mXWalkContent);
405         mXWalkContent = 0;
406     }
407
408     public int getRoutingID() {
409         return nativeGetRoutingID(mXWalkContent);
410     }
411
412     //--------------------------------------------------------------------------------------------
413     private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
414         // All methods are called on the IO thread.
415
416         @Override
417         public int getCacheMode() {
418             return mSettings.getCacheMode();
419         }
420
421         @Override
422         public InterceptedRequestData shouldInterceptRequest(final String url,
423                 boolean isMainFrame) {
424
425             // Notify a resource load is started. This is not the best place to start the callback
426             // but it's a workable way.
427             mContentsClientBridge.onResourceLoadStarted(url);
428
429             WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
430             InterceptedRequestData interceptedRequestData = null;
431
432             if (webResourceResponse == null) {
433                 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
434             } else {
435                 if (isMainFrame && webResourceResponse.getData() == null) {
436                     mContentsClientBridge.getCallbackHelper().postOnReceivedError(-1, null, url);
437                 }
438                 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
439                                                                     webResourceResponse.getEncoding(),
440                                                                     webResourceResponse.getData());
441             }
442             return interceptedRequestData;
443         }
444
445         @Override
446         public boolean shouldBlockContentUrls() {
447             return !mSettings.getAllowContentAccess();
448         }
449
450         @Override
451         public boolean shouldBlockFileUrls() {
452             return !mSettings.getAllowFileAccess();
453         }
454
455         @Override
456         public boolean shouldBlockNetworkLoads() {
457             return mSettings.getBlockNetworkLoads();
458         }
459
460         @Override
461         public void onDownloadStart(String url,
462                                     String userAgent,
463                                     String contentDisposition,
464                                     String mimeType,
465                                     long contentLength) {
466             mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
467                     contentDisposition, mimeType, contentLength);
468         }
469
470         @Override
471         public void newLoginRequest(String realm, String account, String args) {
472             mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
473         }
474     }
475
476     private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
477         @Override
478         public void invoke(final String origin, final boolean allow, final boolean retain) {
479             ThreadUtils.runOnUiThread(new Runnable() {
480                 @Override
481                 public void run() {
482                     if (retain) {
483                         if (allow) {
484                             mGeolocationPermissions.allow(origin);
485                         } else {
486                             mGeolocationPermissions.deny(origin);
487                         }
488                     }
489                     nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
490                 }
491             });
492         }
493     }
494
495     @CalledByNative
496     private void onGeolocationPermissionsShowPrompt(String origin) {
497         // Reject if geolocation is disabled, or the origin has a retained deny.
498         if (!mSettings.getGeolocationEnabled()) {
499             nativeInvokeGeolocationCallback(mXWalkContent, false, origin);
500             return;
501         }
502         // Allow if the origin has a retained allow.
503         if (mGeolocationPermissions.hasOrigin(origin)) {
504             nativeInvokeGeolocationCallback(mXWalkContent,
505                     mGeolocationPermissions.isOriginAllowed(origin),
506                     origin);
507             return;
508         }
509         mContentsClientBridge.onGeolocationPermissionsShowPrompt(
510                 origin, new XWalkGeolocationCallback());
511     }
512
513     @CalledByNative
514     public void onGeolocationPermissionsHidePrompt() {
515         mContentsClientBridge.onGeolocationPermissionsHidePrompt();
516     }
517
518     public String enableRemoteDebugging(int allowedUid) {
519         // Chrome looks for "devtools_remote" pattern in the name of a unix domain socket
520         // to identify a debugging page
521         final String socketName = getContext().getApplicationContext().getPackageName() + "_devtools_remote";
522         if (mDevToolsServer == null) {
523             mDevToolsServer = new XWalkDevToolsServer(socketName);
524             mDevToolsServer.allowConnectionFromUid(allowedUid);
525             mDevToolsServer.setRemoteDebuggingEnabled(true);
526         }
527         // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
528         return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
529     }
530
531     // Enables remote debugging and returns the URL at which the dev tools server is listening
532     // for commands. Only the current process is allowed to connect to the server.
533     String enableRemoteDebugging() {
534         return enableRemoteDebugging(getContext().getApplicationInfo().uid);
535     }
536
537     void disableRemoteDebugging() {
538         if (mDevToolsServer ==  null) return;
539
540         if (mDevToolsServer.isRemoteDebuggingEnabled()) {
541             mDevToolsServer.setRemoteDebuggingEnabled(false);
542         }
543         mDevToolsServer.destroy();
544         mDevToolsServer = null;
545     }
546
547     @Override
548     public void onKeyValueChanged(String key, boolean value) {
549         if (key == XWalkPreferences.REMOTE_DEBUGGING) {
550             if (value) enableRemoteDebugging();
551             else disableRemoteDebugging();
552         }
553     }
554
555     private native int nativeInit(XWalkWebContentsDelegate webViewContentsDelegate,
556             XWalkContentsClientBridge bridge);
557     private static native void nativeDestroy(int nativeXWalkContent);
558     private native int nativeGetWebContents(int nativeXWalkContent,
559             XWalkContentsIoThreadClient ioThreadClient,
560             InterceptNavigationDelegate delegate);
561     private native void nativeClearCache(int nativeXWalkContent, boolean includeDiskFiles);
562     private native String nativeDevToolsAgentId(int nativeXWalkContent);
563     private native String nativeGetVersion(int nativeXWalkContent);
564     private native void nativeSetJsOnlineProperty(int nativeXWalkContent, boolean networkUp);
565     private native boolean nativeSetManifest(int nativeXWalkContent, String path, String manifest);
566     private native int nativeGetRoutingID(int nativeXWalkContent);
567     private native void nativeInvokeGeolocationCallback(
568             int nativeXWalkContent, boolean value, String requestingFrame);
569     private native byte[] nativeGetState(int nativeXWalkContent);
570     private native boolean nativeSetState(int nativeXWalkContent, byte[] state);
571 }