├── .circleci └── config.yml ├── .gitignore ├── .npmrc ├── .vscode └── ipch │ └── 8a9338aa9cb5778 │ └── mmap_address.bin ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CROSSWALK-README.md ├── IONIC4-README.md ├── LICENSE ├── README.md ├── hooks ├── after_build │ └── 000-build_64_bit.js ├── after_plugin_install │ └── 000-shared_mode_special.js ├── before_build │ └── 000-build_64_bit.js ├── before_plugin_uninstall │ └── 000-shared_mode_special.js └── update_config.js ├── package.json ├── plugin.xml └── src ├── android ├── com │ ├── ionicframework │ │ └── cordova │ │ │ └── webview │ │ │ ├── AndroidProtocolHandler.java │ │ │ ├── IonicWebView.java │ │ │ ├── IonicWebViewEngine.java │ │ │ ├── UriMatcher.java │ │ │ └── WebViewLocalServer.java │ └── lifang123 │ │ └── cordova │ │ └── webview │ │ ├── CrosswalkWebViewEngine.java │ │ ├── IonicCrosswalkWebView.java │ │ └── IonicCrosswalkWebViewEngine.java ├── org │ └── crosswalk │ │ └── engine │ │ ├── XWalkCordovaClientCertRequest.java │ │ ├── XWalkCordovaCookieManager.java │ │ ├── XWalkCordovaHttpAuthHandler.java │ │ ├── XWalkCordovaResourceClient.java │ │ ├── XWalkCordovaUiClient.java │ │ ├── XWalkCordovaView.java │ │ ├── XWalkExposedJsApi.java │ │ ├── XWalkFileChooser.java │ │ └── XWalkWebViewEngine.java └── xwalk.gradle ├── ios ├── CDVWKProcessPoolFactory.h ├── CDVWKProcessPoolFactory.m ├── CDVWKWebViewEngine.h ├── CDVWKWebViewEngine.m ├── CDVWKWebViewUIDelegate.h ├── CDVWKWebViewUIDelegate.m ├── IONAssetHandler.h ├── IONAssetHandler.m ├── LICENSE └── wk-plugin.js └── www ├── ios └── ios-wkwebview-exec.js └── util.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | aliases: 4 | - &restore-cache 5 | keys: 6 | - dependency-cache-{{ checksum "package.json" }}-1 7 | 8 | - &save-cache 9 | key: dependency-cache-{{ checksum "package.json" }}-1 10 | paths: 11 | - node_modules 12 | 13 | defaults: &defaults 14 | docker: 15 | - image: circleci/node:10 16 | working_directory: /tmp/workspace 17 | 18 | jobs: 19 | build: 20 | <<: *defaults 21 | steps: 22 | - checkout 23 | - restore_cache: *restore-cache 24 | - run: npm install 25 | - save_cache: *save-cache 26 | - persist_to_workspace: 27 | root: /tmp/workspace 28 | paths: 29 | - "*" 30 | 31 | deploy: 32 | <<: *defaults 33 | environment: 34 | GIT_AUTHOR_NAME: Ionitron 35 | GIT_AUTHOR_EMAIL: hi@ionicframework.com 36 | GIT_COMMITTER_NAME: Ionitron 37 | GIT_COMMITTER_EMAIL: hi@ionicframework.com 38 | steps: 39 | - add_ssh_keys: 40 | fingerprints: 41 | - "ae:6d:3a:f1:cf:39:e1:94:6e:22:2a:9f:54:f9:b0:1b" # ionitron user key 42 | - checkout 43 | - attach_workspace: 44 | at: /tmp/workspace 45 | - run: npx semantic-release 46 | 47 | 48 | workflows: 49 | version: 2 50 | build: 51 | jobs: 52 | - build 53 | - deploy: 54 | requires: [build] 55 | filters: 56 | branches: 57 | only: stable 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #If ignorance is bliss, then somebody knock the smile off my face 2 | 3 | *.csproj.user 4 | *.suo 5 | *.cache 6 | Thumbs.db 7 | *.DS_Store 8 | 9 | *.bak 10 | *.cache 11 | *.log 12 | *.swp 13 | *.user 14 | 15 | node_modules 16 | xcuserdata 17 | package-lock.json 18 | 19 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.vscode/ipch/8a9338aa9cb5778/mmap_address.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waitaction/cordova-plugin-ionic4-crosswalk-webview/137ef996deff22dea7e5064b541e71b5459a67aa/.vscode/ipch/8a9338aa9cb5778/mmap_address.bin -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [4.0.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v4.0.0...v4.0.1) (2019-03-26) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **ios:** Fix autofocus on iOS 12.2 ([#334](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/334)) ([cb4c491](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/cb4c491)), closes [#330](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/330) 7 | * account port on resolving uri path ([#321](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/321)) ([fdfe8aa](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/fdfe8aa)) 8 | 9 | # [4.0.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v3.1.2...v4.0.0) (2019-02-18) 10 | 11 | 12 | ### Features 13 | 14 | * **ios:** Make iOS app Scheme configurable with a preference ([#307](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/307)) ([d52d37e](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/d52d37e)), closes [#282](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/282) 15 | * **ios:** Remove WKSuspendInBackground preference ([#309](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/309)) ([73b6659](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/73b6659)), closes [#286](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/286) 16 | 17 | 18 | ### BREAKING CHANGES 19 | 20 | * **ios:** Remove the WKSuspendInBackground preference, so app relying on that prefere will 21 | not behave as expected 22 | 23 | ## [3.1.2](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v3.1.1...v3.1.2) (2019-02-04) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * **Android:** Handle Range Requests for proper media file handling ([#298](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/298)) ([6f18248](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/6f18248)), closes [#248](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/248) [#205](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/205) [#141](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/141) 29 | 30 | ## [3.1.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v3.1.0...v3.1.1) (2019-01-18) 31 | 32 | 33 | ### Bug Fixes 34 | 35 | * **ios:** Remove unused code ([#281](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/281)) ([fc7ea27](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/fc7ea27)) 36 | 37 | # [3.1.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v3.0.0...v3.1.0) (2019-01-17) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * **ios:** Fix video playback of files with uppercase extension ([#264](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/264)) ([2c4b225](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/2c4b225)), closes [#260](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/260) 43 | * Set engines to require Cordova CLI 7.1.0 or newer ([#276](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/276)) ([40f42e1](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/40f42e1)), closes [#263](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/263) 44 | * Use a single scheme for all files ([#270](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/270)) ([3d1bcdd](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/3d1bcdd)), closes [#258](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/258) 45 | 46 | 47 | ### Features 48 | 49 | * **Android:** Make app Scheme configurable with a preference ([#274](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/274)) ([18d9f2c](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/18d9f2c)), closes [#269](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/269) [#255](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/255) 50 | 51 | # [3.0.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.3.1...v3.0.0) (2019-01-03) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * **iOS:** Remove unused code ([#247](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/247)) ([bceb17a](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/bceb17a)) 57 | 58 | 59 | ### Features 60 | 61 | * Allows configuration of Mixed Content Mode ([#240](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/240)) ([486d412](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/486d412)), closes [#231](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/231) 62 | * **Android:** Implement ionic-file and ionic-content urls ([#242](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/242)) ([8ef0c30](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/8ef0c30)), closes [#204](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/204) [#183](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/183) 63 | * **iOS:** Remove GCDWebServer ([#244](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/244)) ([0dee0cf](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/0dee0cf)) 64 | * **WebViewLocalServer.java:** return 404 error code when a local file is not found ([#217](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/217)) ([f7a551e](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/f7a551e)), closes [#216](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/216) 65 | 66 | 67 | ### BREAKING CHANGES 68 | 69 | * **iOS:** Sets deployment-target to 11, so will only work on iOS 11+ 70 | 71 | * Address changes 72 | * changes the default from 1 (never) to 0 (always) 73 | * **WebViewLocalServer.java:** Until now, the Android part of the plugin was returning a 200 http code even though 74 | the requested file didn't exist. This behavior was inconsistent with the historical behavior of the 75 | iOS webView. This change makes them both work in the same manner but introduces a breaking change 76 | for the current Android users that are expecting a 200 http code no matter what and are testing the 77 | not found error just by checking if the body is null. 78 | 79 | ## [2.3.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.3.0...v2.3.1) (2018-12-06) 80 | 81 | 82 | ### Bug Fixes 83 | 84 | * Handle convertFileSrc when using ionic:// scheme ([#236](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/236)) ([89ce899](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/89ce899)) 85 | 86 | # [2.3.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.5...v2.3.0) (2018-12-05) 87 | 88 | 89 | ### Features 90 | 91 | * **ios:** Add URLSchemeHandler for iOS 11+ ([#221](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/221)) ([4a973f4](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/4a973f4)) 92 | 93 | ## [2.2.5](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.4...v2.2.5) (2018-11-20) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * Add option for Dark keyboard appearance ([#44](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/44)) ([6c0fe56](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/6c0fe56)) 99 | 100 | ## [2.2.4](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.3...v2.2.4) (2018-11-20) 101 | 102 | 103 | ### Bug Fixes 104 | 105 | * fix keyboard displacement bug in iOS 12 WKWebView ([#201](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/201)) ([a670568](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/a670568)) 106 | 107 | ## [2.2.3](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.2...v2.2.3) (2018-11-09) 108 | 109 | 110 | ### Bug Fixes 111 | 112 | * Remove main and fix description ([d52db66](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/d52db66)) 113 | 114 | ## [2.2.2](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.1...v2.2.2) (2018-11-09) 115 | 116 | ### Bug Fixes 117 | 118 | * Add more server checks before loading urls or reloading ([#211](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/211)) ([60eff2f](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/60eff2f)) 119 | 120 | ## [2.2.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.0...v2.2.1) (2018-11-07) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * Show error page if server is not running ([#207](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/207)) ([6a2e07e](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/6a2e07e)) 126 | 127 | 128 | ### 2.2.0 (2018-10-04) 129 | 130 | * Fix issue where two apps running on the same port could conflict with each other ([#169](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/165) & [#186](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/186)) 131 | * Add kitkat support (API 19) ([#144](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/144)) [@leo6104](https://github.com/leo6104) 132 | * Fix issue where local server was being used if launch URL is external ([#169](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/169)) 133 | 134 | 135 | ### 2.1.4 (2018-09-13) 136 | 137 | * Allow Ionic Deploy `DisableDeploy` preference to disable loading of deploy updates ([#172](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/172)) 138 | 139 | 140 | ### 2.1.3 (2018-09-06) 141 | 142 | * Make server path relative ([#164](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/164)) 143 | 144 | 145 | ### 2.1.2 (2018-09-05) 146 | 147 | * Return 404 response when file doesn't exist ([#162](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/162)) 148 | * Load local assets if the app is a freshly installed binary ([#155](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/155)) 149 | * Reset stored server path on new binary ([#161](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/161)) 150 | 151 | 152 | ### 2.1.1 (2018-09-04) 153 | 154 | * Allow range requests for local files ([#154](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/154)) 155 | 156 | 157 | ### 2.1.0 (2018-08-23) 158 | 159 | * Add support for `cordova-android` 6 ([#150](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/150)) 160 | 161 | 162 | ### 2.0.3 (2018-08-14) 163 | 164 | * Fix nil reference by setting up the server URL before routes are set up. ([#135](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/135)) [@matejkramny](https://github.com/matejkramny) 165 | * Resolve issue when app is launched in background. ([#124](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/124)) [@ghenry22](https://github.com/ghenry22) 166 | 167 | 168 | ### 2.0.2 (2018-07-30) 169 | 170 | * Immediately load new server base path upon setting it. ([#132](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/132)) 171 | 172 | 173 | ### 2.0.1 (2018-07-25) 174 | 175 | * Avoid "not modified" response on iOS by always overriding last modified date. ([#127](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/127)) 176 | 177 | 178 | ### 2.0.0 (2018-07-23) 179 | 180 | * **BREAKING**: HTTP server now runs for iOS **and** Android, instead of just iOS. The server is configured the same for both platforms. 181 | * **BREAKING**: HTTP server now loads the app from a base href of `/`. The app URL behaves like `http://localhost:8080/index.html` instead of `http://localhost:8080/Users/.../index.html`. 182 | * **BREAKING**: HTTP server is configured to run in HTML5 routing mode (push state) by default. 183 | * **BREAKING**: File access through the Web View must be served by the HTTP server to avoid security errors in the Web View. Loading files via `file://` is not allowed by the Web View. The HTTP server will serve files via the `_file_` prefix, e.g. `http://localhost:8080/_file_/Users/.../file.png`. 184 | * `window.Ionic.normalizeURL()` has been deprecated. Use `window.Ionic.WebView.convertFileSrc()`. 185 | * iOS update HTTP server to latest upstream version (GCDwebserve 3.4.2) 186 | * iOS update HTTP server to restart sockets with error state when resuming from background 187 | * iOS enable HTTP server to continue running in background if the webview is running. 188 | * iOS enable Webview to continue running in background. Requires background mode capability enabled in xcode + valid use case as per app store requirements. If your app is not performing valid background tasks it will still be suspended by the OS as usual. As long as valid background tasks are running the webview will continue to function as expected. 189 | * iOS add config.xml options: 190 | * WKSuspendInBackground - defaults to true, if set to false then the webview and HTTP server will continue to run when the app is in the background or screen is locked 191 | * WKPort - defaults to 8080, define the port that the HTTP server will listen on 192 | * WKBind - defaults to localhost, if set to 127.0.0.1 then this IP will be used instead of the localhost hostname for the HTTP server 193 | 194 | See [Github releases](https://github.com/ionic-team/cordova-plugin-ionic-webview/releases) for earlier changes. 195 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :mega: **Support/Questions?**: Please see our [Support Page](https://ionicframework.com/support) for general support questions. The issues on GitHub should be reserved for bug reports and feature requests. 4 | 5 | ### Bug Reports 6 | 7 | Please create an issue describing the bug in detail. 8 | 9 | ### Feature Requests 10 | 11 | Please create an issue! 12 | 13 | ## Developing 14 | 15 | Please familiarize yourself with [Cordova plugin development](https://cordova.apache.org/docs/en/latest/guide/hybrid/plugins/). 16 | 17 | You can use `cordova plugin add` with a local directory to copy and compile plugin changes into a test project. 18 | 19 | ### Workflow 20 | 21 | This repo uses [semantic-release](https://github.com/semantic-release/semantic-release), so it's important to follow a strict workflow to ensure properly automated releases. 22 | 23 | * Work off of `master` branch (create new branch or fork) 24 | * Make changes 25 | * Use `npm run cz` (or `git cz` if [commitizen](https://github.com/commitizen/cz-cli) is installed globally) to make commits 26 | * Create a pull request 27 | * Pull requests will be approved and squashed into the `master` branch 28 | * Try to make pull requests with a single objective (don't have multiple features in one PR, don't mix fixes and features in one PR, etc.) 29 | 30 | ### Publishing 31 | 32 | Releases are automated in CI using [semantic-release](https://github.com/semantic-release/semantic-release) when the `stable` branch is pushed to Github. Rebase `master` with `stable`. Commits in `master` should be appropriately formatted from the PR workflow (see [Workflow](#workflow)). 33 | -------------------------------------------------------------------------------- /CROSSWALK-README.md: -------------------------------------------------------------------------------- 1 | # cordova-plugin-crosswalk-webview 2 | 3 | Makes your Cordova application use the [Crosswalk WebView](https://crosswalk-project.org/) 4 | instead of the System WebView. Requires cordova-android 4.0 or greater. 5 | 6 | ### Benefits 7 | 8 | * WebView doesn't change depending on Android version 9 | * Capabilities: such as WebRTC, WebAudio, Web Components 10 | * Performance improvements (compared to older system webviews) 11 | 12 | 13 | ### Drawbacks 14 | 15 | * Increased memory footprint 16 | * An overhead of ~30MB (as reported by the RSS column of ps) 17 | * Increased APK size (about 17MB) 18 | * Increased size on disk when installed (about 50MB) 19 | * Crosswalk WebView stores data (IndexedDB, LocalStorage, etc) separately from System WebView 20 | * You'll need to manually migrate local data when switching between the two (note: this is fixed in Crosswalk 15) 21 | 22 | ### Install 23 | 24 | The following directions are for cordova-cli (most people). Alternatively you can use the [Android platform scripts workflow](PlatformScriptsWorkflow.md). 25 | 26 | * Open an existing cordova project, with cordova-android 4.0.0+, and using the latest CLI. Crosswalk variables can be configured as an option when installing the plugin 27 | * Add this plugin 28 | 29 | ``` 30 | $ cordova plugin add cordova-plugin-crosswalk-webview 31 | ``` 32 | 33 | * Build 34 | ``` 35 | $ cordova build android 36 | ``` 37 | The build script will automatically fetch the Crosswalk WebView libraries from Crosswalk project download site (https://download.01.org/crosswalk/releases/crosswalk/android/maven2/) and build for both X86 and ARM architectures. 38 | 39 | For example, building android with Crosswalk generates: 40 | 41 | ``` 42 | /path/to/hello/platforms/android/build/outputs/apk/hello-x86-debug.apk 43 | /path/to/hello/platforms/android/build/outputs/apk/hello-armv7-debug.apk 44 | ``` 45 | 46 | Note that you might have to run `cordova clean` before building, if you previously built the app without cordova-plugin-crosswalk-webview. Also, manually uninstall the app from the device/emulator before attempting to install the crosswalk-enabled version. 47 | 48 | Also note that it is also possible to publish a multi-APK application on the Play Store that uses Crosswalk for Pre-L devices, and the (updatable) system webview for L+: 49 | 50 | To build Crosswalk-enabled apks, add this plugin and run: 51 | 52 | $ cordova build --release 53 | 54 | To build System-webview apk, remove this plugin and run: 55 | 56 | $ cordova build --release -- --minSdkVersion=21 57 | 58 | ### Configure 59 | 60 | You can try out a different Crosswalk version by specifying certain variables while installing the plugin, or by changing the value of `xwalkVersion` in your `config.xml` after installing the plugin. Some examples: 61 | 62 | 63 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_core_library:14+" 64 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="xwalk_core_library:14+" 65 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="14+" 66 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="14" 67 | 68 | 69 | 70 | 71 | 72 | You can also use a Crosswalk beta version. Some examples: 73 | 74 | 75 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_core_library_beta:14+" 76 | 77 | 78 | You can set [command-line flags](http://peter.sh/experiments/chromium-command-line-switches/) as well: 79 | 80 | 81 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_COMMANDLINE="--disable-pull-to-refresh-effect" 82 | 83 | 84 | You can use the Crosswalk [shared mode](https://crosswalk-project.org/documentation/shared_mode.html) which allows multiple Crosswalk applications to share one Crosswalk runtime downloaded from the Play Store. 85 | 86 | 87 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_MODE="shared" 88 | 89 | 90 | You can also use a Crosswalk beta version on shared mode, e.g.: 91 | 92 | 93 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_shared_library_beta:14+" 94 | 95 | You can use the Crosswalk [lite mode](https://crosswalk-project.org/documentation/crosswalk_lite.html) which is the Crosswalk runtime designed to be as small as possible by removing less common libraries and features and compressing the APK. 96 | 97 | 98 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_MODE="lite" 99 | 100 | 101 | You can set background color with the preference of BackgroundColor. 102 | 103 | 104 | 105 | 106 | You can also set user agent with the preference of xwalkUserAgent. 107 | 108 | 109 | 110 | ### Release Notes 111 | 112 | #### 2.4.0 (January 18, 2018) 113 | * Keep compatibility with cordova-android 7.0 project structure 114 | 115 | #### 2.3.0 (January 21, 2017) 116 | * Uses the latest Crosswalk 23 stable version by default 117 | 118 | #### 2.2.0 (November 4, 2016) 119 | * Uses the latest Crosswalk 22 stable version by default 120 | * Keep compatible for Cordova-android 6.0 with evaluating Javascript bridge 121 | * This version requires cordova-android 6.0.0 or newer 122 | 123 | #### 2.1.0 (September 9, 2016) 124 | * Uses the latest Crosswalk 21 stable version by default 125 | 126 | #### 2.0.0 (August 17, 2016) 127 | * Uses the latest Crosswalk 20 stable version by default 128 | * Discontinue support for Android 4.0 (ICS) in Crosswalk starting with version 20 129 | 130 | #### 1.8.0 (June 30, 2016) 131 | * Uses the latest Crosswalk 19 stable version by default 132 | 133 | #### 1.7.0 (May 4, 2016) 134 | * Uses the latest Crosswalk 18 stable version by default 135 | * Support to use [Crosswalk Lite](https://crosswalk-project.org/documentation/crosswalk_lite.html), It's possible to specify lite value with the variable of XWALK_MODE at install plugin time. 136 | * [Cordova screenshot plugin](https://github.com/gitawego/cordova-screenshot.git) can capture the visible content of web page with Crosswalk library. 137 | * Doesn't work with Crosswalk 17 and earlier 138 | 139 | #### 1.6.0 (March 11, 2016) 140 | * Uses the latest Crosswalk 17 stable version by default 141 | * Support to [package apps for 64-bit devices](https://crosswalk-project.org/documentation/android/android_64bit.html), it's possible to specify 64-bit targets using the `--xwalk64bit` option in the build command: 142 | 143 | cordova build android --xwalk64bit 144 | 145 | #### 1.5.0 (January 18, 2016) 146 | * Uses the latest Crosswalk 16 stable version by default 147 | * The message of xwalk's ready can be listened 148 | 149 | #### 1.4.0 (November 5, 2015) 150 | * Uses the latest Crosswalk 15 stable version by default 151 | * Support User Agent and Background Color configuration preferences 152 | * Compatible with the newest Cordova version 5.3.4 153 | 154 | #### 1.3.0 (August 28, 2015) 155 | * Crosswalk variables can be configured as an option via CLI 156 | * Support for [Crosswalk's shared mode](https://crosswalk-project.org/documentation/shared_mode.html) via the XWALK_MODE install variable or xwalkMode preference 157 | * Uses the latest Crosswalk 14 stable version by default 158 | * The ANIMATABLE_XWALK_VIEW preference is false by default 159 | * Doesn't work with Crosswalk 14.43.343.17 and earlier 160 | 161 | #### 1.2.0 (April 22, 2015) 162 | * Made Crosswalk command-line configurable via `` 163 | * Disabled pull-down-to-refresh by default 164 | 165 | #### 1.1.0 (April 21, 2015) 166 | * Based on Crosswalk v13 167 | * Made Crosswalk version configurable via `` 168 | 169 | #### 1.0.0 (Mar 25, 2015) 170 | * Initial release 171 | * Based on Crosswalk v11 172 | -------------------------------------------------------------------------------- /IONIC4-README.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 23 | [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&identifier=104773211)](https://dependabot.com) 24 | [![npm](https://img.shields.io/npm/v/cordova-plugin-ionic-webview.svg)](https://www.npmjs.com/package/cordova-plugin-ionic-webview) 25 | 26 | # Ionic Web View for Cordova 27 | 28 | A Web View plugin for Cordova, focused on providing the highest performance experience for Ionic apps (but can be used with any Cordova app). 29 | 30 | This plugin uses WKWebView on iOS and the latest evergreen webview on Android. Additionally, this plugin makes it easy to use HTML5 style routing that web developers expect for building single-page apps. 31 | 32 | Note: This repo and its documentation are for `cordova-plugin-ionic-webview` @ `4.x`, which uses the new features that may not work with all apps. See [Requirements](#plugin-requirements) and [Migrating to 4.x](#migrating-to-4x). 33 | 34 | 2.x documentation can be found [here](https://github.com/ionic-team/cordova-plugin-ionic-webview/blob/2.x/README.md). 35 | 36 | :book: **Documentation**: [https://beta.ionicframework.com/docs/building/webview][ionic-webview-docs] 37 | 38 | :mega: **Support/Questions?** Please see our [Support Page][ionic-support] for general support questions. The issues on GitHub should be reserved for bug reports and feature requests. 39 | 40 | :sparkling_heart: **Want to contribute?** Please see [CONTRIBUTING.md](https://github.com/ionic-team/cordova-plugin-ionic-webview/blob/master/CONTRIBUTING.md). 41 | 42 | ## Configuration 43 | 44 | This plugin has several configuration options that can be set in `config.xml`. 45 | 46 | ### Android and iOS Preferences 47 | 48 | Preferences available for both iOS and Android 49 | 50 | #### Hostname 51 | 52 | `` 53 | 54 | Default value is `localhost`. 55 | 56 | Example `ionic://app` on iOS, `http://app` on Android. 57 | 58 | If you change it, you'll need to add a new `allow-navigation` entry in the `config.xml` for the configured url (i.e `` if `Hostname` is set to `app`). 59 | This is only needed for the Android url when using `http://`, `https://` or a custom scheme. All `ionic://` urls are whitelisted by the plugin. 60 | 61 | ### Android Preferences 62 | 63 | Preferences only available Android platform 64 | 65 | #### Scheme 66 | 67 | ```xml 68 | 69 | ``` 70 | 71 | Default value is `http` 72 | 73 | Configures the Scheme the app uses to load the content. 74 | 75 | 76 | #### MixedContentMode 77 | 78 | ```xml 79 | 80 | ``` 81 | 82 | Configures the WebView's behavior when an origin attempts to load a resource from a different origin. 83 | 84 | Default value is `0` (`MIXED_CONTENT_ALWAYS_ALLOW`), which allows loading resources from other origins. 85 | 86 | Other possible values are `1` (`MIXED_CONTENT_NEVER_ALLOW`) and `2` (`MIXED_CONTENT_COMPATIBILITY_MODE`) 87 | 88 | 89 | [Android documentation](https://developer.android.com/reference/android/webkit/WebSettings.html#setMixedContentMode(int)) 90 | 91 | 92 | ### iOS Preferences 93 | 94 | Preferences only available for iOS platform 95 | 96 | #### iosScheme 97 | 98 | ```xml 99 | 100 | ``` 101 | 102 | Default value is `ionic` 103 | 104 | Configures the Scheme the app uses to load the content. 105 | 106 | Values like `http`, `https` or `file` are not valid and will use default value instead. 107 | 108 | If you change it, you'll need to add a new `allow-navigation` entry in the `config.xml` for the configured scheme (i.e `` if `iosScheme` is set to `httpsionic`). 109 | 110 | #### WKSuspendInBackground 111 | 112 | ```xml 113 | 114 | ``` 115 | 116 | Default value is `true` (suspend). 117 | 118 | Set to false to stop WKWebView suspending in background too eagerly. 119 | 120 | #### KeyboardAppearanceDark 121 | 122 | ```xml 123 | 124 | ``` 125 | 126 | Whether to use a dark styled keyboard on iOS 127 | 128 | ## Plugin Requirements 129 | 130 | * **Cordova CLI**: 7.1.0+ 131 | * **iOS**: iOS 11+ and `cordova-ios` 4+ 132 | * **Android**: Android 4.4+ and `cordova-android` 6.4+ 133 | 134 | ## Migrating to 4.x 135 | 136 | 1. Remove and re-add the Web View plugin: 137 | 138 | ``` 139 | cordova plugin rm cordova-plugin-ionic-webview 140 | cordova plugin add cordova-plugin-ionic-webview@latest 141 | ``` 142 | 143 | 1. Apps are now served from HTTP on Android by default. 144 | 145 | * The default origin for requests from the Android WebView is `http://localhost`. If `Hostname` and `Scheme` preferences are set, then origin will be `schemeValue://HostnameValue`. 146 | 147 | 1. Apps are now served from `ionic://` scheme on iOS by default. 148 | 149 | * The default origin for requests from the iOS WebView is `ionic://localhost`. If `Hostname` and `iosScheme` preferences are set, then origin will be `iosSchemeValue://HostnameValue`. 150 | 151 | 1. The WebView is not able to display images, videos or other files from file or content protocols or if it doesn't have protocol at all. For those cases use `window.Ionic.WebView.convertFileSrc()` to get the proper url. 152 | 153 | 1. Replace any usages of `window.Ionic.normalizeURL()` with `window.Ionic.WebView.convertFileSrc()`. 154 | 155 | * For Ionic Angular projects, there is an [Ionic Native wrapper](https://beta.ionicframework.com/docs/native/ionic-webview): 156 | 157 | ``` 158 | npm install @ionic-native/ionic-webview@beta 159 | ``` 160 | 161 | [ionic-homepage]: https://ionicframework.com 162 | [ionic-docs]: https://ionicframework.com/docs 163 | [ionic-webview-docs]: https://beta.ionicframework.com/docs/building/webview 164 | [ionic-support]: https://ionicframework.com/support 165 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 该插件的功能是在`cordova ionic4`项目中使用`cordova-plugin-crosswalk-webview`浏览器内核。 4 | 5 | **↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓** 6 | 7 | > `目前只支持cordova-android@8.0.0或以下平台,高于cordova-android@8.0.0将出现启动空白情况` 8 | 9 | 10 | ## 介绍 11 | 12 | 使用了`cordova-plugin-crosswalk-webview`作为 cordova 浏览器内核,cordova使用`file:///`协议访问app内置的html页面,但由于`angular` `ionic4`中无法兼容`file:///` 协议。 13 | 14 | 因此在ionic4中,ionic官方提供了一个插件`cordova-plugin-ionic-webview`,将`file:///`协议替换成了`http://`协议,以兼容`angular`框架,但是该插件调用的`android`系统自带的浏览器,且无法同时兼容`cordova-plugin-crosswalk-webview`插件。 15 | 16 | 为了能在`ionic4`项目中同时使用`cordova-plugin-crosswalk-webview`,可以安装此插件。 17 | 18 | ## 安装 19 | 20 | 使用前需卸载`cordova-plugin-ionic-webview`与`cordova-plugin-crosswalk-webview` 21 | 22 | ``` shell 23 | cordova plugin remove cordova-plugin-crosswalk-webview 24 | cordova plugin remove cordova-plugin-ionic-webview 25 | ``` 26 | 27 | #### 安装插件 28 | 29 | 30 | **`android sdk 24 以下`,使用`crosswalk webview`浏览器内核; 31 | `android sdk 24及以上`,使用安卓系统内置的高版本浏览器内核。** 32 | ``` shell 33 | cordova plugin add cordova-plugin-ionic4-crosswalk-webview --variable WEBVIEW_ENGINE=AUTO 34 | ``` 35 | --- 36 | 37 | **始终使用CrosswalkWebView内核** 38 | 39 | ``` shell 40 | cordova plugin add cordova-plugin-ionic4-crosswalk-webview --variable WEBVIEW_ENGINE=CROSSWALK 41 | ``` 42 | 43 | --- 44 | 45 | **始终使用SystemWebView内核** 46 | ``` shell 47 | cordova plugin add cordova-plugin-ionic4-crosswalk-webview --variable WEBVIEW_ENGINE=SYSTEM 48 | ``` 49 | 50 | 51 | ## 如何使用 52 | 53 | 在你的`cordova`启动页面index.html中写以下脚本 54 | 55 | ``` html 56 | 57 | 63 | 64 | ``` 65 | 66 | #### 切换内核 67 | 68 | 69 | ``` js 70 | //在下次启动APP时使用CrosswalkWebView内核后 71 | Ionic.WebView.useCrosswalkWebViewAtTheNextStartup(); 72 | ``` 73 | 74 | ``` js 75 | //在下次启动APP时使用SystemkWebView内核 76 | Ionic.WebView.useSystemWebViewAtTheNextStartup(); 77 | ``` 78 | 79 | ## 如何调试 80 | 81 | 由于crosswalk编译成了不同架构的apk,因此原来的命令`ionic cordova run android --emulator`不再适用,请改用以下命令调试apk 82 | 83 | ``` shell 84 | ng run app:ionic-cordova-build --platform=android && cordova run android --emulator 85 | ``` 86 | 87 | 可以使用npm封装命令,在`package.json`中 88 | 89 | ``` json 90 | { 91 | "scripts": { 92 | "ng": "ng", 93 | "start": "ng serve", 94 | "build": "ng build", 95 | "test": "ng test", 96 | "lint": "ng lint", 97 | "e2e": "ng e2e", 98 | "debug": "ng run app:ionic-cordova-build --platform=android && cordova run android --emulator" 99 | } 100 | } 101 | ``` 102 | 103 | 使用npm命令调试 104 | 105 | ``` shell 106 | npm run debug 107 | ``` 108 | 109 | ## Demo 110 | 111 | 你可以参考示例 112 | `https://github.com/waitaction/cordova-plugin-ionic4-crosswalk-demo` 113 | -------------------------------------------------------------------------------- /hooks/after_build/000-build_64_bit.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | module.exports = function (context) { 4 | var p = new Promise(function (resolve, reject) { 5 | var UpdateConfig = require('./../update_config.js'); 6 | var updateConfig = new UpdateConfig(context); 7 | updateConfig.afterBuild64bit(); 8 | resolve(); 9 | }); 10 | return p; 11 | }; 12 | -------------------------------------------------------------------------------- /hooks/after_plugin_install/000-shared_mode_special.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | module.exports = function (context) { 4 | var p = new Promise(function (resolve, reject) { 5 | var UpdateConfig = require('./../update_config.js'); 6 | var updateConfig = new UpdateConfig(context); 7 | updateConfig.addPreferences(); 8 | resolve(); 9 | }) 10 | return p; 11 | }; 12 | -------------------------------------------------------------------------------- /hooks/before_build/000-build_64_bit.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | module.exports = function (context) { 4 | var p = new Promise(function (resolve, reject) { 5 | var UpdateConfig = require('./../update_config.js'); 6 | var updateConfig = new UpdateConfig(context); 7 | updateConfig.beforeBuild64bit(); 8 | resolve(); 9 | }) 10 | return p; 11 | }; 12 | -------------------------------------------------------------------------------- /hooks/before_plugin_uninstall/000-shared_mode_special.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | module.exports = function(context) { 4 | var p = new Promise(function (resolve, reject) { 5 | var UpdateConfig = require('./../update_config.js'); 6 | var updateConfig = new UpdateConfig(context); 7 | updateConfig.removePreferences(); 8 | resolve(); 9 | }) 10 | return p; 11 | }; 12 | -------------------------------------------------------------------------------- /hooks/update_config.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | module.exports = function(context) { 4 | 5 | var ConfigParser, XmlHelpers; 6 | try { 7 | // cordova-lib >= 5.3.4 doesn't contain ConfigParser and xml-helpers anymore 8 | ConfigParser = context.requireCordovaModule("cordova-common").ConfigParser; 9 | XmlHelpers = context.requireCordovaModule("cordova-common").xmlHelpers; 10 | } catch (e) { 11 | ConfigParser = context.requireCordovaModule("cordova-lib/src/configparser/ConfigParser"); 12 | XmlHelpers = context.requireCordovaModule("cordova-lib/src/util/xml-helpers"); 13 | } 14 | 15 | /** @external */ 16 | var fs = require('fs'), 17 | path = require('path'), 18 | et = require('elementtree'); 19 | 20 | /** @defaults */ 21 | var xwalkVariables = {}, 22 | argumentsString = context.cmdLine, 23 | pluginConfigurationFile = path.join(context.opts.plugin.dir, 'plugin.xml'), 24 | androidPlatformDir = path.join(context.opts.projectRoot, 25 | 'platforms', 'android'), 26 | projectConfigurationFile = path.join(context.opts.projectRoot, 27 | 'config.xml'), 28 | platformConfigurationFile, 29 | projectManifestFile = path.join(androidPlatformDir, 30 | 'AndroidManifest.xml'), 31 | xwalk64bit = "xwalk64bit", 32 | xwalkLiteVersion = "", 33 | specificVersion = false; 34 | 35 | var oldConfigXMLLocation = path.join(androidPlatformDir, 'res', 'xml', 'config.xml'); 36 | var newConfigXMLLocation = path.join(androidPlatformDir, 'app', 'src', 'main', 'res', 'xml', 'config.xml'); 37 | 38 | if (fs.existsSync(newConfigXMLLocation)) { 39 | // cordova-android >= 7.0.0 40 | platformConfigurationFile = newConfigXMLLocation; 41 | } else { 42 | // cordova-android < 7.0.0 43 | platformConfigurationFile = oldConfigXMLLocation; 44 | } 45 | 46 | /** Init */ 47 | var CordovaConfig = new ConfigParser(platformConfigurationFile); 48 | 49 | var addPermission = function() { 50 | var projectManifestXmlRoot = XmlHelpers.parseElementtreeSync(projectManifestFile); 51 | var child = et.XML(''); 52 | XmlHelpers.graftXML(projectManifestXmlRoot, [child], '/manifest'); 53 | fs.writeFileSync(projectManifestFile, projectManifestXmlRoot.write({indent: 4}), 'utf-8'); 54 | } 55 | 56 | var removePermission = function() { 57 | var projectManifestXmlRoot = XmlHelpers.parseElementtreeSync(projectManifestFile); 58 | var child = et.XML(''); 59 | XmlHelpers.pruneXML(projectManifestXmlRoot, [child], '/manifest'); 60 | fs.writeFileSync(projectManifestFile, projectManifestXmlRoot.write({indent: 4}), 'utf-8'); 61 | } 62 | 63 | var defaultPreferences = function() { 64 | var pluginPreferences = {}; 65 | 66 | var pluginXmlRoot = XmlHelpers.parseElementtreeSync(pluginConfigurationFile), 67 | tagName = "preference", 68 | containerName = "config-file", 69 | targetPlatform = 'android', 70 | targetPlatformTag = pluginXmlRoot.find('./platform[@name="' + targetPlatform + '"]'); 71 | 72 | var tagsInRoot = pluginXmlRoot.findall(tagName) || [], 73 | tagsInPlatform = targetPlatformTag ? targetPlatformTag.findall(tagName) : [], 74 | tagsInContainer = targetPlatformTag ? targetPlatformTag.findall(containerName) : [], 75 | tagsList = tagsInRoot.concat(tagsInContainer); 76 | 77 | // Parses tags within -blocks 78 | tagsList.map(function(prefTag) { 79 | prefTag.getchildren().forEach(function(element) { 80 | if ((element.tag == 'preference') && (element.attrib['name']) && element.attrib['default']) { 81 | // Don't add xwalkLiteVersion in the app/config.xml 82 | if (element.attrib['name'] == "xwalkLiteVersion") { 83 | xwalkLiteVersion = element.attrib['default']; 84 | } else { 85 | pluginPreferences[element.attrib['name']] = element.attrib['default']; 86 | } 87 | } 88 | }); 89 | }); 90 | 91 | return pluginPreferences; 92 | } 93 | 94 | /** The style of name align with config.xml */ 95 | var setConfigPreference = function(name, value) { 96 | var trimName = name.replace('_', ''); 97 | for (var localName in xwalkVariables) { 98 | if (localName.toUpperCase() == trimName.toUpperCase()) { 99 | xwalkVariables[localName] = value; 100 | if (localName == 'xwalkVersion') { 101 | specificVersion = true; 102 | } 103 | } 104 | } 105 | } 106 | 107 | /** Pase the cli command to get the specific preference*/ 108 | var parseCliPreference = function() { 109 | var commandlineVariablesList = argumentsString.split('--variable'); 110 | if (commandlineVariablesList) { 111 | commandlineVariablesList.forEach(function(element) { 112 | element = element.trim(); 113 | if(element && element.indexOf('XWALK') == 0) { 114 | var preference = element.split('='); 115 | if (preference && preference.length == 2) { 116 | setConfigPreference(preference[0], preference[1]); 117 | } 118 | } 119 | }); 120 | } 121 | } 122 | 123 | /** Add preference */ 124 | this.addPreferences = function() { 125 | // Pick the xwalk variables with the cli preferences 126 | // parseCliPreference(); 127 | 128 | // Add the permission of writing external storage when using shared mode 129 | if (CordovaConfig.getGlobalPreference('xwalkMode') == 'shared') { 130 | addPermission(); 131 | } 132 | 133 | // Configure the final value in the config.xml 134 | // var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile); 135 | // var preferenceUpdated = false; 136 | // for (var name in xwalkVariables) { 137 | // var child = configXmlRoot.find('./preference[@name="' + name + '"]'); 138 | // if(!child) { 139 | // preferenceUpdated = true; 140 | // child = et.XML(''); 141 | // XmlHelpers.graftXML(configXmlRoot, [child], '/*'); 142 | // } 143 | // } 144 | // if(preferenceUpdated) { 145 | // fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8'); 146 | // } 147 | } 148 | 149 | /** Remove preference*/ 150 | this.removePreferences = function() { 151 | if (CordovaConfig.getGlobalPreference('xwalkMode') == 'shared') { 152 | // Add the permission of write_external_storage in shared mode 153 | removePermission(); 154 | } 155 | 156 | // var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile); 157 | // for (var name in xwalkVariables) { 158 | // var child = configXmlRoot.find('./preference[@name="' + name + '"]'); 159 | // if (child) { 160 | // XmlHelpers.pruneXML(configXmlRoot, [child], '/*'); 161 | // } 162 | // } 163 | // fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8'); 164 | } 165 | 166 | var build64bit = function() { 167 | var build64bit = false; 168 | var commandlineVariablesList = argumentsString.split('--'); 169 | 170 | if (commandlineVariablesList) { 171 | commandlineVariablesList.forEach(function(element) { 172 | element = element.trim(); 173 | if(element && element.indexOf(xwalk64bit) == 0) { 174 | build64bit = true; 175 | } 176 | }); 177 | } 178 | return build64bit; 179 | } 180 | 181 | this.beforeBuild64bit = function() { 182 | if(build64bit()) { 183 | var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile); 184 | var child = configXmlRoot.find('./preference[@name="' + xwalk64bit + '"]'); 185 | if(!child) { 186 | child = et.XML(''); 187 | XmlHelpers.graftXML(configXmlRoot, [child], '/*'); 188 | fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8'); 189 | } 190 | } 191 | } 192 | 193 | this.afterBuild64bit = function() { 194 | if(build64bit()) { 195 | var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile); 196 | var child = configXmlRoot.find('./preference[@name="' + xwalk64bit + '"]'); 197 | if (child) { 198 | XmlHelpers.pruneXML(configXmlRoot, [child], '/*'); 199 | fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8'); 200 | } 201 | } 202 | 203 | console.log("Crosswalk info:"); 204 | console.log(" After much discussion and analysis of the market,"); 205 | console.log(" we have decided to discontinue support for Android 4.0 (ICS) in Crosswalk starting with version 20,"); 206 | console.log(" so the minSdkVersion of Cordova project is configured to 16 by default. \n"); 207 | } 208 | 209 | xwalkVariables = defaultPreferences(); 210 | 211 | }; 212 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-ionic4-crosswalk-webview", 3 | "version": "4.2.0", 4 | "description": "Ionic Crosswalk Web View Engine Plugin", 5 | "scripts": { 6 | "sync_plugin_xml": "sync-cordova-xml package.json plugin.xml --output=plugin.xml", 7 | "version": "npm run sync_plugin_xml && git add plugin.xml", 8 | "cz": "git-cz" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/waitaction/cordova-plugin-ionic4-crosswalk-webview" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/waitaction/cordova-plugin-ionic4-crosswalk-webview/issues" 16 | }, 17 | "keywords": [ 18 | "cordova", 19 | "wkwebview" 20 | ], 21 | "author": "Ionic Team && CrosswalkWebview Team", 22 | "license": "Apache-2.0", 23 | "engines": { 24 | "cordovaDependencies": { 25 | "2.0.0": { 26 | "cordova-android": ">=6.4.0", 27 | "cordova-ios": ">=4.0.0-dev" 28 | }, 29 | "3.1.0": { 30 | "cordova-android": ">=6.4.0", 31 | "cordova-ios": ">=4.0.0-dev", 32 | "cordova": ">=7.1.0" 33 | } 34 | } 35 | }, 36 | "devDependencies": { 37 | "@semantic-release/changelog": "^3.0.0", 38 | "@semantic-release/exec": "^3.3.0", 39 | "@semantic-release/git": "^7.0.4", 40 | "@semantic-release/github": "^5.0.6", 41 | "@semantic-release/npm": "^5.0.4", 42 | "commitizen": "^3.0.2", 43 | "cz-conventional-changelog": "^2.1.0", 44 | "semantic-release": "^15.9.17", 45 | "sync-cordova-xml": "^0.4.0" 46 | }, 47 | "release": { 48 | "branch": "stable", 49 | "verifyConditions": [ 50 | "@semantic-release/changelog", 51 | "@semantic-release/npm", 52 | "@semantic-release/git", 53 | "@semantic-release/github" 54 | ], 55 | "prepare": [ 56 | "@semantic-release/changelog", 57 | "@semantic-release/npm", 58 | "@semantic-release/exec", 59 | "@semantic-release/git" 60 | ], 61 | "publish": [ 62 | "@semantic-release/github", 63 | "@semantic-release/npm" 64 | ], 65 | "success": [ 66 | "@semantic-release/github" 67 | ], 68 | "failure": [ 69 | "@semantic-release/github" 70 | ], 71 | "prepareCmd": "npm run version" 72 | }, 73 | "config": { 74 | "commitizen": { 75 | "path": "./node_modules/cz-conventional-changelog" 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | cordova-plugin-ionic4-crosswalk-webview 4 | Ionic Crosswalk Web View Engine Plugin 5 | Apache-2.0 6 | cordova,wkwebview 7 | https://github.com/waitaction/cordova-plugin-ionic4-crosswalk-webview 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | https://github.com/waitaction/cordova-plugin-ionic4-crosswalk-webview/issues 97 | Ionic Team And Crosswalk Team 98 | -------------------------------------------------------------------------------- /src/android/com/ionicframework/cordova/webview/AndroidProtocolHandler.java: -------------------------------------------------------------------------------- 1 | package com.ionicframework.cordova.webview; 2 | 3 | // Copyright 2012 The Chromium Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style license that can be 5 | // found in the LICENSE file. 6 | 7 | import android.content.Context; 8 | import android.content.res.AssetManager; 9 | import android.net.Uri; 10 | import android.util.Log; 11 | import android.util.TypedValue; 12 | 13 | import java.io.File; 14 | import java.io.FileInputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.util.List; 18 | 19 | 20 | public class AndroidProtocolHandler { 21 | private static final String TAG = "AndroidProtocolHandler"; 22 | 23 | private Context context; 24 | 25 | public AndroidProtocolHandler(Context context) { 26 | this.context = context; 27 | } 28 | 29 | public InputStream openAsset(String path) throws IOException { 30 | return context.getAssets().open(path, AssetManager.ACCESS_STREAMING); 31 | } 32 | 33 | public InputStream openResource(Uri uri) { 34 | assert uri.getPath() != null; 35 | // The path must be of the form ".../asset_type/asset_name.ext". 36 | List pathSegments = uri.getPathSegments(); 37 | String assetType = pathSegments.get(pathSegments.size() - 2); 38 | String assetName = pathSegments.get(pathSegments.size() - 1); 39 | 40 | // Drop the file extension. 41 | assetName = assetName.split("\\.")[0]; 42 | try { 43 | // Use the application context for resolving the resource package name so that we do 44 | // not use the browser's own resources. Note that if 'context' here belongs to the 45 | // test suite, it does not have a separate application context. In that case we use 46 | // the original context object directly. 47 | if (context.getApplicationContext() != null) { 48 | context = context.getApplicationContext(); 49 | } 50 | int fieldId = getFieldId(context, assetType, assetName); 51 | int valueType = getValueType(context, fieldId); 52 | if (valueType == TypedValue.TYPE_STRING) { 53 | return context.getResources().openRawResource(fieldId); 54 | } else { 55 | Log.e(TAG, "Asset not of type string: " + uri); 56 | return null; 57 | } 58 | } catch (ClassNotFoundException e) { 59 | Log.e(TAG, "Unable to open resource URL: " + uri, e); 60 | return null; 61 | } catch (NoSuchFieldException e) { 62 | Log.e(TAG, "Unable to open resource URL: " + uri, e); 63 | return null; 64 | } catch (IllegalAccessException e) { 65 | Log.e(TAG, "Unable to open resource URL: " + uri, e); 66 | return null; 67 | } 68 | } 69 | 70 | public InputStream openFile(String filePath) throws IOException { 71 | String realPath = filePath.replace(WebViewLocalServer.fileStart, ""); 72 | File localFile = new File(realPath); 73 | return new FileInputStream(localFile); 74 | } 75 | 76 | public InputStream openContentUrl(Uri uri) throws IOException { 77 | Integer port = uri.getPort(); 78 | String realPath; 79 | if (port == -1) { 80 | realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + WebViewLocalServer.contentStart, "content:/"); 81 | } else { 82 | realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + port + WebViewLocalServer.contentStart, "content:/"); 83 | } 84 | InputStream stream = null; 85 | try { 86 | stream = context.getContentResolver().openInputStream(Uri.parse(realPath)); 87 | } catch (SecurityException e) { 88 | Log.e(TAG, "Unable to open content URL: " + uri, e); 89 | } 90 | return stream; 91 | } 92 | 93 | private static int getFieldId(Context context, String assetType, String assetName) 94 | throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { 95 | Class d = context.getClassLoader() 96 | .loadClass(context.getPackageName() + ".R$" + assetType); 97 | java.lang.reflect.Field field = d.getField(assetName); 98 | int id = field.getInt(null); 99 | return id; 100 | } 101 | 102 | private static int getValueType(Context context, int fieldId) { 103 | TypedValue value = new TypedValue(); 104 | context.getResources().getValue(fieldId, value, true); 105 | return value.type; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/android/com/ionicframework/cordova/webview/IonicWebView.java: -------------------------------------------------------------------------------- 1 | package com.ionicframework.cordova.webview; 2 | 3 | import android.app.Activity; 4 | import android.content.SharedPreferences; 5 | 6 | import org.apache.cordova.CallbackContext; 7 | import org.apache.cordova.CordovaPlugin; 8 | import org.json.JSONArray; 9 | import org.json.JSONException; 10 | 11 | public class IonicWebView extends CordovaPlugin { 12 | 13 | public static final String WEBVIEW_PREFS_NAME = "WebViewSettings"; 14 | public static final String CDV_SERVER_PATH = "serverBasePath"; 15 | 16 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 17 | 18 | if (action.equals("setServerBasePath")) { 19 | final String path = args.getString(0); 20 | cordova.getActivity().runOnUiThread(new Runnable() { 21 | public void run() { 22 | ((IonicWebViewEngine)webView.getEngine()).setServerBasePath(path); 23 | } 24 | }); 25 | return true; 26 | } else if (action.equals("getServerBasePath")) { 27 | callbackContext.success(((IonicWebViewEngine)webView.getEngine()).getServerBasePath()); 28 | return true; 29 | } else if (action.equals("persistServerBasePath")) { 30 | String path = ((IonicWebViewEngine)webView.getEngine()).getServerBasePath(); 31 | SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE); 32 | SharedPreferences.Editor editor = prefs.edit(); 33 | editor.putString(CDV_SERVER_PATH, path); 34 | editor.apply(); 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/android/com/ionicframework/cordova/webview/IonicWebViewEngine.java: -------------------------------------------------------------------------------- 1 | package com.ionicframework.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.annotation.TargetApi; 8 | import android.graphics.Bitmap; 9 | import android.net.Uri; 10 | import android.os.Build; 11 | import android.support.annotation.RequiresApi; 12 | import android.util.Log; 13 | import android.webkit.WebResourceRequest; 14 | import android.webkit.WebResourceResponse; 15 | import android.webkit.WebSettings; 16 | import android.webkit.WebView; 17 | import org.apache.cordova.ConfigXmlParser; 18 | import org.apache.cordova.CordovaInterface; 19 | import org.apache.cordova.CordovaPreferences; 20 | import org.apache.cordova.CordovaResourceApi; 21 | import org.apache.cordova.CordovaWebView; 22 | import org.apache.cordova.CordovaWebViewEngine; 23 | import org.apache.cordova.NativeToJsMessageQueue; 24 | import org.apache.cordova.PluginManager; 25 | import org.apache.cordova.engine.SystemWebViewClient; 26 | import org.apache.cordova.engine.SystemWebViewEngine; 27 | import org.apache.cordova.engine.SystemWebView; 28 | 29 | public class IonicWebViewEngine extends SystemWebViewEngine { 30 | public static final String TAG = "IonicWebViewEngine"; 31 | 32 | private WebViewLocalServer localServer; 33 | private String CDV_LOCAL_SERVER; 34 | private String scheme; 35 | private static final String LAST_BINARY_VERSION_CODE = "lastBinaryVersionCode"; 36 | private static final String LAST_BINARY_VERSION_NAME = "lastBinaryVersionName"; 37 | 38 | /** 39 | * Used when created via reflection. 40 | */ 41 | public IonicWebViewEngine(Context context, CordovaPreferences preferences) { 42 | super(new SystemWebView(context), preferences); 43 | Log.d(TAG, "Ionic Web View Engine Starting Right Up 1..."); 44 | } 45 | 46 | public IonicWebViewEngine(SystemWebView webView) { 47 | super(webView, null); 48 | Log.d(TAG, "Ionic Web View Engine Starting Right Up 2..."); 49 | } 50 | 51 | public IonicWebViewEngine(SystemWebView webView, CordovaPreferences preferences) { 52 | super(webView, preferences); 53 | Log.d(TAG, "Ionic Web View Engine Starting Right Up 3..."); 54 | } 55 | 56 | @Override 57 | public void init(CordovaWebView parentWebView, CordovaInterface cordova, final CordovaWebViewEngine.Client client, 58 | CordovaResourceApi resourceApi, PluginManager pluginManager, 59 | NativeToJsMessageQueue nativeToJsMessageQueue) { 60 | ConfigXmlParser parser = new ConfigXmlParser(); 61 | parser.parse(cordova.getActivity()); 62 | 63 | String hostname = preferences.getString("Hostname", "localhost"); 64 | scheme = preferences.getString("Scheme", "http"); 65 | CDV_LOCAL_SERVER = scheme + "://" + hostname; 66 | 67 | localServer = new WebViewLocalServer(cordova.getActivity(), hostname, true, parser, scheme); 68 | localServer.hostAssets("www"); 69 | 70 | webView.setWebViewClient(new ServerClient(this, parser)); 71 | 72 | super.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue); 73 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 74 | final WebSettings settings = webView.getSettings(); 75 | int mode = preferences.getInteger("MixedContentMode", 0); 76 | settings.setMixedContentMode(mode); 77 | } 78 | SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE); 79 | String path = prefs.getString(IonicWebView.CDV_SERVER_PATH, null); 80 | if (!isDeployDisabled() && !isNewBinary() && path != null && !path.isEmpty()) { 81 | setServerBasePath(path); 82 | } 83 | } 84 | 85 | private boolean isNewBinary() { 86 | String versionCode = ""; 87 | String versionName = ""; 88 | SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE); 89 | String lastVersionCode = prefs.getString(LAST_BINARY_VERSION_CODE, null); 90 | String lastVersionName = prefs.getString(LAST_BINARY_VERSION_NAME, null); 91 | 92 | try { 93 | PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0); 94 | versionCode = Integer.toString(pInfo.versionCode); 95 | versionName = pInfo.versionName; 96 | } catch(Exception ex) { 97 | Log.e(TAG, "Unable to get package info", ex); 98 | } 99 | 100 | if (!versionCode.equals(lastVersionCode) || !versionName.equals(lastVersionName)) { 101 | SharedPreferences.Editor editor = prefs.edit(); 102 | editor.putString(LAST_BINARY_VERSION_CODE, versionCode); 103 | editor.putString(LAST_BINARY_VERSION_NAME, versionName); 104 | editor.putString(IonicWebView.CDV_SERVER_PATH, ""); 105 | editor.apply(); 106 | return true; 107 | } 108 | return false; 109 | } 110 | 111 | private boolean isDeployDisabled() { 112 | return preferences.getBoolean("DisableDeploy", false); 113 | } 114 | private class ServerClient extends SystemWebViewClient { 115 | private ConfigXmlParser parser; 116 | 117 | public ServerClient(SystemWebViewEngine parentEngine, ConfigXmlParser parser) { 118 | super(parentEngine); 119 | this.parser = parser; 120 | } 121 | 122 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 123 | @Override 124 | public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 125 | return localServer.shouldInterceptRequest(request.getUrl(), request); 126 | } 127 | 128 | @TargetApi(Build.VERSION_CODES.KITKAT) 129 | @Override 130 | public WebResourceResponse shouldInterceptRequest(WebView view, String url) { 131 | return localServer.shouldInterceptRequest(Uri.parse(url), null); 132 | } 133 | 134 | @Override 135 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 136 | super.onPageStarted(view, url, favicon); 137 | String launchUrl = parser.getLaunchUrl(); 138 | if (!launchUrl.contains(WebViewLocalServer.httpsScheme) && !launchUrl.contains(WebViewLocalServer.httpScheme) && url.equals(launchUrl)) { 139 | view.stopLoading(); 140 | // When using a custom scheme the app won't load if server start url doesn't end in / 141 | String startUrl = CDV_LOCAL_SERVER; 142 | if (!scheme.equalsIgnoreCase(WebViewLocalServer.httpsScheme) && !scheme.equalsIgnoreCase(WebViewLocalServer.httpScheme)) { 143 | startUrl += "/"; 144 | } 145 | view.loadUrl(startUrl); 146 | } 147 | } 148 | 149 | @Override 150 | public void onPageFinished(WebView view, String url) { 151 | super.onPageFinished(view, url); 152 | view.loadUrl("javascript:(function() { " + 153 | "window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "';" + 154 | "})()"); 155 | } 156 | } 157 | 158 | public void setServerBasePath(String path) { 159 | localServer.hostFiles(path); 160 | webView.loadUrl(CDV_LOCAL_SERVER); 161 | } 162 | 163 | public String getServerBasePath() { 164 | return this.localServer.getBasePath(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/android/com/ionicframework/cordova/webview/UriMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ionicframework.cordova.webview; 2 | 3 | /* 4 | * Copyright (C) 2006 The Android Open Source Project 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * 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, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | //package com.google.webviewlocalserver.third_party.android; 19 | 20 | import android.net.Uri; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.regex.Pattern; 25 | 26 | public class UriMatcher { 27 | /** 28 | * Creates the root node of the URI tree. 29 | * 30 | * @param code the code to match for the root URI 31 | */ 32 | public UriMatcher(Object code) { 33 | mCode = code; 34 | mWhich = -1; 35 | mChildren = new ArrayList(); 36 | mText = null; 37 | } 38 | 39 | private UriMatcher() { 40 | mCode = null; 41 | mWhich = -1; 42 | mChildren = new ArrayList(); 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; --------------------------------------------------------------------------------