();
43 | mText = null;
44 | }
45 |
46 | /**
47 | * Add a URI to match, and the code to return when this URI is
48 | * matched. URI nodes may be exact match string, the token "*"
49 | * that matches any text, or the token "#" that matches only
50 | * numbers.
51 | *
52 | * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
53 | * this method will accept a leading slash in the path.
54 | *
55 | * @param authority the authority to match
56 | * @param path the path to match. * may be used as a wild card for
57 | * any text, and # may be used as a wild card for numbers.
58 | * @param code the code that is returned when a URI is matched
59 | * against the given components. Must be positive.
60 | */
61 | public void addURI(String scheme, String authority, String path, Object code) {
62 | if (code == null) {
63 | throw new IllegalArgumentException("Code can't be null");
64 | }
65 |
66 | String[] tokens = null;
67 | if (path != null) {
68 | String newPath = path;
69 | // Strip leading slash if present.
70 | if (path.length() > 0 && path.charAt(0) == '/') {
71 | newPath = path.substring(1);
72 | }
73 | tokens = PATH_SPLIT_PATTERN.split(newPath);
74 | }
75 |
76 | int numTokens = tokens != null ? tokens.length : 0;
77 | UriMatcher node = this;
78 | for (int i = -2; i < numTokens; i++) {
79 | String token;
80 | if (i == -2)
81 | token = scheme;
82 | else if (i == -1)
83 | token = authority;
84 | else
85 | token = tokens[i];
86 | ArrayList children = node.mChildren;
87 | int numChildren = children.size();
88 | UriMatcher child;
89 | int j;
90 | for (j = 0; j < numChildren; j++) {
91 | child = children.get(j);
92 | if (token.equals(child.mText)) {
93 | node = child;
94 | break;
95 | }
96 | }
97 | if (j == numChildren) {
98 | // Child not found, create it
99 | child = new UriMatcher();
100 | if (token.equals("**")) {
101 | child.mWhich = REST;
102 | } else if (token.equals("*")) {
103 | child.mWhich = TEXT;
104 | } else {
105 | child.mWhich = EXACT;
106 | }
107 | child.mText = token;
108 | node.mChildren.add(child);
109 | node = child;
110 | }
111 | }
112 | node.mCode = code;
113 | }
114 |
115 | static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/");
116 |
117 | /**
118 | * Try to match against the path in a url.
119 | *
120 | * @param uri The url whose path we will match against.
121 | * @return The code for the matched node (added using addURI),
122 | * or null if there is no matched node.
123 | */
124 | public Object match(Uri uri) {
125 | final List pathSegments = uri.getPathSegments();
126 | final int li = pathSegments.size();
127 |
128 | UriMatcher node = this;
129 |
130 | if (li == 0 && uri.getAuthority() == null) {
131 | return this.mCode;
132 | }
133 |
134 | for (int i = -2; i < li; i++) {
135 | String u;
136 | if (i == -2)
137 | u = uri.getScheme();
138 | else if (i == -1)
139 | u = uri.getAuthority();
140 | else
141 | u = pathSegments.get(i);
142 | ArrayList list = node.mChildren;
143 | if (list == null) {
144 | break;
145 | }
146 | node = null;
147 | int lj = list.size();
148 | for (int j = 0; j < lj; j++) {
149 | UriMatcher n = list.get(j);
150 | which_switch:
151 | switch (n.mWhich) {
152 | case EXACT:
153 | if (n.mText.equals(u)) {
154 | node = n;
155 | }
156 | break;
157 | case TEXT:
158 | node = n;
159 | break;
160 | case REST:
161 | return n.mCode;
162 | }
163 | if (node != null) {
164 | break;
165 | }
166 | }
167 | if (node == null) {
168 | return null;
169 | }
170 | }
171 |
172 | return node.mCode;
173 | }
174 |
175 | private static final int EXACT = 0;
176 | private static final int TEXT = 1;
177 | private static final int REST = 2;
178 |
179 | private Object mCode;
180 | private int mWhich;
181 | private String mText;
182 | private ArrayList mChildren;
183 | }
184 |
--------------------------------------------------------------------------------
/src/android/com/ionicframework/cordova/webview/WebViewLocalServer.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2015 Google Inc. All rights reserved.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.ionicframework.cordova.webview;
17 |
18 | import android.content.Context;
19 | import android.net.Uri;
20 | import android.os.Build;
21 | import android.util.Log;
22 | import android.webkit.WebResourceRequest;
23 | import android.webkit.WebResourceResponse;
24 |
25 | import org.apache.cordova.ConfigXmlParser;
26 |
27 | import java.io.IOException;
28 | import java.io.InputStream;
29 | import java.net.HttpURLConnection;
30 | import java.net.SocketTimeoutException;
31 | import java.net.URL;
32 | import java.net.URLConnection;
33 | import java.util.HashMap;
34 | import java.util.Map;
35 | import java.util.UUID;
36 |
37 | /**
38 | * Helper class meant to be used with the android.webkit.WebView class to enable hosting assets,
39 | * resources and other data on 'virtual' http(s):// URL.
40 | * Hosting assets and resources on http(s):// URLs is desirable as it is compatible with the
41 | * Same-Origin policy.
42 | *
43 | * This class is intended to be used from within the
44 | * {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView, String)} and
45 | * {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
46 | * android.webkit.WebResourceRequest)}
47 | * methods.
48 | */
49 | public class WebViewLocalServer {
50 | private static String TAG = "WebViewAssetServer";
51 | private String basePath;
52 | public final static String httpScheme = "http";
53 | public final static String httpsScheme = "https";
54 | public final static String fileStart = "/_app_file_";
55 | public final static String contentStart = "/_app_content_";
56 |
57 | private final UriMatcher uriMatcher;
58 | private final AndroidProtocolHandler protocolHandler;
59 | private final String authority;
60 | private final String customScheme;
61 | // Whether we're serving local files or proxying (for example, when doing livereload on a
62 | // non-local endpoint (will be false in that case)
63 | private boolean isAsset;
64 | // Whether to route all requests to paths without extensions back to `index.html`
65 | private final boolean html5mode;
66 | private ConfigXmlParser parser;
67 |
68 | public String getAuthority() { return authority; }
69 |
70 | /**
71 | * A handler that produces responses for paths on the virtual asset server.
72 | *
73 | * Methods of this handler will be invoked on a background thread and care must be taken to
74 | * correctly synchronize access to any shared state.
75 | *
76 | * On Android KitKat and above these methods may be called on more than one thread. This thread
77 | * may be different than the thread on which the shouldInterceptRequest method was invoke.
78 | * This means that on Android KitKat and above it is possible to block in this method without
79 | * blocking other resources from loading. The number of threads used to parallelize loading
80 | * is an internal implementation detail of the WebView and may change between updates which
81 | * means that the amount of time spend blocking in this method should be kept to an absolute
82 | * minimum.
83 | */
84 | public abstract static class PathHandler {
85 | protected String mimeType;
86 | private String encoding;
87 | private String charset;
88 | private int statusCode;
89 | private String reasonPhrase;
90 | private Map responseHeaders;
91 |
92 | public PathHandler() {
93 | this(null, null, 200, "OK", null);
94 | }
95 |
96 | public PathHandler(String encoding, String charset, int statusCode,
97 | String reasonPhrase, Map responseHeaders) {
98 | this.encoding = encoding;
99 | this.charset = charset;
100 | this.statusCode = statusCode;
101 | this.reasonPhrase = reasonPhrase;
102 | Map tempResponseHeaders;
103 | if (responseHeaders == null) {
104 | tempResponseHeaders = new HashMap();
105 | } else {
106 | tempResponseHeaders = responseHeaders;
107 | }
108 | tempResponseHeaders.put("Cache-Control", "no-cache");
109 | this.responseHeaders = tempResponseHeaders;
110 | }
111 |
112 | abstract public InputStream handle(Uri url);
113 |
114 | public String getEncoding() {
115 | return encoding;
116 | }
117 |
118 | public String getCharset() {
119 | return charset;
120 | }
121 |
122 | public int getStatusCode() {
123 | return statusCode;
124 | }
125 |
126 | public String getReasonPhrase() {
127 | return reasonPhrase;
128 | }
129 |
130 | public Map getResponseHeaders() {
131 | return responseHeaders;
132 | }
133 | }
134 |
135 | /**
136 | * Information about the URLs used to host the assets in the WebView.
137 | */
138 | public static class AssetHostingDetails {
139 | private Uri httpPrefix;
140 | private Uri httpsPrefix;
141 |
142 | /*package*/ AssetHostingDetails(Uri httpPrefix, Uri httpsPrefix) {
143 | this.httpPrefix = httpPrefix;
144 | this.httpsPrefix = httpsPrefix;
145 | }
146 |
147 | /**
148 | * Gets the http: scheme prefix at which assets are hosted.
149 | *
150 | * @return the http: scheme prefix at which assets are hosted. Can return null.
151 | */
152 | public Uri getHttpPrefix() {
153 | return httpPrefix;
154 | }
155 |
156 | /**
157 | * Gets the https: scheme prefix at which assets are hosted.
158 | *
159 | * @return the https: scheme prefix at which assets are hosted. Can return null.
160 | */
161 | public Uri getHttpsPrefix() {
162 | return httpsPrefix;
163 | }
164 | }
165 |
166 | public WebViewLocalServer(Context context, String authority, boolean html5mode, ConfigXmlParser parser, String customScheme) {
167 | uriMatcher = new UriMatcher(null);
168 | this.html5mode = html5mode;
169 | this.parser = parser;
170 | this.protocolHandler = new AndroidProtocolHandler(context.getApplicationContext());
171 | this.authority = authority;
172 | this.customScheme = customScheme;
173 | }
174 |
175 | private static Uri parseAndVerifyUrl(String url) {
176 | if (url == null) {
177 | return null;
178 | }
179 | Uri uri = Uri.parse(url);
180 | if (uri == null) {
181 | Log.e(TAG, "Malformed URL: " + url);
182 | return null;
183 | }
184 | String path = uri.getPath();
185 | if (path == null || path.length() == 0) {
186 | Log.e(TAG, "URL does not have a path: " + url);
187 | return null;
188 | }
189 | return uri;
190 | }
191 |
192 | private static WebResourceResponse createWebResourceResponse(String mimeType, String encoding, int statusCode, String reasonPhrase, Map responseHeaders, InputStream data) {
193 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
194 | int finalStatusCode = statusCode;
195 | try {
196 | if (data.available() == 0) {
197 | finalStatusCode = 404;
198 | }
199 | } catch (IOException e) {
200 | finalStatusCode = 500;
201 | }
202 | return new WebResourceResponse(mimeType, encoding, finalStatusCode, reasonPhrase, responseHeaders, data);
203 | } else {
204 | return new WebResourceResponse(mimeType, encoding, data);
205 | }
206 | }
207 |
208 | /**
209 | * Attempt to retrieve the WebResourceResponse associated with the given request
.
210 | * This method should be invoked from within
211 | * {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
212 | * android.webkit.WebResourceRequest)}.
213 | *
214 | * @param uri the request Uri to process.
215 | * @return a response if the request URL had a matching handler, null if no handler was found.
216 | */
217 | public WebResourceResponse shouldInterceptRequest(Uri uri, WebResourceRequest request) {
218 | PathHandler handler;
219 | synchronized (uriMatcher) {
220 | handler = (PathHandler) uriMatcher.match(uri);
221 | }
222 | if (handler == null) {
223 | return null;
224 | }
225 |
226 | if (isLocalFile(uri) || uri.getAuthority().equals(this.authority)) {
227 | Log.d("SERVER", "Handling local request: " + uri.toString());
228 | return handleLocalRequest(uri, handler, request);
229 | } else {
230 | return handleProxyRequest(uri, handler);
231 | }
232 | }
233 |
234 | private boolean isLocalFile(Uri uri) {
235 | String path = uri.getPath();
236 | if (path.startsWith(contentStart) || path.startsWith(fileStart)) {
237 | return true;
238 | }
239 | return false;
240 | }
241 |
242 |
243 | private WebResourceResponse handleLocalRequest(Uri uri, PathHandler handler, WebResourceRequest request) {
244 | String path = uri.getPath();
245 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && request != null && request.getRequestHeaders().get("Range") != null) {
246 | InputStream responseStream = new LollipopLazyInputStream(handler, uri);
247 | String mimeType = getMimeType(path, responseStream);
248 | Map tempResponseHeaders = handler.getResponseHeaders();
249 | int statusCode = 206;
250 | try {
251 | int totalRange = responseStream.available();
252 | String rangeString = request.getRequestHeaders().get("Range");
253 | String[] parts = rangeString.split("=");
254 | String[] streamParts = parts[1].split("-");
255 | String fromRange = streamParts[0];
256 | int range = totalRange-1;
257 | if (streamParts.length > 1) {
258 | range = Integer.parseInt(streamParts[1]);
259 | }
260 | tempResponseHeaders.put("Accept-Ranges", "bytes");
261 | tempResponseHeaders.put("Content-Range", "bytes " + fromRange + "-" + range + "/" + totalRange);
262 | } catch (IOException e) {
263 | statusCode = 404;
264 | }
265 | return createWebResourceResponse(mimeType, handler.getEncoding(),
266 | statusCode, handler.getReasonPhrase(), tempResponseHeaders, responseStream);
267 | }
268 | if (isLocalFile(uri)) {
269 | InputStream responseStream = new LollipopLazyInputStream(handler, uri);
270 | String mimeType = getMimeType(path, responseStream);
271 | return createWebResourceResponse(mimeType, handler.getEncoding(),
272 | handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
273 | }
274 |
275 | if (path.equals("") || path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
276 | InputStream stream;
277 | String launchURL = parser.getLaunchUrl();
278 | String launchFile = launchURL.substring(launchURL.lastIndexOf("/") + 1, launchURL.length());
279 | try {
280 | String startPath = this.basePath + "/" + launchFile;
281 | if (isAsset) {
282 | stream = protocolHandler.openAsset(startPath);
283 | } else {
284 | stream = protocolHandler.openFile(startPath);
285 | }
286 |
287 | } catch (IOException e) {
288 | Log.e(TAG, "Unable to open " + launchFile, e);
289 | return null;
290 | }
291 |
292 | return createWebResourceResponse("text/html", handler.getEncoding(),
293 | handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
294 | }
295 |
296 | int periodIndex = path.lastIndexOf(".");
297 | if (periodIndex >= 0) {
298 | InputStream responseStream = new LollipopLazyInputStream(handler, uri);
299 | String mimeType = getMimeType(path, responseStream);
300 | return createWebResourceResponse(mimeType, handler.getEncoding(),
301 | handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
302 | }
303 |
304 | return null;
305 | }
306 |
307 | /**
308 | * Instead of reading files from the filesystem/assets, proxy through to the URL
309 | * and let an external server handle it.
310 | * @param uri
311 | * @param handler
312 | * @return
313 | */
314 | private WebResourceResponse handleProxyRequest(Uri uri, PathHandler handler) {
315 | try {
316 | String path = uri.getPath();
317 | URL url = new URL(uri.toString());
318 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
319 | conn.setRequestMethod("GET");
320 | conn.setReadTimeout(30 * 1000);
321 | conn.setConnectTimeout(30 * 1000);
322 |
323 | InputStream stream = conn.getInputStream();
324 |
325 | if (path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
326 | return createWebResourceResponse("text/html", handler.getEncoding(),
327 | handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
328 | }
329 |
330 | int periodIndex = path.lastIndexOf(".");
331 | if (periodIndex >= 0) {
332 | String ext = path.substring(path.lastIndexOf("."), path.length());
333 |
334 | // TODO: Conjure up a bit more subtlety than this
335 | if (ext.equals(".html")) {
336 | }
337 |
338 | String mimeType = getMimeType(path, stream);
339 |
340 | return createWebResourceResponse(mimeType, handler.getEncoding(),
341 | handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
342 | }
343 |
344 | return createWebResourceResponse("", handler.getEncoding(),
345 | handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
346 |
347 | } catch (SocketTimeoutException ex) {
348 | // bridge.handleAppUrlLoadError(ex);
349 | } catch (Exception ex) {
350 | // bridge.handleAppUrlLoadError(ex);
351 | }
352 | return null;
353 | }
354 |
355 | private String getMimeType(String path, InputStream stream) {
356 | String mimeType = null;
357 | try {
358 | mimeType = URLConnection.guessContentTypeFromName(path); // Does not recognize *.js
359 | if (mimeType != null && path.endsWith(".js") && mimeType.equals("image/x-icon")) {
360 | Log.d(IonicWebViewEngine.TAG, "We shouldn't be here");
361 | }
362 | if (mimeType == null) {
363 | if (path.endsWith(".js") || path.endsWith(".mjs")) {
364 | // Make sure JS files get the proper mimetype to support ES modules
365 | mimeType = "application/javascript";
366 | } else if (path.endsWith(".wasm")) {
367 | mimeType = "application/wasm";
368 | } else {
369 | mimeType = URLConnection.guessContentTypeFromStream(stream);
370 | }
371 | }
372 | } catch (Exception ex) {
373 | Log.e(TAG, "Unable to get mime type" + path, ex);
374 | }
375 | return mimeType;
376 | }
377 |
378 | /**
379 | * Registers a handler for the given uri
. The handler
will be invoked
380 | * every time the shouldInterceptRequest
method of the instance is called with
381 | * a matching uri
.
382 | *
383 | * @param uri the uri to use the handler for. The scheme and authority (domain) will be matched
384 | * exactly. The path may contain a '*' element which will match a single element of
385 | * a path (so a handler registered for /a/* will be invoked for /a/b and /a/c.html
386 | * but not for /a/b/b) or the '**' element which will match any number of path
387 | * elements.
388 | * @param handler the handler to use for the uri.
389 | */
390 | void register(Uri uri, PathHandler handler) {
391 | synchronized (uriMatcher) {
392 | uriMatcher.addURI(uri.getScheme(), uri.getAuthority(), uri.getPath(), handler);
393 | }
394 | }
395 |
396 | /**
397 | * Hosts the application's assets on an http(s):// URL. Assets from the local path
398 | * assetPath/...
will be available under
399 | * http(s)://{uuid}.androidplatform.net/assets/...
.
400 | *
401 | * @param assetPath the local path in the application's asset folder which will be made
402 | * available by the server (for example "/www").
403 | */
404 | public void hostAssets(String assetPath) {
405 | hostAssets(authority, assetPath);
406 | }
407 |
408 |
409 | /**
410 | * Hosts the application's assets on an http(s):// URL. Assets from the local path
411 | * assetPath/...
will be available under
412 | * http(s)://{domain}/{virtualAssetPath}/...
.
413 | *
414 | * @param domain custom domain on which the assets should be hosted (for example "example.com").
415 | * @param assetPath the local path in the application's asset folder which will be made
416 | * available by the server (for example "/www").
417 | * @return prefixes under which the assets are hosted.
418 | */
419 | public void hostAssets(final String domain,
420 | final String assetPath) {
421 | this.isAsset = true;
422 | this.basePath = assetPath;
423 |
424 | createHostingDetails();
425 | }
426 |
427 | private void createHostingDetails() {
428 | final String assetPath = this.basePath;
429 |
430 | if (assetPath.indexOf('*') != -1) {
431 | throw new IllegalArgumentException("assetPath cannot contain the '*' character.");
432 | }
433 |
434 | PathHandler handler = new PathHandler() {
435 | @Override
436 | public InputStream handle(Uri url) {
437 | InputStream stream = null;
438 | String path = url.getPath();
439 | try {
440 | if (path.startsWith(contentStart)) {
441 | stream = protocolHandler.openContentUrl(url);
442 | } else if (path.startsWith(fileStart) || !isAsset) {
443 | if (!path.startsWith(fileStart)) {
444 | path = basePath + url.getPath();
445 | }
446 | stream = protocolHandler.openFile(path);
447 | } else {
448 | stream = protocolHandler.openAsset(assetPath + path);
449 | }
450 | } catch (IOException e) {
451 | Log.e(TAG, "Unable to open asset URL: " + url);
452 | return null;
453 | }
454 |
455 | return stream;
456 | }
457 | };
458 |
459 | registerUriForScheme(httpScheme, handler, authority);
460 | registerUriForScheme(httpsScheme, handler, authority);
461 | if (!customScheme.equals(httpScheme) && !customScheme.equals(httpsScheme)) {
462 | registerUriForScheme(customScheme, handler, authority);
463 | }
464 |
465 | }
466 |
467 | private void registerUriForScheme(String scheme, PathHandler handler, String authority) {
468 | Uri.Builder uriBuilder = new Uri.Builder();
469 | uriBuilder.scheme(scheme);
470 | uriBuilder.authority(authority);
471 | uriBuilder.path("");
472 | Uri uriPrefix = uriBuilder.build();
473 |
474 | register(Uri.withAppendedPath(uriPrefix, "/"), handler);
475 | register(Uri.withAppendedPath(uriPrefix, "**"), handler);
476 | }
477 |
478 | /**
479 | * Hosts the application's resources on an http(s):// URL. Resources
480 | * http(s)://{uuid}.androidplatform.net/res/{resource_type}/{resource_name}
.
481 | *
482 | * @return prefixes under which the resources are hosted.
483 | */
484 | public AssetHostingDetails hostResources() {
485 | return hostResources(authority, "/res", true, true);
486 | }
487 |
488 | /**
489 | * Hosts the application's resources on an http(s):// URL. Resources
490 | * http(s)://{uuid}.androidplatform.net/{virtualResourcesPath}/{resource_type}/{resource_name}
.
491 | *
492 | * @param virtualResourcesPath the path on the local server under which the resources
493 | * should be hosted.
494 | * @param enableHttp whether to enable hosting using the http scheme.
495 | * @param enableHttps whether to enable hosting using the https scheme.
496 | * @return prefixes under which the resources are hosted.
497 | */
498 | public AssetHostingDetails hostResources(final String virtualResourcesPath, boolean enableHttp,
499 | boolean enableHttps) {
500 | return hostResources(authority, virtualResourcesPath, enableHttp, enableHttps);
501 | }
502 |
503 | /**
504 | * Hosts the application's resources on an http(s):// URL. Resources
505 | * http(s)://{domain}/{virtualResourcesPath}/{resource_type}/{resource_name}
.
506 | *
507 | * @param domain custom domain on which the assets should be hosted (for example "example.com").
508 | * If untrusted content is to be loaded into the WebView it is advised to make
509 | * this random.
510 | * @param virtualResourcesPath the path on the local server under which the resources
511 | * should be hosted.
512 | * @param enableHttp whether to enable hosting using the http scheme.
513 | * @param enableHttps whether to enable hosting using the https scheme.
514 | * @return prefixes under which the resources are hosted.
515 | */
516 | public AssetHostingDetails hostResources(final String domain,
517 | final String virtualResourcesPath, boolean enableHttp,
518 | boolean enableHttps) {
519 | if (virtualResourcesPath.indexOf('*') != -1) {
520 | throw new IllegalArgumentException(
521 | "virtualResourcesPath cannot contain the '*' character.");
522 | }
523 |
524 | Uri.Builder uriBuilder = new Uri.Builder();
525 | uriBuilder.scheme(httpScheme);
526 | uriBuilder.authority(domain);
527 | uriBuilder.path(virtualResourcesPath);
528 |
529 | Uri httpPrefix = null;
530 | Uri httpsPrefix = null;
531 |
532 | PathHandler handler = new PathHandler() {
533 | @Override
534 | public InputStream handle(Uri url) {
535 | InputStream stream = protocolHandler.openResource(url);
536 | String mimeType = null;
537 | try {
538 | mimeType = URLConnection.guessContentTypeFromStream(stream);
539 | } catch (Exception ex) {
540 | Log.e(TAG, "Unable to get mime type" + url);
541 | }
542 |
543 | return stream;
544 | }
545 | };
546 |
547 | if (enableHttp) {
548 | httpPrefix = uriBuilder.build();
549 | register(Uri.withAppendedPath(httpPrefix, "**"), handler);
550 | }
551 | if (enableHttps) {
552 | uriBuilder.scheme(httpsScheme);
553 | httpsPrefix = uriBuilder.build();
554 | register(Uri.withAppendedPath(httpsPrefix, "**"), handler);
555 | }
556 | return new AssetHostingDetails(httpPrefix, httpsPrefix);
557 | }
558 |
559 |
560 | /**
561 | * Hosts the application's files on an http(s):// URL. Files from the basePath
562 | * basePath/...
will be available under
563 | * http(s)://{uuid}.androidplatform.net/...
.
564 | *
565 | * @param basePath the local path in the application's data folder which will be made
566 | * available by the server (for example "/www").
567 | */
568 | public void hostFiles(final String basePath) {
569 | this.isAsset = false;
570 | this.basePath = basePath;
571 | createHostingDetails();
572 | }
573 |
574 | /**
575 | * The KitKat WebView reads the InputStream on a separate threadpool. We can use that to
576 | * parallelize loading.
577 | */
578 | private static abstract class LazyInputStream extends InputStream {
579 | protected final PathHandler handler;
580 | private InputStream is = null;
581 |
582 | public LazyInputStream(PathHandler handler) {
583 | this.handler = handler;
584 | }
585 |
586 | private InputStream getInputStream() {
587 | if (is == null) {
588 | is = handle();
589 | }
590 | return is;
591 | }
592 |
593 | protected abstract InputStream handle();
594 |
595 | @Override
596 | public int available() throws IOException {
597 | InputStream is = getInputStream();
598 | return (is != null) ? is.available() : 0;
599 | }
600 |
601 | @Override
602 | public int read() throws IOException {
603 | InputStream is = getInputStream();
604 | return (is != null) ? is.read() : -1;
605 | }
606 |
607 | @Override
608 | public int read(byte b[]) throws IOException {
609 | InputStream is = getInputStream();
610 | return (is != null) ? is.read(b) : -1;
611 | }
612 |
613 | @Override
614 | public int read(byte b[], int off, int len) throws IOException {
615 | InputStream is = getInputStream();
616 | return (is != null) ? is.read(b, off, len) : -1;
617 | }
618 |
619 | @Override
620 | public long skip(long n) throws IOException {
621 | InputStream is = getInputStream();
622 | return (is != null) ? is.skip(n) : 0;
623 | }
624 | }
625 |
626 | // For L and above.
627 | private static class LollipopLazyInputStream extends LazyInputStream {
628 | private Uri uri;
629 | private InputStream is;
630 |
631 | public LollipopLazyInputStream(PathHandler handler, Uri uri) {
632 | super(handler);
633 | this.uri = uri;
634 | }
635 |
636 | @Override
637 | protected InputStream handle() {
638 | return handler.handle(uri);
639 | }
640 | }
641 |
642 | public String getBasePath(){
643 | return this.basePath;
644 | }
645 | }
646 |
--------------------------------------------------------------------------------
/src/android/com/lifang123/cordova/webview/CrosswalkWebViewEngine.java:
--------------------------------------------------------------------------------
1 | package com.lifang123.cordova.webview;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.content.pm.PackageInfo;
7 | import android.net.Uri;
8 | import android.os.Build;
9 | import android.util.Log;
10 | import android.webkit.WebResourceResponse;
11 |
12 | import com.ionicframework.cordova.webview.IonicWebView;
13 | import com.ionicframework.cordova.webview.WebViewLocalServer;
14 |
15 | import org.apache.cordova.ConfigXmlParser;
16 | import org.apache.cordova.CordovaInterface;
17 | import org.apache.cordova.CordovaPreferences;
18 | import org.apache.cordova.CordovaResourceApi;
19 | import org.apache.cordova.CordovaWebView;
20 | import org.apache.cordova.CordovaWebViewEngine;
21 | import org.apache.cordova.NativeToJsMessageQueue;
22 | import org.apache.cordova.PluginManager;
23 | import org.apache.cordova.engine.SystemWebView;
24 | import org.crosswalk.engine.XWalkCordovaResourceClient;
25 | import org.crosswalk.engine.XWalkWebViewEngine;
26 | import org.xwalk.core.XWalkView;
27 |
28 |
29 | public class CrosswalkWebViewEngine extends XWalkWebViewEngine {
30 | public static final String TAG = "CrosswalkWebViewEngine";
31 |
32 | private WebViewLocalServer localServer;
33 | private String CDV_LOCAL_SERVER;
34 | private static final String LAST_BINARY_VERSION_CODE = "lastBinaryVersionCode";
35 | private static final String LAST_BINARY_VERSION_NAME = "lastBinaryVersionName";
36 |
37 | /**
38 | * Used when created via reflection.
39 | */
40 | public CrosswalkWebViewEngine(Context context, CordovaPreferences preferences) {
41 | super(context, preferences);
42 | Log.d(TAG, "Ionic Web View Engine Starting Right Up 1...");
43 | }
44 |
45 |
46 |
47 | @Override
48 | public void init(CordovaWebView parentWebView, CordovaInterface cordova, final CordovaWebViewEngine.Client client,
49 | CordovaResourceApi resourceApi, PluginManager pluginManager,
50 | NativeToJsMessageQueue nativeToJsMessageQueue) {
51 | ConfigXmlParser parser = new ConfigXmlParser();
52 | parser.parse(cordova.getActivity());
53 |
54 | String hostname = preferences.getString("Hostname", "localhost");
55 | String scheme = preferences.getString("Scheme", "http");
56 | CDV_LOCAL_SERVER = scheme + "://" + hostname;
57 |
58 | ServerClient serverClient = new ServerClient(this, parser);
59 |
60 | localServer = new WebViewLocalServer(cordova.getActivity(), hostname, true, parser, scheme);
61 | localServer.hostAssets("www");
62 |
63 | //webView.setWebViewClient(new ServerClient(this, parser));
64 | webView.setResourceClient(serverClient);
65 |
66 |
67 | super.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue);
68 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
69 | /*
70 | final WebSettings settings = webView.getSettings();
71 | int mode = preferences.getInteger("MixedContentMode", 0);
72 | settings.setMixedContentMode(mode);
73 | */
74 | }
75 | SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
76 | String path = prefs.getString(IonicWebView.CDV_SERVER_PATH, null);
77 | if (!isDeployDisabled() && !isNewBinary() && path != null && !path.isEmpty()) {
78 | setServerBasePath(path);
79 | }
80 | }
81 |
82 | private boolean isNewBinary() {
83 | String versionCode = "";
84 | String versionName = "";
85 | SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
86 | String lastVersionCode = prefs.getString(LAST_BINARY_VERSION_CODE, null);
87 | String lastVersionName = prefs.getString(LAST_BINARY_VERSION_NAME, null);
88 |
89 | try {
90 | PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0);
91 | versionCode = Integer.toString(pInfo.versionCode);
92 | versionName = pInfo.versionName;
93 | } catch (Exception ex) {
94 | Log.e(TAG, "Unable to get package info", ex);
95 | }
96 |
97 | if (!versionCode.equals(lastVersionCode) || !versionName.equals(lastVersionName)) {
98 | SharedPreferences.Editor editor = prefs.edit();
99 | editor.putString(LAST_BINARY_VERSION_CODE, versionCode);
100 | editor.putString(LAST_BINARY_VERSION_NAME, versionName);
101 | editor.putString(IonicWebView.CDV_SERVER_PATH, "");
102 | editor.apply();
103 | return true;
104 | }
105 | return false;
106 | }
107 |
108 | private boolean isDeployDisabled() {
109 | return preferences.getBoolean("DisableDeploy", false);
110 | }
111 |
112 | public class ServerClient extends XWalkCordovaResourceClient {
113 | private ConfigXmlParser parser;
114 |
115 | public ServerClient(XWalkWebViewEngine parentEngine, ConfigXmlParser parser) {
116 | super(parentEngine);
117 | this.parser = parser;
118 | }
119 |
120 | @Override
121 | public WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
122 | return localServer.shouldInterceptRequest(Uri.parse(url), null);
123 | }
124 |
125 | @Override
126 | public void onLoadStarted(XWalkView view, String url) {
127 | super.onLoadStarted(view, url);
128 | /**为了保留加载 file:// 协议 ,以下代码暂时被注释掉*/
129 | // String launchUrl = parser.getLaunchUrl();
130 | // if (!launchUrl.contains(WebViewLocalServer.httpsScheme) && !launchUrl.contains(WebViewLocalServer.httpScheme) && url.equals(launchUrl)) {
131 | // view.stopLoading();
132 | // // When using a custom scheme the app won't load if server start url doesn't end in /
133 | // String startUrl = CDV_LOCAL_SERVER;
134 | // if (!CDV_LOCAL_SERVER.startsWith(WebViewLocalServer.httpsScheme) && !CDV_LOCAL_SERVER.startsWith(WebViewLocalServer.httpScheme)) {
135 | // startUrl += "/";
136 | // }
137 | // view.loadUrl(startUrl);
138 | // }
139 | }
140 |
141 | @Override
142 | public void onLoadFinished(XWalkView view, String url) {
143 | super.onLoadFinished(view, url);
144 | view.loadUrl("javascript:(function() { " +
145 | "window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "';" +
146 | "})()");
147 | }
148 |
149 |
150 | }
151 |
152 | public void setServerBasePath(String path) {
153 | localServer.hostFiles(path);
154 | webView.loadUrl(CDV_LOCAL_SERVER);
155 | }
156 |
157 | public String getServerBasePath() {
158 | return this.localServer.getBasePath();
159 | }
160 | }
--------------------------------------------------------------------------------
/src/android/com/lifang123/cordova/webview/IonicCrosswalkWebView.java:
--------------------------------------------------------------------------------
1 | package com.lifang123.cordova.webview;
2 |
3 | import android.app.Activity;
4 | import android.content.SharedPreferences;
5 |
6 | import com.ionicframework.cordova.webview.IonicWebViewEngine;
7 |
8 | import org.apache.cordova.CallbackContext;
9 | import org.apache.cordova.CordovaPlugin;
10 | import org.json.JSONArray;
11 | import org.json.JSONException;
12 |
13 | public class IonicCrosswalkWebView extends CordovaPlugin {
14 |
15 | public static final String WEBVIEW_PREFS_NAME = "WebViewSettings";
16 | public static final String CDV_SERVER_PATH = "serverBasePath";
17 |
18 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
19 |
20 | if (action.equals("setServerBasePath")) {
21 | final String path = args.getString(0);
22 | cordova.getActivity().runOnUiThread(new Runnable() {
23 | public void run() {
24 | ((IonicWebViewEngine) webView.getEngine()).setServerBasePath(path);
25 | }
26 | });
27 | return true;
28 | } else if (action.equals("getServerBasePath")) {
29 | callbackContext.success(((IonicWebViewEngine) webView.getEngine()).getServerBasePath());
30 | return true;
31 | } else if (action.equals("persistServerBasePath")) {
32 | String path = ((IonicWebViewEngine) webView.getEngine()).getServerBasePath();
33 | SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
34 | SharedPreferences.Editor editor = prefs.edit();
35 | editor.putString(CDV_SERVER_PATH, path);
36 | editor.apply();
37 | return true;
38 | } else if (action.equals("useCrosswalkWebViewAtTheNextStartup")) {
39 | SharedPreferences sharedPreferences = this.cordova.getContext().getSharedPreferences("use_webview_engine",0);
40 | SharedPreferences.Editor editor = sharedPreferences.edit();
41 | editor.putString("WEBVIEW_ENGINE","CROSSWALK");
42 | editor.commit();
43 | return true;
44 | } else if (action.equals("useSystemWebViewAtTheNextStartup")) {
45 | SharedPreferences sharedPreferences = this.cordova.getContext().getSharedPreferences("use_webview_engine",0);
46 | SharedPreferences.Editor editor = sharedPreferences.edit();
47 | editor.putString("WEBVIEW_ENGINE","SYSTEM");
48 | editor.commit();
49 | return true;
50 | }
51 | return false;
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/src/android/com/lifang123/cordova/webview/IonicCrosswalkWebViewEngine.java:
--------------------------------------------------------------------------------
1 | package com.lifang123.cordova.webview;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.content.pm.ApplicationInfo;
7 | import android.content.pm.PackageInfo;
8 | import android.annotation.TargetApi;
9 | import android.content.pm.PackageManager;
10 | import android.graphics.Bitmap;
11 | import android.net.Uri;
12 | import android.os.Build;
13 | import android.os.Bundle;
14 | import android.support.annotation.RequiresApi;
15 | import android.util.Log;
16 | import android.view.View;
17 | import android.webkit.ValueCallback;
18 | import android.webkit.WebResourceRequest;
19 | import android.webkit.WebResourceResponse;
20 | import android.webkit.WebSettings;
21 | import android.webkit.WebView;
22 |
23 | import com.ionicframework.cordova.webview.IonicWebViewEngine;
24 |
25 | import org.apache.cordova.ConfigXmlParser;
26 | import org.apache.cordova.CordovaInterface;
27 | import org.apache.cordova.CordovaPreferences;
28 | import org.apache.cordova.CordovaResourceApi;
29 | import org.apache.cordova.CordovaWebView;
30 | import org.apache.cordova.CordovaWebViewEngine;
31 | import org.apache.cordova.ICordovaCookieManager;
32 | import org.apache.cordova.NativeToJsMessageQueue;
33 | import org.apache.cordova.PluginManager;
34 | import org.apache.cordova.engine.SystemWebViewClient;
35 | import org.apache.cordova.engine.SystemWebViewEngine;
36 | import org.apache.cordova.engine.SystemWebView;
37 |
38 | public class IonicCrosswalkWebViewEngine implements CordovaWebViewEngine {
39 |
40 | public static final String TAG = "IonicWebViewEngine";
41 |
42 | public IonicWebViewEngine ionicWebViewEngine;
43 | public CrosswalkWebViewEngine crosswalkWebViewEngine;
44 | public boolean isUseCrosswalkWebView;
45 |
46 | public IonicCrosswalkWebViewEngine(Context context, CordovaPreferences preferences) {
47 | try {
48 | if (android.os.Build.VERSION.SDK_INT >= 24) {
49 | isUseCrosswalkWebView = false;
50 | } else {
51 | isUseCrosswalkWebView = true;
52 | }
53 | SharedPreferences sharedPreferences = context.getSharedPreferences("use_webview_engine", 0);
54 | String useWebViewEngine = sharedPreferences.getString("WEBVIEW_ENGINE", "AUTO");
55 | ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), context.getPackageManager().GET_META_DATA);
56 | Bundle bundle = appInfo.metaData;
57 | String action = bundle.getString("WEBVIEW_ENGINE");
58 | Log.i(TAG, "meta-data:" + action);
59 | if (action.equals("CROSSWALK")) {
60 | isUseCrosswalkWebView = true;
61 | } else {
62 | if (action.equals("SYSTEM")) {
63 | isUseCrosswalkWebView = false;
64 | }
65 | }
66 |
67 | if (useWebViewEngine != null && useWebViewEngine.equals("CROSSWALK")) {
68 | isUseCrosswalkWebView = true;
69 | } else {
70 | if (useWebViewEngine != null && useWebViewEngine.equals("SYSTEM")) {
71 | isUseCrosswalkWebView = false;
72 | }
73 | }
74 | } catch (PackageManager.NameNotFoundException e) {
75 | e.printStackTrace();
76 | }
77 |
78 |
79 | if (isUseCrosswalkWebView) {
80 | crosswalkWebViewEngine = new CrosswalkWebViewEngine(context, preferences);
81 | } else {
82 | ionicWebViewEngine = new IonicWebViewEngine(context, preferences);
83 | }
84 |
85 | Log.d(TAG, "Ionic Web View Engine Starting Right Up 1...");
86 | }
87 |
88 | @Override
89 | public void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
90 | CordovaResourceApi resourceApi, PluginManager pluginManager,
91 | NativeToJsMessageQueue nativeToJsMessageQueue) {
92 | if (isUseCrosswalkWebView) {
93 | crosswalkWebViewEngine.init(parentWebView, cordova, client, resourceApi, pluginManager,
94 | nativeToJsMessageQueue);
95 | } else {
96 | ionicWebViewEngine.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue);
97 | }
98 | }
99 |
100 | @Override
101 | public CordovaWebView getCordovaWebView() {
102 | if (isUseCrosswalkWebView) {
103 | return crosswalkWebViewEngine.getCordovaWebView();
104 | } else {
105 | return ionicWebViewEngine.getCordovaWebView();
106 | }
107 | }
108 |
109 | @Override
110 | public ICordovaCookieManager getCookieManager() {
111 | if (isUseCrosswalkWebView) {
112 | return crosswalkWebViewEngine.getCookieManager();
113 | } else {
114 | return ionicWebViewEngine.getCookieManager();
115 | }
116 | }
117 |
118 | @Override
119 | public View getView() {
120 | if (isUseCrosswalkWebView) {
121 | return crosswalkWebViewEngine.getView();
122 | } else {
123 | return ionicWebViewEngine.getView();
124 | }
125 | }
126 |
127 | @Override
128 | public void loadUrl(String url, boolean clearNavigationStack) {
129 | if (isUseCrosswalkWebView) {
130 | crosswalkWebViewEngine.loadUrl(url, clearNavigationStack);
131 | } else {
132 | ionicWebViewEngine.loadUrl(url, clearNavigationStack);
133 | }
134 | }
135 |
136 | @Override
137 | public void stopLoading() {
138 | if (isUseCrosswalkWebView) {
139 | crosswalkWebViewEngine.stopLoading();
140 | } else {
141 | ionicWebViewEngine.stopLoading();
142 | }
143 | }
144 |
145 | @Override
146 | public String getUrl() {
147 | if (isUseCrosswalkWebView) {
148 | return crosswalkWebViewEngine.getUrl();
149 | } else {
150 | return ionicWebViewEngine.getUrl();
151 | }
152 | }
153 |
154 | @Override
155 | public void clearCache() {
156 | if (isUseCrosswalkWebView) {
157 | crosswalkWebViewEngine.clearCache();
158 | } else {
159 | ionicWebViewEngine.clearCache();
160 | }
161 | }
162 |
163 | @Override
164 | public void clearHistory() {
165 | if (isUseCrosswalkWebView) {
166 | crosswalkWebViewEngine.clearHistory();
167 | } else {
168 | ionicWebViewEngine.clearHistory();
169 | }
170 | }
171 |
172 | @Override
173 | public boolean canGoBack() {
174 | if (isUseCrosswalkWebView) {
175 | return crosswalkWebViewEngine.canGoBack();
176 | } else {
177 | return ionicWebViewEngine.canGoBack();
178 | }
179 | }
180 |
181 | @Override
182 | public boolean goBack() {
183 | if (isUseCrosswalkWebView) {
184 | return crosswalkWebViewEngine.goBack();
185 | } else {
186 | return ionicWebViewEngine.goBack();
187 | }
188 | }
189 |
190 | @Override
191 | public void setPaused(boolean value) {
192 | if (isUseCrosswalkWebView) {
193 | crosswalkWebViewEngine.setPaused(value);
194 | } else {
195 | ionicWebViewEngine.setPaused(value);
196 | }
197 | }
198 |
199 | @Override
200 | public void destroy() {
201 | if (isUseCrosswalkWebView) {
202 | crosswalkWebViewEngine.destroy();
203 | } else {
204 | ionicWebViewEngine.destroy();
205 | }
206 | }
207 |
208 | @Override
209 | public void evaluateJavascript(String js, ValueCallback callback) {
210 | if (isUseCrosswalkWebView) {
211 | crosswalkWebViewEngine.evaluateJavascript(js, callback);
212 | } else {
213 | ionicWebViewEngine.evaluateJavascript(js, callback);
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkCordovaClientCertRequest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 | package org.crosswalk.engine;
20 |
21 | import org.apache.cordova.ICordovaClientCertRequest;
22 | import org.xwalk.core.ClientCertRequest;
23 |
24 | import java.security.cert.X509Certificate;
25 | import java.security.Principal;
26 | import java.security.PrivateKey;
27 | import java.util.Arrays;
28 |
29 | public class XWalkCordovaClientCertRequest implements ICordovaClientCertRequest {
30 |
31 | private final ClientCertRequest request;
32 |
33 | public XWalkCordovaClientCertRequest(ClientCertRequest request) {
34 | this.request = request;
35 | }
36 |
37 | /**
38 | * Cancel this request
39 | */
40 | public void cancel() {
41 | request.cancel();
42 | }
43 |
44 | /*
45 | * Returns the host name of the server requesting the certificate.
46 | */
47 | public String getHost() {
48 | return request.getHost();
49 | }
50 |
51 | /*
52 | * Returns the acceptable types of asymmetric keys (can be null).
53 | */
54 | public String[] getKeyTypes() {
55 | return request.getKeyTypes();
56 | }
57 |
58 | /*
59 | * Returns the port number of the server requesting the certificate.
60 | */
61 | public int getPort() {
62 | return request.getPort();
63 | }
64 |
65 | /*
66 | * Returns the acceptable certificate issuers for the certificate matching the private
67 | * key (can be null).
68 | */
69 | public Principal[] getPrincipals() {
70 | return request.getPrincipals();
71 | }
72 |
73 | /*
74 | * Ignore the request for now. Do not remember user's choice.
75 | */
76 | public void ignore() {
77 | request.ignore();
78 | }
79 |
80 | /*
81 | * Proceed with the specified private key and client certificate chain. Remember the user's
82 | * positive choice and use it for future requests.
83 | *
84 | * @param privateKey The privateKey
85 | * @param chain The certificate chain
86 | */
87 | public void proceed(PrivateKey privateKey, X509Certificate[] chain) {
88 | request.proceed(privateKey, Arrays.asList(chain));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkCordovaCookieManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 | package org.crosswalk.engine;
20 |
21 | import org.apache.cordova.ICordovaCookieManager;
22 | import org.xwalk.core.XWalkCookieManager;
23 |
24 | class XWalkCordovaCookieManager implements ICordovaCookieManager {
25 |
26 | protected XWalkCookieManager cookieManager = null;
27 |
28 | public XWalkCordovaCookieManager() {
29 | cookieManager = new XWalkCookieManager();
30 | }
31 |
32 | public void setCookiesEnabled(boolean accept) {
33 | cookieManager.setAcceptCookie(accept);
34 | }
35 |
36 | public void setCookie(final String url, final String value) {
37 | cookieManager.setCookie(url, value);
38 | }
39 |
40 | public String getCookie(final String url) {
41 | return cookieManager.getCookie(url);
42 | }
43 |
44 | public void clearCookies() {
45 | cookieManager.removeAllCookie();
46 | }
47 |
48 | public void flush() {
49 | cookieManager.flushCookieStore();
50 | }
51 | };
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkCordovaHttpAuthHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 | package org.crosswalk.engine;
20 |
21 | import org.apache.cordova.ICordovaHttpAuthHandler;
22 | import org.xwalk.core.XWalkHttpAuthHandler;
23 |
24 | /**
25 | * Specifies interface for HTTP auth handler object which is used to handle auth requests and
26 | * specifying user credentials.
27 | */
28 | public class XWalkCordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
29 |
30 | private final XWalkHttpAuthHandler handler;
31 |
32 | public XWalkCordovaHttpAuthHandler(XWalkHttpAuthHandler handler) {
33 | this.handler = handler;
34 | }
35 |
36 | /**
37 | * Instructs the XWalkView to cancel the authentication request.
38 | */
39 | public void cancel() {
40 | handler.cancel();
41 | }
42 |
43 | /**
44 | * Instructs the XWalkView to proceed with the authentication with the given credentials.
45 | *
46 | * @param username
47 | * @param password
48 | */
49 | public void proceed(String username, String password) {
50 | handler.proceed(username, password);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkCordovaResourceClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 | package org.crosswalk.engine;
20 |
21 | import android.content.pm.ApplicationInfo;
22 | import android.content.pm.PackageManager;
23 | import android.net.Uri;
24 | import android.net.http.SslError;
25 | import android.webkit.ValueCallback;
26 | import android.webkit.WebResourceResponse;
27 |
28 | import org.apache.cordova.CordovaResourceApi;
29 | import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
30 | import org.apache.cordova.LOG;
31 | import org.apache.cordova.PluginManager;
32 | import org.xwalk.core.ClientCertRequest;
33 | import org.xwalk.core.XWalkHttpAuthHandler;
34 | import org.xwalk.core.XWalkResourceClient;
35 | import org.xwalk.core.XWalkView;
36 |
37 | import java.io.FileNotFoundException;
38 | import java.io.IOException;
39 |
40 | public class XWalkCordovaResourceClient extends XWalkResourceClient {
41 |
42 | private static final String TAG = "XWalkCordovaResourceClient";
43 | protected XWalkWebViewEngine parentEngine;
44 |
45 | public XWalkCordovaResourceClient(XWalkWebViewEngine parentEngine) {
46 | super(parentEngine.webView);
47 | this.parentEngine = parentEngine;
48 | }
49 |
50 | /**
51 | * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
52 | * The errorCode parameter corresponds to one of the ERROR_* constants.
53 | *
54 | * @param view The WebView that is initiating the callback.
55 | * @param errorCode The error code corresponding to an ERROR_* value.
56 | * @param description A String describing the error.
57 | * @param failingUrl The url that failed to load.
58 | */
59 | @Override
60 | public void onReceivedLoadError(XWalkView view, int errorCode, String description,
61 | String failingUrl) {
62 | LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
63 |
64 | parentEngine.client.onReceivedError(errorCode, description, failingUrl);
65 | }
66 |
67 | @Override
68 | public WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
69 | try {
70 | // Check the against the white-list.
71 | if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
72 | LOG.w(TAG, "URL blocked by whitelist: " + url);
73 | // Results in a 404.
74 | return new WebResourceResponse("text/plain", "UTF-8", null);
75 | }
76 |
77 | CordovaResourceApi resourceApi = parentEngine.resourceApi;
78 | Uri origUri = Uri.parse(url);
79 | // Allow plugins to intercept WebView requests.
80 | Uri remappedUri = resourceApi.remapUri(origUri);
81 |
82 | if (!origUri.equals(remappedUri)) {
83 | OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
84 | return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
85 | }
86 | // If we don't need to special-case the request, let the browser load it.
87 | return null;
88 | } catch (IOException e) {
89 | if (!(e instanceof FileNotFoundException)) {
90 | LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
91 | }
92 | // Results in a 404.
93 | return new WebResourceResponse("text/plain", "UTF-8", null);
94 | }
95 | }
96 |
97 | @Override
98 | public boolean shouldOverrideUrlLoading(XWalkView view, String url) {
99 | return parentEngine.client.onNavigationAttempt(url);
100 | }
101 |
102 |
103 | /**
104 | * Notify the host application that an SSL error occurred while loading a
105 | * resource. The host application must call either callback.onReceiveValue(true)
106 | * or callback.onReceiveValue(false). Note that the decision may be
107 | * retained for use in response to future SSL errors. The default behavior
108 | * is to pop up a dialog.
109 | */
110 | @Override
111 | public void onReceivedSslError(XWalkView view, ValueCallback callback, SslError error) {
112 | final String packageName = parentEngine.cordova.getActivity().getPackageName();
113 | final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
114 |
115 | ApplicationInfo appInfo;
116 | try {
117 | appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
118 | if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
119 | // debug = true
120 | callback.onReceiveValue(true);
121 | } else {
122 | // debug = false
123 | callback.onReceiveValue(false);
124 | }
125 | } catch (PackageManager.NameNotFoundException e) {
126 | // When it doubt, lock it out!
127 | callback.onReceiveValue(false);
128 | }
129 | }
130 |
131 | @Override
132 | public void onReceivedHttpAuthRequest(XWalkView view, XWalkHttpAuthHandler handler,
133 | String host, String realm) {
134 | // Check if there is some plugin which can resolve this auth challenge
135 | PluginManager pluginManager = parentEngine.pluginManager;
136 | if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(
137 | parentEngine.parentWebView,
138 | new XWalkCordovaHttpAuthHandler(handler), host, realm)) {
139 | parentEngine.client.clearLoadTimeoutTimer();
140 | return;
141 | }
142 |
143 | // By default handle 401 like we'd normally do!
144 | super.onReceivedHttpAuthRequest(view, handler, host, realm);
145 | }
146 |
147 | @Override
148 | public void onReceivedClientCertRequest(XWalkView view, ClientCertRequest request) {
149 | // Check if there is some plugin which can resolve this certificate request
150 | PluginManager pluginManager = parentEngine.pluginManager;
151 | if (pluginManager != null && pluginManager.onReceivedClientCertRequest(
152 | parentEngine.parentWebView, new XWalkCordovaClientCertRequest(request))) {
153 | parentEngine.client.clearLoadTimeoutTimer();
154 | return;
155 | }
156 |
157 | super.onReceivedClientCertRequest(view, request);
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkCordovaUiClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 | package org.crosswalk.engine;
20 |
21 | import android.app.Activity;
22 | import android.content.ActivityNotFoundException;
23 | import android.content.Intent;
24 | import android.net.Uri;
25 | import android.util.Log;
26 | import android.webkit.ValueCallback;
27 |
28 | import org.apache.cordova.CordovaDialogsHelper;
29 | import org.apache.cordova.CordovaPlugin;
30 | import org.apache.cordova.LOG;
31 | import org.xwalk.core.XWalkJavascriptResult;
32 | import org.xwalk.core.XWalkUIClient;
33 | import org.xwalk.core.XWalkView;
34 |
35 | import org.crosswalk.engine.XWalkWebViewEngine.PermissionRequestListener;
36 |
37 | public class XWalkCordovaUiClient extends XWalkUIClient {
38 | private static final String TAG = "XWalkCordovaUiClient";
39 | protected final CordovaDialogsHelper dialogsHelper;
40 | protected final XWalkWebViewEngine parentEngine;
41 |
42 | private XWalkFileChooser mFileChooser;
43 | private CordovaPlugin mFileChooserResultPlugin;
44 |
45 | private static final int FILECHOOSER_RESULTCODE = 5173;
46 |
47 | public XWalkCordovaUiClient(XWalkWebViewEngine parentEngine) {
48 | super(parentEngine.webView);
49 | this.parentEngine = parentEngine;
50 | dialogsHelper = new CordovaDialogsHelper(parentEngine.webView.getContext());
51 | }
52 |
53 | @Override
54 | public boolean onJavascriptModalDialog(XWalkView view, JavascriptMessageType type, String url,
55 | String message, String defaultValue, XWalkJavascriptResult result) {
56 | switch (type) {
57 | case JAVASCRIPT_ALERT:
58 | return onJsAlert(view, url, message, result);
59 | case JAVASCRIPT_CONFIRM:
60 | return onJsConfirm(view, url, message, result);
61 | case JAVASCRIPT_PROMPT:
62 | return onJsPrompt(view, url, message, defaultValue, result);
63 | case JAVASCRIPT_BEFOREUNLOAD:
64 | // Reuse onJsConfirm to show the dialog.
65 | return onJsConfirm(view, url, message, result);
66 | default:
67 | break;
68 | }
69 | assert (false);
70 | return false;
71 | }
72 |
73 | /**
74 | * Tell the client to display a javascript alert dialog.
75 | */
76 | public boolean onJsAlert(XWalkView view, String url, String message,
77 | final XWalkJavascriptResult result) {
78 | dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
79 | @Override
80 | public void gotResult(boolean success, String value) {
81 | if (success) {
82 | result.confirm();
83 | } else {
84 | result.cancel();
85 | }
86 | }
87 | });
88 | return true;
89 | }
90 |
91 | /**
92 | * Tell the client to display a confirm dialog to the user.
93 | */
94 | public boolean onJsConfirm(XWalkView view, String url, String message,
95 | final XWalkJavascriptResult result) {
96 | dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
97 | @Override
98 | public void gotResult(boolean success, String value) {
99 | if (success) {
100 | result.confirm();
101 | } else {
102 | result.cancel();
103 | }
104 | }
105 | });
106 | return true;
107 | }
108 |
109 | /**
110 | * Tell the client to display a prompt dialog to the user.
111 | * If the client returns true, WebView will assume that the client will
112 | * handle the prompt dialog and call the appropriate JsPromptResult method.
113 | *
114 | * Since we are hacking prompts for our own purposes, we should not be using them for
115 | * this purpose, perhaps we should hack console.log to do this instead!
116 | */
117 | public boolean onJsPrompt(XWalkView view, String origin, String message, String defaultValue,
118 | final XWalkJavascriptResult result) {
119 | // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
120 | String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
121 | if (handledRet != null) {
122 | result.confirmWithResult(handledRet);
123 | } else {
124 | dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
125 | @Override
126 | public void gotResult(boolean success, String value) {
127 | if (success) {
128 | result.confirmWithResult(value);
129 | } else {
130 | result.cancel();
131 | }
132 | }
133 | });
134 |
135 | }
136 | return true;
137 | }
138 |
139 | /**
140 | * Notify the host application that a page has started loading.
141 | * This method is called once for each main frame load so a page with iframes or framesets will call onPageLoadStarted
142 | * one time for the main frame. This also means that onPageLoadStarted will not be called when the contents of an
143 | * embedded frame changes, i.e. clicking a link whose target is an iframe.
144 | *
145 | * @param view The webView initiating the callback.
146 | * @param url The url of the page.
147 | */
148 | @Override
149 | public void onPageLoadStarted(XWalkView view, String url) {
150 | LOG.d(TAG, "onPageLoadStarted(" + url + ")");
151 | if (view.getUrl() != null) {
152 | // Flush stale messages.
153 | parentEngine.client.onPageStarted(url);
154 | parentEngine.bridge.reset();
155 | }
156 | }
157 |
158 | /**
159 | * Notify the host application that a page has stopped loading.
160 | * This method is called only for main frame. When onPageLoadStopped() is called, the rendering picture may not be updated yet.
161 | *
162 | * @param view The webView initiating the callback.
163 | * @param url The url of the page.
164 | * @param status The load status of the webView, can be FINISHED, CANCELLED or FAILED.
165 | */
166 | @Override
167 | public void onPageLoadStopped(XWalkView view, String url, LoadStatus status) {
168 | LOG.d(TAG, "onPageLoadStopped(" + url + ")");
169 | if (status == LoadStatus.FINISHED) {
170 | parentEngine.client.onPageFinishedLoading(url);
171 | } else if (status == LoadStatus.FAILED) {
172 | // TODO: Should this call parentEngine.client.onReceivedError()?
173 | // Right now we call this from ResourceClient, but maybe that is just for sub-resources?
174 | }
175 | }
176 |
177 | // File Chooser
178 | @Override
179 | public void openFileChooser(XWalkView view, final ValueCallback uploadFile,
180 | final String acceptType, final String capture) {
181 | if (mFileChooser == null) {
182 | mFileChooser = new XWalkFileChooser(parentEngine.cordova.getActivity());
183 | mFileChooserResultPlugin = new CordovaPlugin() {
184 | @Override
185 | public void onActivityResult(int requestCode, int resultCode, Intent intent) {
186 | mFileChooser.onActivityResult(requestCode, resultCode, intent);
187 | }
188 | };
189 | }
190 |
191 | PermissionRequestListener listener = new PermissionRequestListener() {
192 | @Override
193 | public void onRequestPermissionResult(int requestCode, String[] permissions,
194 | int[] grantResults) {
195 | for (int i = 0; i < permissions.length; ++i) {
196 | Log.d(TAG, "permission:" + permissions[i] + " result:" + grantResults[i]);
197 | }
198 | parentEngine.cordova.setActivityResultCallback(mFileChooserResultPlugin);
199 | mFileChooser.showFileChooser(uploadFile, acceptType, capture);
200 | }
201 | };
202 |
203 | if (!parentEngine.requestPermissionsForFileChooser(listener)) {
204 | parentEngine.cordova.setActivityResultCallback(mFileChooserResultPlugin);
205 | mFileChooser.showFileChooser(uploadFile, acceptType, capture);
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkCordovaView.java:
--------------------------------------------------------------------------------
1 | package org.crosswalk.engine;
2 |
3 | import org.apache.cordova.CordovaPreferences;
4 | import org.xwalk.core.XWalkPreferences;
5 | import org.xwalk.core.XWalkResourceClient;
6 | import org.xwalk.core.XWalkUIClient;
7 | import org.xwalk.core.XWalkView;
8 |
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.content.pm.ApplicationInfo;
12 | import android.content.pm.PackageManager;
13 | import android.os.Bundle;
14 | import android.util.AttributeSet;
15 | import android.util.Log;
16 | import android.view.KeyEvent;
17 |
18 | import org.apache.cordova.CordovaPlugin;
19 | import org.apache.cordova.CordovaWebView;
20 | import org.apache.cordova.CordovaWebViewEngine;
21 |
22 | public class XWalkCordovaView extends XWalkView implements CordovaWebViewEngine.EngineView {
23 |
24 | public static final String TAG = "XWalkCordovaView";
25 |
26 | protected XWalkCordovaResourceClient resourceClient;
27 | protected XWalkCordovaUiClient uiClient;
28 | protected XWalkWebViewEngine parentEngine;
29 |
30 | private static boolean hasSetStaticPref;
31 | // This needs to run before the super's constructor.
32 | private static Context setGlobalPrefs(Context context, CordovaPreferences preferences) {
33 | if (!hasSetStaticPref) {
34 | hasSetStaticPref = true;
35 | ApplicationInfo ai = null;
36 | try {
37 | ai = context.getPackageManager().getApplicationInfo(context.getApplicationContext().getPackageName(), PackageManager.GET_META_DATA);
38 | } catch (PackageManager.NameNotFoundException e) {
39 | throw new RuntimeException(e);
40 | }
41 | boolean prefAnimatable = preferences == null ? false : preferences.getBoolean("CrosswalkAnimatable", false);
42 | boolean manifestAnimatable = ai.metaData == null ? false : ai.metaData.getBoolean("CrosswalkAnimatable");
43 | // Selects between a TextureView (obeys framework transforms applied to view) or a SurfaceView (better performance).
44 | XWalkPreferences.setValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW, prefAnimatable || manifestAnimatable);
45 | if ((ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
46 | XWalkPreferences.setValue(XWalkPreferences.REMOTE_DEBUGGING, true);
47 | }
48 | XWalkPreferences.setValue(XWalkPreferences.JAVASCRIPT_CAN_OPEN_WINDOW, true);
49 | XWalkPreferences.setValue(XWalkPreferences.ALLOW_UNIVERSAL_ACCESS_FROM_FILE, true);
50 | }
51 | return context;
52 | }
53 |
54 | public XWalkCordovaView(Context context, CordovaPreferences preferences) {
55 | super(setGlobalPrefs(context, preferences), (AttributeSet)null);
56 | }
57 |
58 | public XWalkCordovaView(Context context, AttributeSet attrs) {
59 | super(setGlobalPrefs(context, null), attrs);
60 | }
61 |
62 | void init(XWalkWebViewEngine parentEngine) {
63 | this.parentEngine = parentEngine;
64 | if (resourceClient == null) {
65 | setResourceClient(new XWalkCordovaResourceClient(parentEngine));
66 | }
67 | if (uiClient == null) {
68 | setUIClient(new XWalkCordovaUiClient(parentEngine));
69 | }
70 | }
71 |
72 | @Override
73 | public void setResourceClient(XWalkResourceClient client) {
74 | // XWalk calls this method from its constructor.
75 | if (client instanceof XWalkCordovaResourceClient) {
76 | this.resourceClient = (XWalkCordovaResourceClient)client;
77 | }
78 | super.setResourceClient(client);
79 | }
80 |
81 | @Override
82 | public void setUIClient(XWalkUIClient client) {
83 | // XWalk calls this method from its constructor.
84 | if (client instanceof XWalkCordovaUiClient) {
85 | this.uiClient = (XWalkCordovaUiClient)client;
86 | }
87 | super.setUIClient(client);
88 | }
89 |
90 | // Call CordovaInterface to start activity for result to make sure
91 | // onActivityResult() callback will be triggered from CordovaActivity correctly.
92 | // Todo(leonhsl) How to handle |options|?
93 | @Override
94 | public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
95 | parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
96 | @Override
97 | public void onActivityResult(int requestCode, int resultCode, Intent intent) {
98 | // Route to XWalkView.
99 | Log.i(TAG, "Route onActivityResult() to XWalkView");
100 | XWalkCordovaView.this.onActivityResult(requestCode, resultCode, intent);
101 | }
102 | }, intent, requestCode);
103 | }
104 |
105 | @Override
106 | public boolean dispatchKeyEvent(KeyEvent event) {
107 | Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
108 | if (ret != null) {
109 | return ret.booleanValue();
110 | }
111 | return super.dispatchKeyEvent(event);
112 | }
113 |
114 | @Override
115 | public void pauseTimers() {
116 | // This is called by XWalkViewInternal.onActivityStateChange().
117 | // We don't want them paused by default though.
118 | }
119 |
120 | public void pauseTimersForReal() {
121 | super.pauseTimers();
122 | }
123 |
124 | @Override
125 | public CordovaWebView getCordovaWebView() {
126 | return parentEngine == null ? null : parentEngine.getCordovaWebView();
127 | }
128 |
129 | @Override
130 | public void setBackgroundColor(int color) {
131 | if (parentEngine != null && parentEngine.isXWalkReady()) {
132 | super.setBackgroundColor(color);
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkExposedJsApi.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 | package org.crosswalk.engine;
20 |
21 | import android.os.Looper;
22 |
23 | import org.apache.cordova.CordovaBridge;
24 | import org.apache.cordova.ExposedJsApi;
25 | import org.json.JSONException;
26 | import org.xwalk.core.JavascriptInterface;
27 |
28 | class XWalkExposedJsApi implements ExposedJsApi {
29 | private final CordovaBridge bridge;
30 |
31 | XWalkExposedJsApi(CordovaBridge bridge) {
32 | this.bridge = bridge;
33 | }
34 |
35 | @JavascriptInterface
36 | public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
37 | if (Looper.myLooper() == null) {
38 | Looper.prepare();
39 | }
40 | return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
41 | }
42 |
43 | @JavascriptInterface
44 | public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
45 | bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
46 | }
47 |
48 | @JavascriptInterface
49 | public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
50 | return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkFileChooser.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 |
21 | package org.crosswalk.engine;
22 |
23 | import android.app.Activity;
24 | import android.content.Intent;
25 | import android.content.pm.PackageInfo;
26 | import android.content.pm.PackageManager;
27 | import android.content.pm.PackageManager.NameNotFoundException;
28 | import android.Manifest;
29 | import android.net.Uri;
30 | import android.os.Environment;
31 | import android.provider.MediaStore;
32 | import android.util.Log;
33 | import android.webkit.ValueCallback;
34 |
35 | import java.io.File;
36 | import java.io.IOException;
37 | import java.text.SimpleDateFormat;
38 | import java.util.ArrayList;
39 | import java.util.Arrays;
40 | import java.util.Date;
41 |
42 | public class XWalkFileChooser {
43 | private static final String IMAGE_TYPE = "image/";
44 | private static final String VIDEO_TYPE = "video/";
45 | private static final String AUDIO_TYPE = "audio/";
46 | private static final String ALL_IMAGE_TYPES = IMAGE_TYPE + "*";
47 | private static final String ALL_VIDEO_TYPES = VIDEO_TYPE + "*";
48 | private static final String ALL_AUDIO_TYPES = AUDIO_TYPE + "*";
49 | private static final String ANY_TYPES = "*/*";
50 | private static final String SPLIT_EXPRESSION = ",";
51 | private static final String PATH_PREFIX = "file:";
52 | private static final String WRITE_EXTERNAL_STORAGE= "android.permission.WRITE_EXTERNAL_STORAGE";
53 |
54 | public static final int INPUT_FILE_REQUEST_CODE = 1;
55 |
56 | private static final String TAG = "XWalkFileChooser";
57 |
58 | private Activity mActivity;
59 | private ValueCallback mFilePathCallback;
60 | private String mCameraPhotoPath;
61 |
62 | public XWalkFileChooser(Activity activity) {
63 | mActivity = activity;
64 | }
65 |
66 | public boolean showFileChooser(ValueCallback uploadFile, String acceptType,
67 | String capture) {
68 | mFilePathCallback = uploadFile;
69 |
70 | Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
71 | if (takePictureIntent.resolveActivity(mActivity.getPackageManager()) != null) {
72 | // Create the File where the photo should go
73 | File photoFile = createImageFile();
74 | // Continue only if the File was successfully created
75 | if (photoFile != null) {
76 | mCameraPhotoPath = PATH_PREFIX + photoFile.getAbsolutePath();
77 | takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
78 | takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
79 | } else {
80 | takePictureIntent = null;
81 | }
82 | }
83 |
84 | Intent camcorder = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
85 | Intent soundRecorder = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
86 | Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
87 | contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
88 | ArrayList extraIntents = new ArrayList();
89 |
90 | // A single mime type.
91 | if (!(acceptType.contains(SPLIT_EXPRESSION) || acceptType.contains(ANY_TYPES))) {
92 | if (capture.equals("true")) {
93 | if (acceptType.startsWith(IMAGE_TYPE)) {
94 | if (takePictureIntent != null) {
95 | mActivity.startActivityForResult(takePictureIntent, INPUT_FILE_REQUEST_CODE);
96 | Log.d(TAG, "Started taking picture");
97 | return true;
98 | }
99 | } else if (acceptType.startsWith(VIDEO_TYPE)) {
100 | mActivity.startActivityForResult(camcorder, INPUT_FILE_REQUEST_CODE);
101 | Log.d(TAG, "Started camcorder");
102 | return true;
103 | } else if (acceptType.startsWith(AUDIO_TYPE)) {
104 | mActivity.startActivityForResult(soundRecorder, INPUT_FILE_REQUEST_CODE);
105 | Log.d(TAG, "Started sound recorder");
106 | return true;
107 | }
108 | } else {
109 | if (acceptType.startsWith(IMAGE_TYPE)) {
110 | if (takePictureIntent != null) {
111 | extraIntents.add(takePictureIntent);
112 | }
113 | contentSelectionIntent.setType(ALL_IMAGE_TYPES);
114 | } else if (acceptType.startsWith(VIDEO_TYPE)) {
115 | extraIntents.add(camcorder);
116 | contentSelectionIntent.setType(ALL_VIDEO_TYPES);
117 | } else if (acceptType.startsWith(AUDIO_TYPE)) {
118 | extraIntents.add(soundRecorder);
119 | contentSelectionIntent.setType(ALL_AUDIO_TYPES);
120 | }
121 | }
122 | }
123 |
124 | // Couldn't resolve an accept type.
125 | if (extraIntents.isEmpty() && canWriteExternalStorage()) {
126 | if (takePictureIntent != null) {
127 | extraIntents.add(takePictureIntent);
128 | }
129 | extraIntents.add(camcorder);
130 | extraIntents.add(soundRecorder);
131 | contentSelectionIntent.setType(ANY_TYPES);
132 | }
133 |
134 | Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
135 | chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
136 | if (!extraIntents.isEmpty()) {
137 | chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
138 | extraIntents.toArray(new Intent[] { }));
139 | }
140 | mActivity.startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
141 | Log.d(TAG, "Started chooser");
142 | return true;
143 | }
144 |
145 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
146 | if(requestCode == INPUT_FILE_REQUEST_CODE && mFilePathCallback != null) {
147 | Log.d(TAG, "Activity result: " + resultCode);
148 | Uri results = null;
149 |
150 | // Check that the response is a good one
151 | if(Activity.RESULT_OK == resultCode) {
152 | // In Android M, camera results return an empty Intent rather than null.
153 | if(data == null || (data.getAction() == null && data.getData() == null)) {
154 | // If there is not data, then we may have taken a photo
155 | if(mCameraPhotoPath != null) {
156 | results = Uri.parse(mCameraPhotoPath);
157 | }
158 | } else {
159 | String dataString = data.getDataString();
160 | if (dataString != null) {
161 | results = Uri.parse(dataString);
162 | }
163 | deleteImageFile();
164 | }
165 | } else if (Activity.RESULT_CANCELED == resultCode) {
166 | deleteImageFile();
167 | }
168 |
169 | if (results != null) {
170 | Log.d(TAG, "Received file: " + results.toString());
171 | }
172 | mFilePathCallback.onReceiveValue(results);
173 | mFilePathCallback = null;
174 | }
175 | }
176 |
177 | private boolean canWriteExternalStorage() {
178 | try {
179 | PackageManager packageManager = mActivity.getPackageManager();
180 | PackageInfo packageInfo = packageManager.getPackageInfo(
181 | mActivity.getPackageName(), PackageManager.GET_PERMISSIONS);
182 | return Arrays.asList(packageInfo.requestedPermissions).contains(WRITE_EXTERNAL_STORAGE);
183 | } catch (NameNotFoundException e) {
184 | return false;
185 | } catch (NullPointerException e) {
186 | return false;
187 | }
188 | }
189 |
190 | private File createImageFile() {
191 | // FIXME: If the external storage state is not "MEDIA_MOUNTED", we need to get
192 | // other volume paths by "getVolumePaths()" when it was exposed.
193 | String state = Environment.getExternalStorageState();
194 | if (!state.equals(Environment.MEDIA_MOUNTED)) {
195 | Log.e(TAG, "External storage is not mounted.");
196 | return null;
197 | }
198 |
199 | // Create an image file name
200 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
201 | String imageFileName = "JPEG_" + timeStamp + "_";
202 | File storageDir = Environment.getExternalStoragePublicDirectory(
203 | Environment.DIRECTORY_PICTURES);
204 | if (!storageDir.exists()) {
205 | storageDir.mkdirs();
206 | }
207 |
208 | try {
209 | File file = File.createTempFile(imageFileName, ".jpg", storageDir);
210 | Log.d(TAG, "Created image file: " + file.getAbsolutePath());
211 | return file;
212 | } catch (IOException e) {
213 | // Error occurred while creating the File
214 | Log.e(TAG, "Unable to create Image File, " +
215 | "please make sure permission 'WRITE_EXTERNAL_STORAGE' was added.");
216 | return null;
217 | }
218 | }
219 |
220 | private boolean deleteImageFile() {
221 | if (mCameraPhotoPath == null || !mCameraPhotoPath.contains(PATH_PREFIX)) {
222 | return false;
223 | }
224 | String filePath = mCameraPhotoPath.split(PATH_PREFIX)[1];
225 | boolean result = new File(filePath).delete();
226 | Log.d(TAG, "Delete image file: " + filePath + " result: " + result);
227 | return result;
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/android/org/crosswalk/engine/XWalkWebViewEngine.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | package org.crosswalk.engine;
21 |
22 | import android.app.Activity;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.content.pm.PackageInfo;
26 | import android.content.pm.PackageManager;
27 | import android.content.pm.PackageManager.NameNotFoundException;
28 | import android.content.res.AssetManager;
29 | import android.graphics.Bitmap;
30 | import android.graphics.Color;
31 | import android.Manifest;
32 | import android.util.Log;
33 | import android.view.View;
34 | import android.webkit.ValueCallback;
35 |
36 | import java.io.File;
37 | import java.io.IOException;
38 | import java.util.ArrayList;
39 |
40 | import org.apache.cordova.CordovaBridge;
41 | import org.apache.cordova.CordovaInterface;
42 | import org.apache.cordova.CordovaPlugin;
43 | import org.apache.cordova.CordovaPreferences;
44 | import org.apache.cordova.CordovaResourceApi;
45 | import org.apache.cordova.CordovaWebView;
46 | import org.apache.cordova.CordovaWebViewEngine;
47 | import org.apache.cordova.ICordovaCookieManager;
48 | import org.apache.cordova.NativeToJsMessageQueue;
49 | import org.apache.cordova.PluginEntry;
50 | import org.apache.cordova.PluginManager;
51 | import org.xwalk.core.XWalkActivityDelegate;
52 | import org.xwalk.core.XWalkNavigationHistory;
53 | import org.xwalk.core.XWalkView;
54 | import org.xwalk.core.XWalkGetBitmapCallback;
55 |
56 | /**
57 | * Glue class between CordovaWebView (main Cordova logic) and XWalkCordovaView (the actual View).
58 | */
59 | public class XWalkWebViewEngine implements CordovaWebViewEngine {
60 |
61 | public static final String TAG = "XWalkWebViewEngine";
62 | public static final String XWALK_USER_AGENT = "xwalkUserAgent";
63 | public static final String XWALK_Z_ORDER_ON_TOP = "xwalkZOrderOnTop";
64 |
65 | private static final String XWALK_EXTENSIONS_FOLDER = "xwalk-extensions";
66 |
67 | private static final int PERMISSION_REQUEST_CODE = 100;
68 |
69 | protected final XWalkCordovaView webView;
70 | protected XWalkCordovaCookieManager cookieManager;
71 | protected CordovaBridge bridge;
72 | protected CordovaWebViewEngine.Client client;
73 | protected CordovaWebView parentWebView;
74 | protected CordovaInterface cordova;
75 | protected PluginManager pluginManager;
76 | protected CordovaResourceApi resourceApi;
77 | protected NativeToJsMessageQueue nativeToJsMessageQueue;
78 | protected XWalkActivityDelegate activityDelegate;
79 | protected String startUrl;
80 | protected CordovaPreferences preferences;
81 |
82 | /** Used when created via reflection. */
83 | public XWalkWebViewEngine(Context context, CordovaPreferences preferences) {
84 | this.preferences = preferences;
85 | Runnable cancelCommand = new Runnable() {
86 | @Override
87 | public void run() {
88 | cordova.getActivity().finish();
89 | }
90 | };
91 | Runnable completeCommand = new Runnable() {
92 | @Override
93 | public void run() {
94 | cookieManager = new XWalkCordovaCookieManager();
95 |
96 | initWebViewSettings();
97 | exposeJsInterface(webView, bridge);
98 | loadExtensions();
99 |
100 | CordovaPlugin notifPlugin = new CordovaPlugin() {
101 | @Override
102 | public void onNewIntent(Intent intent) {
103 | Log.i(TAG, "notifPlugin route onNewIntent() to XWalkView: " + intent.toString());
104 | XWalkWebViewEngine.this.webView.onNewIntent(intent);
105 | }
106 |
107 | @Override
108 | public Object onMessage(String id, Object data) {
109 | if (id.equals("captureXWalkBitmap")) {
110 | // Capture bitmap on UI thread.
111 | XWalkWebViewEngine.this.cordova.getActivity().runOnUiThread(new Runnable() {
112 | public void run() {
113 | XWalkWebViewEngine.this.webView.captureBitmapAsync(
114 | new XWalkGetBitmapCallback() {
115 | @Override
116 | public void onFinishGetBitmap(Bitmap bitmap,
117 | int response) {
118 | pluginManager.postMessage(
119 | "onGotXWalkBitmap", bitmap);
120 | }
121 | });
122 | }
123 | });
124 | }
125 | return null;
126 | }
127 | };
128 | pluginManager.addService(new PluginEntry("XWalkNotif", notifPlugin));
129 |
130 | // Send the massage of xwalk's ready to plugin.
131 | if (pluginManager != null) {
132 | pluginManager.postMessage("onXWalkReady", this);
133 | }
134 |
135 | if (startUrl != null) {
136 | webView.load(startUrl, null);
137 | }
138 | }
139 | };
140 | activityDelegate = new XWalkActivityDelegate((Activity) context, cancelCommand, completeCommand);
141 |
142 | webView = new XWalkCordovaView(context, preferences);
143 | }
144 |
145 | // Use two-phase init so that the control will work with XML layouts.
146 |
147 | @Override
148 | public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
149 | CordovaResourceApi resourceApi, PluginManager pluginManager,
150 | NativeToJsMessageQueue nativeToJsMessageQueue) {
151 | if (this.cordova != null) {
152 | throw new IllegalStateException();
153 | }
154 | this.parentWebView = parentWebView;
155 | this.cordova = cordova;
156 | this.client = client;
157 | this.resourceApi = resourceApi;
158 | this.pluginManager = pluginManager;
159 | this.nativeToJsMessageQueue = nativeToJsMessageQueue;
160 |
161 | CordovaPlugin activityDelegatePlugin = new CordovaPlugin() {
162 | @Override
163 | public void onResume(boolean multitasking) {
164 | activityDelegate.onResume();
165 | }
166 | };
167 | pluginManager.addService(new PluginEntry("XWalkActivityDelegate", activityDelegatePlugin));
168 |
169 | webView.init(this);
170 |
171 | nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(
172 | new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
173 | @Override
174 | public void setNetworkAvailable(boolean value) {
175 | webView.setNetworkAvailable(value);
176 | }
177 | @Override
178 | public void runOnUiThread(Runnable r) {
179 | XWalkWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
180 | }
181 | }));
182 | nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
183 | bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
184 | }
185 |
186 | @Override
187 | public CordovaWebView getCordovaWebView() {
188 | return parentWebView;
189 | }
190 |
191 | @Override
192 | public View getView() {
193 | return webView;
194 | }
195 |
196 | private void initWebViewSettings() {
197 | webView.setVerticalScrollBarEnabled(false);
198 |
199 | boolean zOrderOnTop = preferences == null ? false : preferences.getBoolean(XWALK_Z_ORDER_ON_TOP, false);
200 | webView.setZOrderOnTop(zOrderOnTop);
201 |
202 | // Set xwalk webview settings by Cordova preferences.
203 | String xwalkUserAgent = preferences == null ? "" : preferences.getString(XWALK_USER_AGENT, "");
204 | if (!xwalkUserAgent.isEmpty()) {
205 | webView.setUserAgentString(xwalkUserAgent);
206 | }
207 |
208 | String appendUserAgent = preferences.getString("AppendUserAgent", "");
209 | if (!appendUserAgent.isEmpty()) {
210 | webView.setUserAgentString(webView.getUserAgentString() + " " + appendUserAgent);
211 | }
212 |
213 | if (preferences.contains("BackgroundColor")) {
214 | int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
215 | webView.setBackgroundColor(backgroundColor);
216 | }
217 | }
218 |
219 | private static void exposeJsInterface(XWalkView webView, CordovaBridge bridge) {
220 | XWalkExposedJsApi exposedJsApi = new XWalkExposedJsApi(bridge);
221 | webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
222 | }
223 |
224 | private void loadExtensions() {
225 | AssetManager assetManager = cordova.getActivity().getAssets();
226 | String[] extList;
227 | try {
228 | Log.i(TAG, "Iterate assets/xwalk-extensions folder");
229 | extList = assetManager.list(XWALK_EXTENSIONS_FOLDER);
230 | } catch (IOException e) {
231 | Log.w(TAG, "Failed to iterate assets/xwalk-extensions folder");
232 | return;
233 | }
234 |
235 | for (String path : extList) {
236 | // Load the extension.
237 | Log.i(TAG, "Start to load extension: " + path);
238 | webView.getExtensionManager().loadExtension(XWALK_EXTENSIONS_FOLDER + File.separator + path);
239 | }
240 | }
241 |
242 | @Override
243 | public boolean canGoBack() {
244 | if (!activityDelegate.isXWalkReady()) return false;
245 | return this.webView.getNavigationHistory().canGoBack();
246 | }
247 |
248 | @Override
249 | public boolean goBack() {
250 | if (this.webView.getNavigationHistory().canGoBack()) {
251 | this.webView.getNavigationHistory().navigate(XWalkNavigationHistory.Direction.BACKWARD, 1);
252 | return true;
253 | }
254 | return false;
255 | }
256 |
257 | @Override
258 | public void setPaused(boolean value) {
259 | if (!activityDelegate.isXWalkReady()) return;
260 | if (value) {
261 | // TODO: I think this has been fixed upstream and we don't need to override pauseTimers() anymore.
262 | webView.pauseTimersForReal();
263 | } else {
264 | webView.resumeTimers();
265 | }
266 | }
267 |
268 | @Override
269 | public void destroy() {
270 | if (!activityDelegate.isXWalkReady()) return;
271 | webView.onDestroy();
272 | }
273 |
274 | @Override
275 | public void clearHistory() {
276 | if (!activityDelegate.isXWalkReady()) return;
277 | this.webView.getNavigationHistory().clear();
278 | }
279 |
280 | @Override
281 | public void stopLoading() {
282 | if (!activityDelegate.isXWalkReady()) return;
283 | this.webView.stopLoading();
284 | }
285 |
286 | @Override
287 | public void clearCache() {
288 | if (!activityDelegate.isXWalkReady()) return;
289 | webView.clearCache(true);
290 | }
291 |
292 | @Override
293 | public String getUrl() {
294 | if (!activityDelegate.isXWalkReady()) return null;
295 | return this.webView.getUrl();
296 | }
297 |
298 | @Override
299 | public ICordovaCookieManager getCookieManager() {
300 | return cookieManager;
301 | }
302 |
303 | @Override
304 | public void loadUrl(String url, boolean clearNavigationStack) {
305 | if (!activityDelegate.isXWalkReady()) {
306 | startUrl = url;
307 | return;
308 | }
309 | webView.load(url, null);
310 | }
311 |
312 | /**
313 | * This API is used in Cordova-Android 6.0.0 override from
314 | *
315 | * CordovaWebViewEngine.java
316 | * @since Cordova 6.0
317 | */
318 | public void evaluateJavascript(String js, ValueCallback callback) {
319 | webView.evaluateJavascript(js, callback);
320 | }
321 |
322 | public boolean isXWalkReady() {
323 | return activityDelegate.isXWalkReady();
324 | }
325 |
326 | public interface PermissionRequestListener {
327 | public void onRequestPermissionResult(int requestCode, String[] permissions,
328 | int[] grantResults);
329 | }
330 |
331 | public boolean requestPermissionsForFileChooser(final PermissionRequestListener listener) {
332 | ArrayList dangerous_permissions = new ArrayList();
333 | try {
334 | PackageManager packageManager = cordova.getActivity().getPackageManager();
335 | PackageInfo packageInfo = packageManager.getPackageInfo(
336 | cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS);
337 | for (String permission : packageInfo.requestedPermissions) {
338 | if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
339 | || permission.equals(Manifest.permission.CAMERA)) {
340 | dangerous_permissions.add(permission);
341 | }
342 | }
343 | } catch (NameNotFoundException e) {
344 | }
345 |
346 | if (dangerous_permissions.isEmpty()) {
347 | return false;
348 | }
349 |
350 | CordovaPlugin permissionRequestPlugin = new CordovaPlugin() {
351 | @Override
352 | public void onRequestPermissionResult(int requestCode, String[] permissions,
353 | int[] grantResults) {
354 | if (requestCode != PERMISSION_REQUEST_CODE) return;
355 | listener.onRequestPermissionResult(requestCode, permissions, grantResults);
356 | }
357 | };
358 | try {
359 | cordova.requestPermissions(permissionRequestPlugin, PERMISSION_REQUEST_CODE,
360 | dangerous_permissions.toArray(new String[dangerous_permissions.size()]));
361 | } catch (NoSuchMethodError e) {
362 | return false;
363 | }
364 | return true;
365 | }
366 | }
367 |
--------------------------------------------------------------------------------
/src/android/xwalk.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | def EMBEDDED_MODE = "embedded"
21 | def SHARED_MODE = "shared"
22 | def LITE_MODE = "lite"
23 | def DEFAULT_GROUP_ID = "org.xwalk:"
24 | def SHARED_ARTIFACT_ID = "xwalk_shared_library:"
25 | def EMBEDD_ARTIFACT_ID = "xwalk_core_library:"
26 | def CANARY_ARTIFACT_ID = "xwalk_core_library_canary:"
27 | def BIT_64 = ":64bit@aar"
28 | def DEFAULT_MIN_SDK_VERSION = 14
29 |
30 | def getConfigPreference(name) {
31 | name = name.toLowerCase()
32 |
33 | def xml
34 |
35 | if (file("src/main/res/xml/config.xml").exists()) {
36 | // cordova-android >= 7.0.0
37 | xml = file("src/main/res/xml/config.xml").getText()
38 | } else {
39 | // cordova-android < 7.0.0
40 | xml = file("res/xml/config.xml").getText()
41 | }
42 |
43 | // Disable namespace awareness since Cordova doesn't use them properly
44 | def root = new XmlParser(false, false).parseText(xml)
45 |
46 | def ret, defaultValue
47 | root.preference.each { it ->
48 | def attrName = it.attribute("name")
49 | if (attrName && attrName.toLowerCase() == name) {
50 | if (it.attribute('default') != null) {
51 | defaultValue = it.attribute('default');
52 | } else {
53 | ret = it.attribute("value")
54 | }
55 | }
56 | }
57 | return ret ? ret : defaultValue
58 | }
59 |
60 | if (!project.hasProperty('xwalk64bit')) {
61 | ext.xwalk64bit = getConfigPreference("xwalk64bit");
62 | println xwalk64bit
63 | }
64 | if (cdvBuildMultipleApks == null) {
65 | ext.xwalkMultipleApk = getConfigPreference("xwalkMultipleApk").toBoolean();
66 | } else {
67 | ext.xwalkMultipleApk = cdvBuildMultipleApks.toBoolean();
68 | }
69 |
70 | def minSdk = getConfigPreference("android-minSdkVersion");
71 | if (cdvMinSdkVersion == null) {
72 | ext.cdvMinSdkVersion = minSdk && Integer.parseInt(minSdk) > DEFAULT_MIN_SDK_VERSION ? minSdk : DEFAULT_MIN_SDK_VERSION;
73 | } else if (Integer.parseInt('' + cdvMinSdkVersion) < Integer.parseInt(minSdk)) {
74 | ext.cdvMinSdkVersion = minSdk;
75 | }
76 |
77 | if (!project.hasProperty('xwalkMode')) {
78 | ext.xwalkMode = getConfigPreference("xwalkMode");
79 | }
80 |
81 |
82 | if (ext.xwalkMode == SHARED_MODE) {
83 | // Build one apk at shared mode because the value of
84 | // ext.cdvBuildMultipleApks is false by default.
85 | xwalk64bit = null;
86 | } else if (xwalk64bit == null) {
87 | // Build embedded 32 bit crosswalk will generate two apks by default.
88 | ext.cdvBuildMultipleApks = xwalkMultipleApk;
89 | }
90 |
91 | // Set defaults before project's build-extras.gradle
92 | if (!project.hasProperty('xwalkVersion')) {
93 | ext.xwalkVersion = getConfigPreference("xwalkVersion")
94 | }
95 |
96 | // Set defaults before project's build-extras.gradle
97 | if (!project.hasProperty('xwalkLiteVersion')) {
98 | ext.xwalkLiteVersion = getConfigPreference("xwalkLiteVersion")
99 | }
100 |
101 | if (!project.hasProperty('xwalkCommandLine')) {
102 | ext.xwalkCommandLine = getConfigPreference("xwalkCommandLine")
103 | }
104 | // Apply values after project's build-extras.gradle
105 | cdvPluginPostBuildExtras.add({
106 | def xwalkMavenRepo = 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2';
107 | if (xwalkMode == LITE_MODE) {
108 | xwalkMavenRepo = 'https://download.01.org/crosswalk/releases/crosswalk-lite/android/maven2';
109 | }
110 | repositories {
111 | maven {
112 | url xwalkMavenRepo
113 | }
114 | }
115 |
116 | android {
117 | if (xwalk64bit != null) {
118 | productFlavors {
119 | x86_64 {
120 | versionCode defaultConfig.versionCode + 6
121 | ndk {
122 | abiFilters "x86_64", ""
123 | }
124 | }
125 | arm64 {
126 | versionCode defaultConfig.versionCode + 9
127 | ndk {
128 | abiFilters "arm64-v8a", ""
129 | }
130 | }
131 | }
132 | }
133 | }
134 |
135 | def xwalkSpec = xwalkVersion
136 | if (ext.xwalkMode == LITE_MODE) {
137 | xwalkSpec = xwalkLiteVersion;
138 | }
139 |
140 | if ((xwalkSpec =~ /:/).count == 1) {
141 | xwalkSpec = DEFAULT_GROUP_ID + xwalkSpec
142 | } else if ((xwalkSpec =~ /:/).count == 0) {
143 | if (xwalkSpec ==~ /\d+/) {
144 | xwalkSpec = "${xwalkSpec}+"
145 | }
146 |
147 | def artifactid = EMBEDD_ARTIFACT_ID;
148 | if (ext.xwalkMode == SHARED_MODE) {
149 | artifactid = SHARED_ARTIFACT_ID;
150 | } else if (ext.xwalkMode == LITE_MODE) {
151 | artifactid = CANARY_ARTIFACT_ID;
152 | }
153 | xwalkSpec = DEFAULT_GROUP_ID + artifactid + xwalkSpec
154 | }
155 | if (xwalk64bit != null) {
156 | xwalkSpec = xwalkSpec + BIT_64
157 | }
158 | println xwalkSpec
159 |
160 | dependencies {
161 | implementation xwalkSpec
162 | }
163 |
164 | if (file('assets/xwalk-command-line').exists()) {
165 | println('Not writing assets/xwalk-command-line since file already exists.')
166 | return
167 | }
168 | android.applicationVariants.all { variant ->
169 | def variantName = variant.name.capitalize()
170 | def mergeTask = tasks["merge${variantName}Assets"]
171 | def processTask = tasks["process${variantName}Resources"]
172 | def outFile = new File (mergeTask.outputDir, "xwalk-command-line")
173 | def newTask = project.task("createXwalkCommandLineFile${variantName}") << {
174 | mergeTask.outputDir.mkdirs()
175 | outFile.write("xwalk ${xwalkCommandLine}\n")
176 | }
177 | newTask.dependsOn(mergeTask)
178 | processTask.dependsOn(newTask)
179 | }
180 | })
181 |
--------------------------------------------------------------------------------
/src/ios/CDVWKProcessPoolFactory.h:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | #import
21 |
22 | @interface CDVWKProcessPoolFactory : NSObject
23 | @property (nonatomic, retain) WKProcessPool* sharedPool;
24 |
25 | +(instancetype) sharedFactory;
26 | -(WKProcessPool*) sharedProcessPool;
27 | @end
28 |
--------------------------------------------------------------------------------
/src/ios/CDVWKProcessPoolFactory.m:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | #import
21 | #import
22 | #import "CDVWKProcessPoolFactory.h"
23 |
24 | static CDVWKProcessPoolFactory *factory = nil;
25 |
26 | @implementation CDVWKProcessPoolFactory
27 |
28 | + (instancetype)sharedFactory
29 | {
30 | static dispatch_once_t onceToken;
31 | dispatch_once(&onceToken, ^{
32 | factory = [[CDVWKProcessPoolFactory alloc] init];
33 | });
34 |
35 | return factory;
36 | }
37 |
38 | - (instancetype)init
39 | {
40 | if (self = [super init]) {
41 | _sharedPool = [[WKProcessPool alloc] init];
42 | }
43 | return self;
44 | }
45 |
46 | - (WKProcessPool*) sharedProcessPool {
47 | return _sharedPool;
48 | }
49 | @end
50 |
--------------------------------------------------------------------------------
/src/ios/CDVWKWebViewEngine.h:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | #import
21 | #import
22 |
23 | @interface CDVWKWebViewEngine : CDVPlugin
24 |
25 | @property (nonatomic, strong, readonly) id uiDelegate;
26 | @property (nonatomic, strong) NSString * basePath;
27 |
28 | -(void)setServerBasePath:(CDVInvokedUrlCommand*)command;
29 | -(void)getServerBasePath:(CDVInvokedUrlCommand*)command;
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/src/ios/CDVWKWebViewUIDelegate.h:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | #import
21 |
22 | @interface CDVWKWebViewUIDelegate : NSObject
23 |
24 | @property (nonatomic, copy) NSString* title;
25 |
26 | - (instancetype)initWithTitle:(NSString*)title;
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/src/ios/CDVWKWebViewUIDelegate.m:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | #import "CDVWKWebViewUIDelegate.h"
21 |
22 | @implementation CDVWKWebViewUIDelegate
23 |
24 | - (instancetype)initWithTitle:(NSString*)title
25 | {
26 | self = [super init];
27 | if (self) {
28 | self.title = title;
29 | }
30 |
31 | return self;
32 | }
33 |
34 | - (void) webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message
35 | initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(void))completionHandler
36 | {
37 | UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
38 | message:message
39 | preferredStyle:UIAlertControllerStyleAlert];
40 |
41 | UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
42 | style:UIAlertActionStyleDefault
43 | handler:^(UIAlertAction* action)
44 | {
45 | completionHandler();
46 | [alert dismissViewControllerAnimated:YES completion:nil];
47 | }];
48 |
49 | [alert addAction:ok];
50 |
51 | UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;
52 |
53 | [rootController presentViewController:alert animated:YES completion:nil];
54 | }
55 |
56 | - (void) webView:(WKWebView*)webView runJavaScriptConfirmPanelWithMessage:(NSString*)message
57 | initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(BOOL result))completionHandler
58 | {
59 | UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
60 | message:message
61 | preferredStyle:UIAlertControllerStyleAlert];
62 |
63 | UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
64 | style:UIAlertActionStyleDefault
65 | handler:^(UIAlertAction* action)
66 | {
67 | completionHandler(YES);
68 | [alert dismissViewControllerAnimated:YES completion:nil];
69 | }];
70 |
71 | [alert addAction:ok];
72 |
73 | UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel")
74 | style:UIAlertActionStyleDefault
75 | handler:^(UIAlertAction* action)
76 | {
77 | completionHandler(NO);
78 | [alert dismissViewControllerAnimated:YES completion:nil];
79 | }];
80 | [alert addAction:cancel];
81 |
82 | UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;
83 |
84 | [rootController presentViewController:alert animated:YES completion:nil];
85 | }
86 |
87 | - (void) webView:(WKWebView*)webView runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
88 | defaultText:(NSString*)defaultText initiatedByFrame:(WKFrameInfo*)frame
89 | completionHandler:(void (^)(NSString* result))completionHandler
90 | {
91 | UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
92 | message:prompt
93 | preferredStyle:UIAlertControllerStyleAlert];
94 |
95 | UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
96 | style:UIAlertActionStyleDefault
97 | handler:^(UIAlertAction* action)
98 | {
99 | completionHandler(((UITextField*)alert.textFields[0]).text);
100 | [alert dismissViewControllerAnimated:YES completion:nil];
101 | }];
102 |
103 | [alert addAction:ok];
104 |
105 | UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel")
106 | style:UIAlertActionStyleDefault
107 | handler:^(UIAlertAction* action)
108 | {
109 | completionHandler(nil);
110 | [alert dismissViewControllerAnimated:YES completion:nil];
111 | }];
112 | [alert addAction:cancel];
113 |
114 | [alert addTextFieldWithConfigurationHandler:^(UITextField* textField) {
115 | textField.text = defaultText;
116 | }];
117 |
118 | UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;
119 |
120 | [rootController presentViewController:alert animated:YES completion:nil];
121 | }
122 |
123 | @end
124 |
--------------------------------------------------------------------------------
/src/ios/IONAssetHandler.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface IONAssetHandler : NSObject
5 |
6 | @property (nonatomic, strong) NSString * basePath;
7 | @property (nonatomic, strong) NSString * scheme;
8 |
9 | -(void)setAssetPath:(NSString *)assetPath;
10 | - (instancetype)initWithBasePath:(NSString *)basePath andScheme:(NSString *)scheme;
11 |
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/src/ios/IONAssetHandler.m:
--------------------------------------------------------------------------------
1 | #import "IONAssetHandler.h"
2 | #import
3 | #import "CDVWKWebViewEngine.h"
4 |
5 | @implementation IONAssetHandler
6 |
7 | -(void)setAssetPath:(NSString *)assetPath {
8 | self.basePath = assetPath;
9 | }
10 |
11 | - (instancetype)initWithBasePath:(NSString *)basePath andScheme:(NSString *)scheme {
12 | self = [super init];
13 | if (self) {
14 | _basePath = basePath;
15 | _scheme = scheme;
16 | }
17 | return self;
18 | }
19 |
20 | - (void)webView:(WKWebView *)webView startURLSchemeTask:(id )urlSchemeTask
21 | {
22 | NSString * startPath = @"";
23 | NSURL * url = urlSchemeTask.request.URL;
24 | NSString * stringToLoad = url.path;
25 | NSString * scheme = url.scheme;
26 |
27 | if ([scheme isEqualToString:self.scheme]) {
28 | if ([stringToLoad hasPrefix:@"/_app_file_"]) {
29 | startPath = [stringToLoad stringByReplacingOccurrencesOfString:@"/_app_file_" withString:@""];
30 | } else {
31 | startPath = self.basePath ? self.basePath : @"";
32 | if ([stringToLoad isEqualToString:@""] || [url.pathExtension isEqualToString:@""]) {
33 | startPath = [startPath stringByAppendingString:@"/index.html"];
34 | } else {
35 | startPath = [startPath stringByAppendingString:stringToLoad];
36 | }
37 | }
38 | }
39 | NSError * fileError = nil;
40 | NSData * data = nil;
41 | if ([self isMediaExtension:url.pathExtension]) {
42 | data = [NSData dataWithContentsOfFile:startPath options:NSDataReadingMappedIfSafe error:&fileError];
43 | }
44 | if (!data || fileError) {
45 | data = [[NSData alloc] initWithContentsOfFile:startPath];
46 | }
47 | NSInteger statusCode = 200;
48 | if (!data) {
49 | statusCode = 404;
50 | }
51 | NSURL * localUrl = [NSURL URLWithString:url.absoluteString];
52 | NSString * mimeType = [self getMimeType:url.pathExtension];
53 | id response = nil;
54 | if (data && [self isMediaExtension:url.pathExtension]) {
55 | response = [[NSURLResponse alloc] initWithURL:localUrl MIMEType:mimeType expectedContentLength:data.length textEncodingName:nil];
56 | } else {
57 | NSDictionary * headers = @{ @"Content-Type" : mimeType, @"Cache-Control": @"no-cache"};
58 | response = [[NSHTTPURLResponse alloc] initWithURL:localUrl statusCode:statusCode HTTPVersion:nil headerFields:headers];
59 | }
60 |
61 | [urlSchemeTask didReceiveResponse:response];
62 | [urlSchemeTask didReceiveData:data];
63 | [urlSchemeTask didFinish];
64 |
65 | }
66 |
67 | - (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask
68 | {
69 | NSLog(@"stop");
70 | }
71 |
72 | -(NSString *) getMimeType:(NSString *)fileExtension {
73 | if (fileExtension && ![fileExtension isEqualToString:@""]) {
74 | NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
75 | NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
76 | return contentType ? contentType : @"application/octet-stream";
77 | } else {
78 | return @"text/html";
79 | }
80 | }
81 |
82 | -(BOOL) isMediaExtension:(NSString *) pathExtension {
83 | NSArray * mediaExtensions = @[@"m4v", @"mov", @"mp4",
84 | @"aac", @"ac3", @"aiff", @"au", @"flac", @"m4a", @"mp3", @"wav"];
85 | if ([mediaExtensions containsObject:pathExtension.lowercaseString]) {
86 | return YES;
87 | }
88 | return NO;
89 | }
90 |
91 |
92 | @end
93 |
--------------------------------------------------------------------------------
/src/ios/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Niklas von Hertzen
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/ios/wk-plugin.js:
--------------------------------------------------------------------------------
1 |
2 | (function _wk_plugin() {
3 | // Check if we are running in WKWebView
4 | if (!window.webkit || !window.webkit.messageHandlers) {
5 | return;
6 | }
7 |
8 | // Initialize Ionic
9 | window.Ionic = window.Ionic || {};
10 |
11 | var stopScrollHandler = window.webkit.messageHandlers.stopScroll;
12 | if (!stopScrollHandler) {
13 | console.error('Can not find stopScroll handler');
14 | return;
15 | }
16 |
17 | var stopScrollFunc = null;
18 | var stopScroll = {
19 | stop: function stop(callback) {
20 | if (!stopScrollFunc) {
21 | stopScrollFunc = callback;
22 | stopScrollHandler.postMessage('');
23 | }
24 | },
25 | fire: function fire() {
26 | stopScrollFunc && stopScrollFunc();
27 | stopScrollFunc = null;
28 | },
29 | cancel: function cancel() {
30 | stopScrollFunc = null;
31 | }
32 | };
33 |
34 | window.Ionic.StopScroll = stopScroll;
35 | // deprecated
36 | window.IonicStopScroll = stopScroll;
37 |
38 | console.debug("Ionic Stop Scroll injected!");
39 | })();
40 |
--------------------------------------------------------------------------------
/src/www/ios/ios-wkwebview-exec.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Licensed to the Apache Software Foundation (ASF) under one
4 | * or more contributor license agreements. See the NOTICE file
5 | * distributed with this work for additional information
6 | * regarding copyright ownership. The ASF licenses this file
7 | * to you under the Apache License, Version 2.0 (the
8 | * "License"); you may not use this file except in compliance
9 | * with the License. You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing,
14 | * software distributed under the License is distributed on an
15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | * KIND, either express or implied. See the License for the
17 | * specific language governing permissions and limitations
18 | * under the License.
19 | *
20 | */
21 |
22 | /**
23 | * Creates the exec bridge used to notify the native code of
24 | * commands.
25 | */
26 | var cordova = require('cordova');
27 | var utils = require('cordova/utils');
28 | var base64 = require('cordova/base64');
29 |
30 | function massageArgsJsToNative (args) {
31 | if (!args || utils.typeName(args) !== 'Array') {
32 | return args;
33 | }
34 | var ret = [];
35 | args.forEach(function (arg, i) {
36 | if (utils.typeName(arg) === 'ArrayBuffer') {
37 | ret.push({
38 | 'CDVType': 'ArrayBuffer',
39 | 'data': base64.fromArrayBuffer(arg)
40 | });
41 | } else {
42 | ret.push(arg);
43 | }
44 | });
45 | return ret;
46 | }
47 |
48 | function massageMessageNativeToJs (message) {
49 | if (message.CDVType === 'ArrayBuffer') {
50 | var stringToArrayBuffer = function (str) {
51 | var ret = new Uint8Array(str.length);
52 | for (var i = 0; i < str.length; i++) {
53 | ret[i] = str.charCodeAt(i);
54 | }
55 | return ret.buffer;
56 | };
57 | var base64ToArrayBuffer = function (b64) {
58 | return stringToArrayBuffer(atob(b64)); // eslint-disable-line no-undef
59 | };
60 | message = base64ToArrayBuffer(message.data);
61 | }
62 | return message;
63 | }
64 |
65 | function convertMessageToArgsNativeToJs (message) {
66 | var args = [];
67 | if (!message || !message.hasOwnProperty('CDVType')) {
68 | args.push(message);
69 | } else if (message.CDVType === 'MultiPart') {
70 | message.messages.forEach(function (e) {
71 | args.push(massageMessageNativeToJs(e));
72 | });
73 | } else {
74 | args.push(massageMessageNativeToJs(message));
75 | }
76 | return args;
77 | }
78 |
79 | var iOSExec = function () {
80 | // detect change in bridge, if there is a change, we forward to new bridge
81 |
82 | // if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.cordova && window.webkit.messageHandlers.cordova.postMessage) {
83 | // bridgeMode = jsToNativeModes.WK_WEBVIEW_BINDING;
84 | // }
85 |
86 | var successCallback, failCallback, service, action, actionArgs;
87 | var callbackId = null;
88 | if (typeof arguments[0] !== 'string') {
89 | // FORMAT ONE
90 | successCallback = arguments[0];
91 | failCallback = arguments[1];
92 | service = arguments[2];
93 | action = arguments[3];
94 | actionArgs = arguments[4];
95 |
96 | // Since we need to maintain backwards compatibility, we have to pass
97 | // an invalid callbackId even if no callback was provided since plugins
98 | // will be expecting it. The Cordova.exec() implementation allocates
99 | // an invalid callbackId and passes it even if no callbacks were given.
100 | callbackId = 'INVALID';
101 | } else {
102 | throw new Error('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' + // eslint-disable-line
103 | 'cordova.exec(null, null, \'Service\', \'action\', [ arg1, arg2 ]);');
104 | }
105 |
106 | // If actionArgs is not provided, default to an empty array
107 | actionArgs = actionArgs || [];
108 |
109 | // Register the callbacks and add the callbackId to the positional
110 | // arguments if given.
111 | if (successCallback || failCallback) {
112 | callbackId = service + cordova.callbackId++;
113 | cordova.callbacks[callbackId] =
114 | {success: successCallback, fail: failCallback};
115 | }
116 |
117 | actionArgs = massageArgsJsToNative(actionArgs);
118 |
119 | // CB-10133 DataClone DOM Exception 25 guard (fast function remover)
120 | var command = [callbackId, service, action, JSON.parse(JSON.stringify(actionArgs))];
121 | window.webkit.messageHandlers.cordova.postMessage(command);
122 | };
123 |
124 | iOSExec.nativeCallback = function (callbackId, status, message, keepCallback, debug) {
125 | var success = status === 0 || status === 1;
126 | var args = convertMessageToArgsNativeToJs(message);
127 | Promise.resolve().then(function () {
128 | cordova.callbackFromNative(callbackId, success, status, args, keepCallback); // eslint-disable-line
129 | });
130 | };
131 |
132 | // for backwards compatibility
133 | iOSExec.nativeEvalAndFetch = function (func) {
134 | try {
135 | func();
136 | } catch (e) {
137 | console.log(e);
138 | }
139 | };
140 |
141 | // Proxy the exec for bridge changes. See CB-10106
142 |
143 | function cordovaExec () {
144 | var cexec = require('cordova/exec');
145 | var cexec_valid = (typeof cexec.nativeFetchMessages === 'function') && (typeof cexec.nativeEvalAndFetch === 'function') && (typeof cexec.nativeCallback === 'function');
146 | return (cexec_valid && execProxy !== cexec) ? cexec : iOSExec;
147 | }
148 |
149 | function execProxy () {
150 | cordovaExec().apply(null, arguments);
151 | }
152 |
153 | execProxy.nativeFetchMessages = function () {
154 | return cordovaExec().nativeFetchMessages.apply(null, arguments);
155 | };
156 |
157 | execProxy.nativeEvalAndFetch = function () {
158 | return cordovaExec().nativeEvalAndFetch.apply(null, arguments);
159 | };
160 |
161 | execProxy.nativeCallback = function () {
162 | return cordovaExec().nativeCallback.apply(null, arguments);
163 | };
164 |
165 | module.exports = execProxy;
166 |
167 | if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.cordova && window.webkit.messageHandlers.cordova.postMessage) {
168 | // unregister the old bridge
169 | cordova.define.remove('cordova/exec');
170 | // redefine bridge to our new bridge
171 | cordova.define('cordova/exec', function (require, exports, module) {
172 | module.exports = execProxy;
173 | });
174 | }
175 |
--------------------------------------------------------------------------------
/src/www/util.js:
--------------------------------------------------------------------------------
1 | var exec = require('cordova/exec');
2 |
3 | var WebView = {
4 | convertFileSrc: function(url) {
5 | if (!url) {
6 | return url;
7 | }
8 | if (url.startsWith('/')) {
9 | return window.WEBVIEW_SERVER_URL + '/_app_file_' + url;
10 | }
11 | if (url.startsWith('file://')) {
12 | return window.WEBVIEW_SERVER_URL + url.replace('file://', '/_app_file_');
13 | }
14 | if (url.startsWith('content://')) {
15 | return window.WEBVIEW_SERVER_URL + url.replace('content:/', '/_app_content_');
16 | }
17 | return url;
18 | },
19 | setServerBasePath: function(path) {
20 | exec(null, null, 'IonicWebView', 'setServerBasePath', [path]);
21 | },
22 | getServerBasePath: function(callback) {
23 | exec(callback, null, 'IonicWebView', 'getServerBasePath', []);
24 | },
25 | persistServerBasePath: function() {
26 | exec(null, null, 'IonicWebView', 'persistServerBasePath', []);
27 | },
28 | useCrosswalkWebViewAtTheNextStartup:function(){
29 | exec(null, null, 'IonicWebView', 'useCrosswalkWebViewAtTheNextStartup', []);
30 | },
31 | useSystemWebViewAtTheNextStartup:function(){
32 | exec(null, null, 'IonicWebView', 'useSystemWebViewAtTheNextStartup', []);
33 | }
34 | }
35 |
36 | module.exports = WebView;
--------------------------------------------------------------------------------