Upstream version 7.35.139.0
[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.util.Log;
17 import android.view.ViewGroup;
18 import android.webkit.ValueCallback;
19 import android.webkit.WebResourceResponse;
20 import android.widget.FrameLayout;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24
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;
37
38 @JNINamespace("xwalk")
39 /**
40  * This class is the implementation class for XWalkView by calling internal
41  * various classes.
42  */
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;
57
58     long mXWalkContent;
59     long mWebContents;
60     boolean mReadyToLoad = false;
61     String mPendingUrl = null;
62     String mPendingData = null;
63
64     public XWalkContent(Context context, AttributeSet attrs, XWalkView xwView) {
65         super(context, attrs);
66
67         // Initialize the WebContensDelegate.
68         mXWalkView = xwView;
69         mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
70         mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
71             mContentsClientBridge);
72         mIoThreadClient = new XWalkIoThreadClientImpl();
73
74         // Initialize mWindow which is needed by content
75         mWindow = new ActivityWindowAndroid(xwView.getActivity());
76
77         // Initialize ContentViewRenderView
78         mContentViewRenderView = new ContentViewRenderView(context, mWindow) {
79             protected void onReadyToRender() {
80                 if (mPendingUrl != null) {
81                     doLoadUrl(mPendingUrl, mPendingData);
82                     mPendingUrl = null;
83                     mPendingData = null;
84                 }
85
86                 mReadyToLoad = true;
87             }
88         };
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));
95
96         mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
97         mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
98                 mContentsClientBridge.getInterceptNavigationDelegate());
99
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);
107
108         mContentViewRenderView.setCurrentContentView(mContentView);
109
110         // For addJavascriptInterface
111         mContentViewCore = mContentView.getContentViewCore();
112         mContentsClientBridge.installWebContentsObserver(mContentViewCore);
113
114         mContentView.getContentViewCore().setDownloadDelegate(mContentsClientBridge);
115
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);
124
125         SharedPreferences sharedPreferences = new InMemorySharedPreferences();
126         mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
127
128         MediaPlayerBridge.setResourceLoadingFilter(
129                 new XWalkMediaPlayerResourceLoadingFilter());
130
131         XWalkPreferences.load(this);
132     }
133
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);
139         } else {
140             LoadUrlParams params = null;
141             if (content == null || content.isEmpty()) {
142                 params = new LoadUrlParams(url);
143             } else {
144                 params = LoadUrlParams.createLoadDataParamsWithBaseUrl(
145                         content, "text/html", false, url, null);
146             }
147             params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
148             mContentView.loadUrl(params);
149         }
150
151         mContentView.requestFocus();
152     }
153
154     public void loadUrl(String url, String data) {
155         if ((url == null || url.isEmpty()) &&
156                 (data == null || data.isEmpty())) {
157             return;
158         }
159
160         if (mReadyToLoad) {
161             doLoadUrl(url, data);
162         } else {
163             mPendingUrl = url;
164             mPendingData = data;
165         }
166     }
167
168     public void reload(int mode) {
169         if (mReadyToLoad) {
170             switch (mode) {
171                 case XWalkView.RELOAD_IGNORE_CACHE:
172                     mContentView.getContentViewCore().reloadIgnoringCache(true);
173                     break;
174                 case XWalkView.RELOAD_NORMAL:
175                 default:
176                     mContentView.getContentViewCore().reload(true);
177
178             }
179         }
180     }
181
182     public String getUrl() {
183         String url = mContentView.getUrl();
184         if (url == null || url.trim().isEmpty()) return null;
185         return url;
186     }
187
188     public String getTitle() {
189         String title = mContentView.getTitle().trim();
190         if (title == null) title = "";
191         return title;
192     }
193
194     public void addJavascriptInterface(Object object, String name) {
195         mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name,
196                 JavascriptInterface.class);
197     }
198
199     public void evaluateJavascript(String script, ValueCallback<String> callback) {
200         final ValueCallback<String>  fCallback = callback;
201         ContentViewCore.JavaScriptCallback coreCallback = new ContentViewCore.JavaScriptCallback() {
202             @Override
203             public void handleJavaScriptResult(String jsonResult) {
204                 fCallback.onReceiveValue(jsonResult);
205             }
206         };
207         mContentViewCore.evaluateJavaScript(script, coreCallback);
208     }
209
210     public void setUIClient(XWalkUIClient client) {
211         mContentsClientBridge.setUIClient(client);
212     }
213
214     public void setResourceClient(XWalkResourceClient client) {
215         mContentsClientBridge.setResourceClient(client);
216     }
217
218     public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
219         mContentsClientBridge.setXWalkWebChromeClient(client);
220     }
221
222     public XWalkWebChromeClient getXWalkWebChromeClient() {
223         return mContentsClientBridge.getXWalkWebChromeClient();
224     }
225
226     public void setXWalkClient(XWalkClient client) {
227         mContentsClientBridge.setXWalkClient(client);
228     }
229
230     public void setDownloadListener(DownloadListener listener) {
231         mContentsClientBridge.setDownloadListener(listener);
232     }
233
234     public void setNavigationHandler(XWalkNavigationHandler handler) {
235         mContentsClientBridge.setNavigationHandler(handler);
236     }
237
238     public void setNotificationService(XWalkNotificationService service) {
239         mContentsClientBridge.setNotificationService(service);
240     }
241
242     public void onPause() {
243         mContentView.onHide();
244     }
245
246     public void onResume() {
247         mContentView.onShow();
248     }
249
250     public void onActivityResult(int requestCode, int resultCode, Intent data) {
251         mWindow.onActivityResult(requestCode, resultCode, data);
252     }
253
254     public boolean onNewIntent(Intent intent) {
255         return mContentsClientBridge.onNewIntent(intent);
256     }
257
258     public void clearCache(boolean includeDiskFiles) {
259         if (mXWalkContent == 0) return;
260         nativeClearCache(mXWalkContent, includeDiskFiles);
261     }
262
263     public void clearHistory() {
264         mContentView.getContentViewCore().clearHistory();
265     }
266
267     public boolean canGoBack() {
268         return mContentView.canGoBack();
269     }
270
271     public void goBack() {
272         mContentView.goBack();
273     }
274
275     public boolean canGoForward() {
276         return mContentView.canGoForward();
277     }
278
279     public void goForward() {
280         mContentView.goForward();
281     }
282
283     void navigateTo(int offset)  {
284         mContentView.getContentViewCore().goToOffset(offset);
285     }
286
287     public void stopLoading() {
288         mContentView.getContentViewCore().stopLoading();
289     }
290
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);
296     }
297
298     public void resumeTimers() {
299         ContentViewStatics.setWebKitSharedTimersSuspended(false);
300     }
301
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();
307         }
308         return null;
309     }
310
311     public String getXWalkVersion() {
312         if (mXWalkContent == 0) return "";
313         return nativeGetVersion(mXWalkContent);
314     }
315
316     public void setNetworkAvailable(boolean networkUp) {
317         if (mXWalkContent == 0) return;
318         nativeSetJsOnlineProperty(mXWalkContent, networkUp);
319     }
320
321     // For instrumentation test.
322     public ContentViewCore getContentViewCoreForTest() {
323         return mContentViewCore;
324     }
325
326     // For instrumentation test.
327     public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
328         contentClient.installWebContentsObserver(mContentViewCore);
329     }
330
331     public String devToolsAgentId() {
332         if (mXWalkContent == 0) return "";
333         return nativeDevToolsAgentId(mXWalkContent);
334     }
335
336     public XWalkSettings getSettings() {
337         return mSettings;
338     }
339
340     public void loadAppFromManifest(String url, String data) {
341         if (mXWalkContent == 0 ||
342                 ((url == null || url.isEmpty()) &&
343                         (data == null || data.isEmpty()))) {
344             return;
345         }
346
347         String content = data;
348         // If the data of manifest.json is not set, try to load it.
349         if (data == null || data.isEmpty()) {
350             try {
351                 content = AndroidProtocolHandler.getUrlContent(mXWalkView.getActivity(), url);
352             } catch (IOException e) {
353                 throw new RuntimeException("Failed to read the manifest: " + url);
354             }
355         }
356
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
359         // to get base url.
360         String baseUrl = url;
361         int position = url.lastIndexOf("/");
362         if (position != -1) {
363             baseUrl = url.substring(0, position + 1);
364         } else {
365             Log.w(TAG, "The url of manifest.json is probably not set correctly.");
366         }
367
368         if (!nativeSetManifest(mXWalkContent, baseUrl, content)) {
369             throw new RuntimeException("Failed to parse the manifest file: " + url);
370         }
371     }
372
373     public XWalkNavigationHistory getNavigationHistory() {
374         return new XWalkNavigationHistory(mXWalkView, mContentViewCore.getNavigationHistory());
375     }
376
377     public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
378
379     public XWalkNavigationHistory saveState(Bundle outState) {
380         if (outState == null) return null;
381
382         byte[] state = nativeGetState(mXWalkContent);
383         if (state == null) return null;
384
385         outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
386         return getNavigationHistory();
387     }
388
389     public XWalkNavigationHistory restoreState(Bundle inState) {
390         if (inState == null) return null;
391
392         byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
393         if (state == null) return null;
394
395         boolean result = nativeSetState(mXWalkContent, state);
396
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.
401         if (result) {
402             mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
403         }
404
405         return result ? getNavigationHistory() : null;
406     }
407
408     boolean hasEnteredFullscreen() {
409         return mContentsClientBridge.hasEnteredFullscreen();
410     }
411
412     void exitFullscreen() {
413         if (hasEnteredFullscreen()) {
414             mContentsClientBridge.exitFullscreen(mWebContents);
415         }
416     }
417
418     @CalledByNative
419     public void onGetUrlFromManifest(String url) {
420         if (url != null && !url.isEmpty()) {
421             loadUrl(url, null);
422         }
423     }
424
425     @CalledByNative
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);
430         loadUrl(url, null);
431     }
432
433     @CalledByNative
434     public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
435         if (enterFullscreen) mContentsClientBridge.onToggleFullscreen(true);
436     }
437
438     public void destroy() {
439         if (mXWalkContent == 0) return;
440
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);
448
449         // Destroy the native resources.
450         mContentViewRenderView.destroy();
451         mContentView.destroy();
452
453         nativeDestroy(mXWalkContent);
454         mXWalkContent = 0;
455     }
456
457     public int getRoutingID() {
458         return nativeGetRoutingID(mXWalkContent);
459     }
460
461     //--------------------------------------------------------------------------------------------
462     private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
463         // All methods are called on the IO thread.
464
465         @Override
466         public int getCacheMode() {
467             return mSettings.getCacheMode();
468         }
469
470         @Override
471         public InterceptedRequestData shouldInterceptRequest(final String url,
472                 boolean isMainFrame) {
473
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);
477
478             WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
479             InterceptedRequestData interceptedRequestData = null;
480
481             if (webResourceResponse == null) {
482                 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
483             } else {
484                 if (isMainFrame && webResourceResponse.getData() == null) {
485                     mContentsClientBridge.getCallbackHelper().postOnReceivedError(
486                             XWalkResourceClient.ERROR_UNKNOWN, null, url);
487                 }
488                 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
489                                                                     webResourceResponse.getEncoding(),
490                                                                     webResourceResponse.getData());
491             }
492             return interceptedRequestData;
493         }
494
495         @Override
496         public boolean shouldBlockContentUrls() {
497             return !mSettings.getAllowContentAccess();
498         }
499
500         @Override
501         public boolean shouldBlockFileUrls() {
502             return !mSettings.getAllowFileAccess();
503         }
504
505         @Override
506         public boolean shouldBlockNetworkLoads() {
507             return mSettings.getBlockNetworkLoads();
508         }
509
510         @Override
511         public void onDownloadStart(String url,
512                                     String userAgent,
513                                     String contentDisposition,
514                                     String mimeType,
515                                     long contentLength) {
516             mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
517                     contentDisposition, mimeType, contentLength);
518         }
519
520         @Override
521         public void newLoginRequest(String realm, String account, String args) {
522             mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
523         }
524     }
525
526     private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
527         @Override
528         public void invoke(final String origin, final boolean allow, final boolean retain) {
529             ThreadUtils.runOnUiThread(new Runnable() {
530                 @Override
531                 public void run() {
532                     if (retain) {
533                         if (allow) {
534                             mGeolocationPermissions.allow(origin);
535                         } else {
536                             mGeolocationPermissions.deny(origin);
537                         }
538                     }
539                     nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
540                 }
541             });
542         }
543     }
544
545     @CalledByNative
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);
550             return;
551         }
552         // Allow if the origin has a retained allow.
553         if (mGeolocationPermissions.hasOrigin(origin)) {
554             nativeInvokeGeolocationCallback(mXWalkContent,
555                     mGeolocationPermissions.isOriginAllowed(origin),
556                     origin);
557             return;
558         }
559         mContentsClientBridge.onGeolocationPermissionsShowPrompt(
560                 origin, new XWalkGeolocationCallback());
561     }
562
563     @CalledByNative
564     public void onGeolocationPermissionsHidePrompt() {
565         mContentsClientBridge.onGeolocationPermissionsHidePrompt();
566     }
567
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);
576         }
577         // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
578         return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
579     }
580
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);
585     }
586
587     void disableRemoteDebugging() {
588         if (mDevToolsServer ==  null) return;
589
590         if (mDevToolsServer.isRemoteDebuggingEnabled()) {
591             mDevToolsServer.setRemoteDebuggingEnabled(false);
592         }
593         mDevToolsServer.destroy();
594         mDevToolsServer = null;
595     }
596
597     @Override
598     public void onKeyValueChanged(String key, boolean value) {
599         if (key == XWalkPreferences.REMOTE_DEBUGGING) {
600             if (value) enableRemoteDebugging();
601             else disableRemoteDebugging();
602         }
603     }
604
605     public void setOverlayVideoMode(boolean enabled) {
606         if (mContentViewRenderView != null) {
607             mContentViewRenderView.setOverlayVideoMode(enabled);
608         }
609     }
610
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);
627 }