Initial Commit ...
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
version: 2
|
||||
|
||||
aliases:
|
||||
- &restore-cache
|
||||
keys:
|
||||
- dependency-cache-{{ checksum "package.json" }}-1
|
||||
|
||||
- &save-cache
|
||||
key: dependency-cache-{{ checksum "package.json" }}-1
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: /tmp/workspace
|
||||
|
||||
jobs:
|
||||
build:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache: *restore-cache
|
||||
- run: npm install
|
||||
- save_cache: *save-cache
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- "*"
|
||||
|
||||
deploy:
|
||||
<<: *defaults
|
||||
environment:
|
||||
GIT_AUTHOR_NAME: Ionitron
|
||||
GIT_AUTHOR_EMAIL: hi@ionicframework.com
|
||||
GIT_COMMITTER_NAME: Ionitron
|
||||
GIT_COMMITTER_EMAIL: hi@ionicframework.com
|
||||
steps:
|
||||
- add_ssh_keys:
|
||||
fingerprints:
|
||||
- "ae:6d:3a:f1:cf:39:e1:94:6e:22:2a:9f:54:f9:b0:1b" # ionitron user key
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run: npx semantic-release
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- build
|
||||
- deploy:
|
||||
requires: [build]
|
||||
filters:
|
||||
branches:
|
||||
only: stable
|
||||
@@ -0,0 +1,248 @@
|
||||
## [4.2.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v4.2.0...v4.2.1) (2020-04-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ios:** release userAgentLock for IAB compat ([#558](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/558)) ([4c027f3](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/4c027f3e1dadf790b1d699936b90b670b401db9e)), closes [#551](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/551)
|
||||
|
||||
# [4.2.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v4.1.3...v4.2.0) (2020-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ionassethandler.m:** fix startPath is getting null ([#463](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/463)) ([0bf16f1](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/0bf16f1b73b853f40781c5de83964457cc4493d5))
|
||||
* **ios:** avoid app scrolling to top on keyboard hide ([#533](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/533)) ([7974eb4](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/7974eb4160f5e83cf4b3e98905beba1f874464a6))
|
||||
* **ios:** Replace deprecated APIs ([#539](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/539)) ([27b9021](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/27b9021d5d76b3e6dc6bfc83ab46b98cd301e694))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **android:** proxy service worker requests through local server ([#452](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/452)) ([c672175](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/c672175b7527d64b077f7715b2ff145325524add))
|
||||
* **ios:** implement custom userAgent handling ([#537](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/537)) ([8587114](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/85871147ba8e5b23b693e518bf5ea800cccce8cc))
|
||||
|
||||
## [4.1.3](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v4.1.2...v4.1.3) (2019-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **android:** return proper mimeType for .mjs files ([#455](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/455)) ([173a313](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/173a313))
|
||||
* **ios:** mitigate media memory usage ([#459](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/459)) ([cbd526d](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/cbd526d))
|
||||
* **ios:** remove itms-services private scheme ([#464](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/464)) ([d7d2600](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/d7d2600))
|
||||
|
||||
## [4.1.2](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v4.1.1...v4.1.2) (2019-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **android:** allow schemes that start by https ([#437](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/437)) ([fab9d1f](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/fab9d1f))
|
||||
* **Android:** return proper mimeType for wasm files ([0eb8a37](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/0eb8a37))
|
||||
* **ios:** make programmatically focus work on iOS 13 ([#438](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/438)) ([7a514b0](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/7a514b0)), closes [#435](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/435)
|
||||
|
||||
## [4.1.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v4.1.0...v4.1.1) (2019-06-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ios:** show error message when app fails to load ([#382](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/382)) ([cb1f026](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/cb1f026))
|
||||
|
||||
# [4.1.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v4.0.1...v4.1.0) (2019-06-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ios:** Add WKSuspendInBackground preference ([#356](https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/356)) ([3613602](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/3613602))
|
||||
|
||||
## [4.0.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v4.0.0...v4.0.1) (2019-03-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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)
|
||||
* 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))
|
||||
|
||||
# [4.0.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v3.1.2...v4.0.0) (2019-02-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **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)
|
||||
* **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)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **ios:** Remove the WKSuspendInBackground preference, so app relying on that prefere will
|
||||
not behave as expected
|
||||
|
||||
## [3.1.2](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v3.1.1...v3.1.2) (2019-02-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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)
|
||||
|
||||
## [3.1.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v3.1.0...v3.1.1) (2019-01-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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))
|
||||
|
||||
# [3.1.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v3.0.0...v3.1.0) (2019-01-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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)
|
||||
* 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)
|
||||
* 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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **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)
|
||||
|
||||
# [3.0.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.3.1...v3.0.0) (2019-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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)
|
||||
* **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)
|
||||
* **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))
|
||||
* **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)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **iOS:** Sets deployment-target to 11, so will only work on iOS 11+
|
||||
|
||||
* Address changes
|
||||
* changes the default from 1 (never) to 0 (always)
|
||||
* **WebViewLocalServer.java:** Until now, the Android part of the plugin was returning a 200 http code even though
|
||||
the requested file didn't exist. This behavior was inconsistent with the historical behavior of the
|
||||
iOS webView. This change makes them both work in the same manner but introduces a breaking change
|
||||
for the current Android users that are expecting a 200 http code no matter what and are testing the
|
||||
not found error just by checking if the body is null.
|
||||
|
||||
## [2.3.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.3.0...v2.3.1) (2018-12-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
# [2.3.0](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.5...v2.3.0) (2018-12-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **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))
|
||||
|
||||
## [2.2.5](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.4...v2.2.5) (2018-11-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
## [2.2.4](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.3...v2.2.4) (2018-11-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
## [2.2.3](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.2...v2.2.3) (2018-11-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remove main and fix description ([d52db66](https://github.com/ionic-team/cordova-plugin-ionic-webview/commit/d52db66))
|
||||
|
||||
## [2.2.2](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.1...v2.2.2) (2018-11-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
## [2.2.1](https://github.com/ionic-team/cordova-plugin-ionic-webview/compare/v2.2.0...v2.2.1) (2018-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
<a name="2.2.0"></a>
|
||||
### 2.2.0 (2018-10-04)
|
||||
|
||||
* 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))
|
||||
* Add kitkat support (API 19) ([#144](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/144)) [@leo6104](https://github.com/leo6104)
|
||||
* 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))
|
||||
|
||||
<a name="2.1.4"></a>
|
||||
### 2.1.4 (2018-09-13)
|
||||
|
||||
* Allow Ionic Deploy `DisableDeploy` preference to disable loading of deploy updates ([#172](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/172))
|
||||
|
||||
<a name="2.1.3"></a>
|
||||
### 2.1.3 (2018-09-06)
|
||||
|
||||
* Make server path relative ([#164](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/164))
|
||||
|
||||
<a name="2.1.2"></a>
|
||||
### 2.1.2 (2018-09-05)
|
||||
|
||||
* Return 404 response when file doesn't exist ([#162](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/162))
|
||||
* Load local assets if the app is a freshly installed binary ([#155](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/155))
|
||||
* Reset stored server path on new binary ([#161](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/161))
|
||||
|
||||
<a name="2.1.1"></a>
|
||||
### 2.1.1 (2018-09-04)
|
||||
|
||||
* Allow range requests for local files ([#154](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/154))
|
||||
|
||||
<a name="2.1.0"></a>
|
||||
### 2.1.0 (2018-08-23)
|
||||
|
||||
* Add support for `cordova-android` 6 ([#150](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/150))
|
||||
|
||||
<a name="2.0.3"></a>
|
||||
### 2.0.3 (2018-08-14)
|
||||
|
||||
* 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)
|
||||
* 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)
|
||||
|
||||
<a name="2.0.2"></a>
|
||||
### 2.0.2 (2018-07-30)
|
||||
|
||||
* Immediately load new server base path upon setting it. ([#132](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/132))
|
||||
|
||||
<a name="2.0.1"></a>
|
||||
### 2.0.1 (2018-07-25)
|
||||
|
||||
* Avoid "not modified" response on iOS by always overriding last modified date. ([#127](https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/127))
|
||||
|
||||
<a name="2.0.0"></a>
|
||||
### 2.0.0 (2018-07-23)
|
||||
|
||||
* **BREAKING**: HTTP server now runs for iOS **and** Android, instead of just iOS. The server is configured the same for both platforms.
|
||||
* **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`.
|
||||
* **BREAKING**: HTTP server is configured to run in HTML5 routing mode (push state) by default.
|
||||
* **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`.
|
||||
* `window.Ionic.normalizeURL()` has been deprecated. Use `window.Ionic.WebView.convertFileSrc()`.
|
||||
* iOS update HTTP server to latest upstream version (GCDwebserve 3.4.2)
|
||||
* iOS update HTTP server to restart sockets with error state when resuming from background
|
||||
* iOS enable HTTP server to continue running in background if the webview is running.
|
||||
* 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.
|
||||
* iOS add config.xml options:
|
||||
* 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
|
||||
* WKPort - defaults to 8080, define the port that the HTTP server will listen on
|
||||
* 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
|
||||
|
||||
See [Github releases](https://github.com/ionic-team/cordova-plugin-ionic-webview/releases) for earlier changes.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Contributing
|
||||
|
||||
: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.
|
||||
|
||||
### Bug Reports
|
||||
|
||||
Please create an issue describing the bug in detail.
|
||||
|
||||
### Feature Requests
|
||||
|
||||
Please create an issue!
|
||||
|
||||
## Developing
|
||||
|
||||
Please familiarize yourself with [Cordova plugin development](https://cordova.apache.org/docs/en/latest/guide/hybrid/plugins/).
|
||||
|
||||
You can use `cordova plugin add` with a local directory to copy and compile plugin changes into a test project.
|
||||
|
||||
### Workflow
|
||||
|
||||
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.
|
||||
|
||||
* Work off of `master` branch (create new branch or fork)
|
||||
* Make changes
|
||||
* Use `npm run cz` (or `git cz` if [commitizen](https://github.com/commitizen/cz-cli) is installed globally) to make commits
|
||||
* Create a pull request
|
||||
* Pull requests will be approved and squashed into the `master` branch
|
||||
* 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.)
|
||||
|
||||
### Publishing
|
||||
|
||||
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)).
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,181 @@
|
||||
<!--
|
||||
# license: Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
-->
|
||||
|
||||
<!-- TODO: remove beta in README.md and CONTRIBUTING.md -->
|
||||
|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||
[](https://dependabot.com)
|
||||
[](https://www.npmjs.com/package/cordova-plugin-ionic-webview)
|
||||
|
||||
# Ionic Web View for Cordova
|
||||
|
||||
A Web View plugin for Cordova, focused on providing the highest performance experience for Ionic apps (but can be used with any Cordova app).
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
2.x documentation can be found [here](https://github.com/ionic-team/cordova-plugin-ionic-webview/blob/2.x/README.md).
|
||||
|
||||
:book: **Documentation**: [https://beta.ionicframework.com/docs/building/webview][ionic-webview-docs]
|
||||
|
||||
: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.
|
||||
|
||||
:sparkling_heart: **Want to contribute?** Please see [CONTRIBUTING.md](https://github.com/ionic-team/cordova-plugin-ionic-webview/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## Configuration
|
||||
|
||||
This plugin has several configuration options that can be set in `config.xml`.
|
||||
|
||||
### Android and iOS Preferences
|
||||
|
||||
Preferences available for both iOS and Android
|
||||
|
||||
#### Hostname
|
||||
|
||||
`<preference name="Hostname" value="app" />`
|
||||
|
||||
Default value is `localhost`.
|
||||
|
||||
Example `ionic://app` on iOS, `http://app` on Android.
|
||||
|
||||
If you change it, you'll need to add a new `allow-navigation` entry in the `config.xml` for the configured url (i.e `<allow-navigation href="http://app/*"/>` if `Hostname` is set to `app`).
|
||||
This is only needed for the Android url when using `http://`, `https://` or a custom scheme. All `ionic://` urls are whitelisted by the plugin.
|
||||
|
||||
### Android Preferences
|
||||
|
||||
Preferences only available Android platform
|
||||
|
||||
#### Scheme
|
||||
|
||||
```xml
|
||||
<preference name="Scheme" value="https" />
|
||||
```
|
||||
|
||||
Default value is `http`
|
||||
|
||||
Configures the Scheme the app uses to load the content.
|
||||
|
||||
#### ResolveServiceWorkerRequests
|
||||
|
||||
```xml
|
||||
<preference name="ResolveServiceWorkerRequests" value="true" />
|
||||
```
|
||||
|
||||
Default value is `false`
|
||||
|
||||
Enable to resolve requests made by Service Workers through the local server.
|
||||
|
||||
#### MixedContentMode
|
||||
|
||||
```xml
|
||||
<preference name="MixedContentMode" value="2" />
|
||||
```
|
||||
|
||||
Configures the WebView's behavior when an origin attempts to load a resource from a different origin.
|
||||
|
||||
Default value is `0` (`MIXED_CONTENT_ALWAYS_ALLOW`), which allows loading resources from other origins.
|
||||
|
||||
Other possible values are `1` (`MIXED_CONTENT_NEVER_ALLOW`) and `2` (`MIXED_CONTENT_COMPATIBILITY_MODE`)
|
||||
|
||||
|
||||
[Android documentation](https://developer.android.com/reference/android/webkit/WebSettings.html#setMixedContentMode(int))
|
||||
|
||||
|
||||
### iOS Preferences
|
||||
|
||||
Preferences only available for iOS platform
|
||||
|
||||
#### iosScheme
|
||||
|
||||
```xml
|
||||
<preference name="iosScheme" value="httpsionic" />
|
||||
```
|
||||
|
||||
Default value is `ionic`
|
||||
|
||||
Configures the Scheme the app uses to load the content.
|
||||
|
||||
Values like `http`, `https` or `file` are not valid and will use default value instead.
|
||||
|
||||
If you change it, you'll need to add a new `allow-navigation` entry in the `config.xml` for the configured scheme (i.e `<allow-navigation href="httpsionic://*"/>` if `iosScheme` is set to `httpsionic`).
|
||||
|
||||
#### WKSuspendInBackground
|
||||
|
||||
```xml
|
||||
<preference name="WKSuspendInBackground" value="false" />
|
||||
```
|
||||
|
||||
Default value is `true` (suspend).
|
||||
|
||||
Set to false to stop WKWebView suspending in background too eagerly.
|
||||
|
||||
#### KeyboardAppearanceDark
|
||||
|
||||
```xml
|
||||
<preference name="KeyboardAppearanceDark" value="false" />
|
||||
```
|
||||
|
||||
Whether to use a dark styled keyboard on iOS
|
||||
|
||||
#### ScrollEnabled
|
||||
|
||||
```xml
|
||||
<preference name="ScrollEnabled" value="true" />
|
||||
```
|
||||
|
||||
Ionic apps work better if the WKWebView is not scrollable, so the scroll is disabled by default, but can be enabled with this preference. This only affects the main ScrollView of the WKWebView, so only affects the body, not other scrollable components.
|
||||
|
||||
## Plugin Requirements
|
||||
|
||||
* **Cordova CLI**: 7.1.0+
|
||||
* **iOS**: iOS 11+ and `cordova-ios` 4+
|
||||
* **Android**: Android 4.4+ and `cordova-android` 6.4+
|
||||
|
||||
## Migrating to 4.x
|
||||
|
||||
1. Remove and re-add the Web View plugin:
|
||||
|
||||
```
|
||||
cordova plugin rm cordova-plugin-ionic-webview
|
||||
cordova plugin add cordova-plugin-ionic-webview@latest
|
||||
```
|
||||
|
||||
1. Apps are now served from HTTP on Android by default.
|
||||
|
||||
* 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`.
|
||||
|
||||
1. Apps are now served from `ionic://` scheme on iOS by default.
|
||||
|
||||
* 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`.
|
||||
|
||||
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.
|
||||
|
||||
1. Replace any usages of `window.Ionic.normalizeURL()` with `window.Ionic.WebView.convertFileSrc()`.
|
||||
|
||||
* For Ionic Angular projects, there is an [Ionic Native wrapper](https://beta.ionicframework.com/docs/native/ionic-webview):
|
||||
|
||||
```
|
||||
npm install @ionic-native/ionic-webview@beta
|
||||
```
|
||||
|
||||
[ionic-homepage]: https://ionicframework.com
|
||||
[ionic-docs]: https://ionicframework.com/docs
|
||||
[ionic-webview-docs]: https://beta.ionicframework.com/docs/building/webview
|
||||
[ionic-support]: https://ionicframework.com/support
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"_from": "cordova-plugin-ionic-webview@^4.0.0",
|
||||
"_id": "cordova-plugin-ionic-webview@4.2.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-7KrmqLaOGq1RP8N2z1ezN1kqkWFzTwwMvQ3/qAkd+exxFZuOe3DIN4eaU1gdNphsxdirI8Ajnr9q4So5vQbWqw==",
|
||||
"_location": "/cordova-plugin-ionic-webview",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "cordova-plugin-ionic-webview@^4.0.0",
|
||||
"name": "cordova-plugin-ionic-webview",
|
||||
"escapedName": "cordova-plugin-ionic-webview",
|
||||
"rawSpec": "^4.0.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^4.0.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#DEV:/",
|
||||
"#USER"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/cordova-plugin-ionic-webview/-/cordova-plugin-ionic-webview-4.2.1.tgz",
|
||||
"_shasum": "42a67bef248472659d421494feb8245ef27becfa",
|
||||
"_spec": "cordova-plugin-ionic-webview@^4.0.0",
|
||||
"_where": "/home/saherelm/Documents/My Projects/xFrameworkTest",
|
||||
"author": {
|
||||
"name": "Ionic Team"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/ionic-team/cordova-plugin-ionic-webview/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Ionic Web View Engine Plugin",
|
||||
"devDependencies": {
|
||||
"@semantic-release/changelog": "^3.0.0",
|
||||
"@semantic-release/exec": "^3.3.0",
|
||||
"@semantic-release/git": "^7.0.4",
|
||||
"@semantic-release/github": "^5.0.6",
|
||||
"@semantic-release/npm": "^5.0.4",
|
||||
"commitizen": "^4.0.3",
|
||||
"cz-conventional-changelog": "^3.0.2",
|
||||
"semantic-release": "^15.9.17",
|
||||
"sync-cordova-xml": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"cordovaDependencies": {
|
||||
"2.0.0": {
|
||||
"cordova-android": ">=6.4.0",
|
||||
"cordova-ios": ">=4.0.0-dev"
|
||||
},
|
||||
"3.1.0": {
|
||||
"cordova": ">=7.1.0",
|
||||
"cordova-android": ">=6.4.0",
|
||||
"cordova-ios": ">=4.0.0-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
"homepage": "https://github.com/ionic-team/cordova-plugin-ionic-webview#readme",
|
||||
"keywords": [
|
||||
"cordova",
|
||||
"wkwebview"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"name": "cordova-plugin-ionic-webview",
|
||||
"release": {
|
||||
"branch": "stable",
|
||||
"verifyConditions": [
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
"@semantic-release/git",
|
||||
"@semantic-release/github"
|
||||
],
|
||||
"prepare": [
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
"@semantic-release/exec",
|
||||
"@semantic-release/git"
|
||||
],
|
||||
"publish": [
|
||||
"@semantic-release/github",
|
||||
"@semantic-release/npm"
|
||||
],
|
||||
"success": [
|
||||
"@semantic-release/github"
|
||||
],
|
||||
"failure": [
|
||||
"@semantic-release/github"
|
||||
],
|
||||
"prepareCmd": "npm run version"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ionic-team/cordova-plugin-ionic-webview.git"
|
||||
},
|
||||
"scripts": {
|
||||
"cz": "git-cz",
|
||||
"sync_plugin_xml": "sync-cordova-xml package.json plugin.xml --output=plugin.xml",
|
||||
"version": "npm run sync_plugin_xml && git add plugin.xml"
|
||||
},
|
||||
"version": "4.2.1"
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:rim="http://www.blackberry.com/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-ionic-webview" version="4.2.1">
|
||||
<name>cordova-plugin-ionic-webview</name>
|
||||
<description>Ionic Web View Engine Plugin</description>
|
||||
<license>Apache-2.0</license>
|
||||
<keywords>cordova,wkwebview</keywords>
|
||||
<repo>https://github.com/ionic-team/cordova-plugin-ionic-webview</repo>
|
||||
|
||||
<engines>
|
||||
<engine name="cordova" version=">=7.1.0"/>
|
||||
<engine name="cordova-ios" version=">=4.0.0-dev"/>
|
||||
<engine name="apple-ios" version=">=11.0"/>
|
||||
<engine name="cordova-android" version=">=6.4.0"/>
|
||||
</engines>
|
||||
|
||||
<js-module src="src/www/util.js" name="IonicWebView">
|
||||
<clobbers target="Ionic.WebView"/>
|
||||
</js-module>
|
||||
|
||||
<platform name="android">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<allow-navigation href="http://localhost/*"/>
|
||||
<allow-navigation href="https://localhost/*"/>
|
||||
<allow-navigation href="ionic://*"/>
|
||||
<preference name="webView" value="com.ionicframework.cordova.webview.IonicWebViewEngine"/>
|
||||
<feature name="IonicWebView">
|
||||
<param name="android-package" value="com.ionicframework.cordova.webview.IonicWebView"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
<source-file src="src/android/com/ionicframework/cordova/webview/IonicWebViewEngine.java" target-dir="src/com/ionicframework/cordova/webview"/>
|
||||
<source-file src="src/android/com/ionicframework/cordova/webview/IonicWebView.java" target-dir="src/com/ionicframework/cordova/webview"/>
|
||||
<source-file src="src/android/com/ionicframework/cordova/webview/AndroidProtocolHandler.java" target-dir="src/com/ionicframework/cordova/webview"/>
|
||||
<source-file src="src/android/com/ionicframework/cordova/webview/UriMatcher.java" target-dir="src/com/ionicframework/cordova/webview"/>
|
||||
<source-file src="src/android/com/ionicframework/cordova/webview/WebViewLocalServer.java" target-dir="src/com/ionicframework/cordova/webview"/>
|
||||
<preference name="ANDROID_SUPPORT_ANNOTATIONS_VERSION" default="27.+"/>
|
||||
<framework src="com.android.support:support-annotations:$ANDROID_SUPPORT_ANNOTATIONS_VERSION"/>
|
||||
</platform>
|
||||
|
||||
<!-- ios -->
|
||||
<platform name="ios">
|
||||
<js-module src="src/www/ios/ios-wkwebview-exec.js" name="ios-wkwebview-exec">
|
||||
<clobbers target="cordova.exec"/>
|
||||
</js-module>
|
||||
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<allow-navigation href="ionic://*"/>
|
||||
<preference name="deployment-target" value="11.0"/>
|
||||
<feature name="IonicWebView">
|
||||
<param name="ios-package" value="CDVWKWebViewEngine"/>
|
||||
</feature>
|
||||
<preference name="CordovaWebViewEngine" value="CDVWKWebViewEngine"/>
|
||||
</config-file>
|
||||
|
||||
<framework src="WebKit.framework" weak="true"/>
|
||||
|
||||
<header-file src="src/ios/CDVWKWebViewEngine.h"/>
|
||||
<source-file src="src/ios/CDVWKWebViewEngine.m"/>
|
||||
<header-file src="src/ios/CDVWKWebViewUIDelegate.h"/>
|
||||
<source-file src="src/ios/CDVWKWebViewUIDelegate.m"/>
|
||||
<header-file src="src/ios/CDVWKProcessPoolFactory.h"/>
|
||||
<source-file src="src/ios/CDVWKProcessPoolFactory.m"/>
|
||||
<header-file src="src/ios/IONAssetHandler.h"/>
|
||||
<source-file src="src/ios/IONAssetHandler.m"/>
|
||||
<asset src="src/ios/wk-plugin.js" target="wk-plugin.js"/>
|
||||
</platform>
|
||||
<issue>https://github.com/ionic-team/cordova-plugin-ionic-webview/issues</issue>
|
||||
<author>Ionic Team</author>
|
||||
</plugin>
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package com.ionicframework.cordova.webview;
|
||||
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class AndroidProtocolHandler {
|
||||
private static final String TAG = "AndroidProtocolHandler";
|
||||
|
||||
private Context context;
|
||||
|
||||
public AndroidProtocolHandler(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public InputStream openAsset(String path) throws IOException {
|
||||
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
|
||||
}
|
||||
|
||||
public InputStream openResource(Uri uri) {
|
||||
assert uri.getPath() != null;
|
||||
// The path must be of the form ".../asset_type/asset_name.ext".
|
||||
List<String> pathSegments = uri.getPathSegments();
|
||||
String assetType = pathSegments.get(pathSegments.size() - 2);
|
||||
String assetName = pathSegments.get(pathSegments.size() - 1);
|
||||
|
||||
// Drop the file extension.
|
||||
assetName = assetName.split("\\.")[0];
|
||||
try {
|
||||
// Use the application context for resolving the resource package name so that we do
|
||||
// not use the browser's own resources. Note that if 'context' here belongs to the
|
||||
// test suite, it does not have a separate application context. In that case we use
|
||||
// the original context object directly.
|
||||
if (context.getApplicationContext() != null) {
|
||||
context = context.getApplicationContext();
|
||||
}
|
||||
int fieldId = getFieldId(context, assetType, assetName);
|
||||
int valueType = getValueType(context, fieldId);
|
||||
if (valueType == TypedValue.TYPE_STRING) {
|
||||
return context.getResources().openRawResource(fieldId);
|
||||
} else {
|
||||
Log.e(TAG, "Asset not of type string: " + uri);
|
||||
return null;
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
Log.e(TAG, "Unable to open resource URL: " + uri, e);
|
||||
return null;
|
||||
} catch (NoSuchFieldException e) {
|
||||
Log.e(TAG, "Unable to open resource URL: " + uri, e);
|
||||
return null;
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(TAG, "Unable to open resource URL: " + uri, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream openFile(String filePath) throws IOException {
|
||||
String realPath = filePath.replace(WebViewLocalServer.fileStart, "");
|
||||
File localFile = new File(realPath);
|
||||
return new FileInputStream(localFile);
|
||||
}
|
||||
|
||||
public InputStream openContentUrl(Uri uri) throws IOException {
|
||||
Integer port = uri.getPort();
|
||||
String realPath;
|
||||
if (port == -1) {
|
||||
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + WebViewLocalServer.contentStart, "content:/");
|
||||
} else {
|
||||
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + port + WebViewLocalServer.contentStart, "content:/");
|
||||
}
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = context.getContentResolver().openInputStream(Uri.parse(realPath));
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Unable to open content URL: " + uri, e);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
private static int getFieldId(Context context, String assetType, String assetName)
|
||||
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
|
||||
Class<?> d = context.getClassLoader()
|
||||
.loadClass(context.getPackageName() + ".R$" + assetType);
|
||||
java.lang.reflect.Field field = d.getField(assetName);
|
||||
int id = field.getInt(null);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static int getValueType(Context context, int fieldId) {
|
||||
TypedValue value = new TypedValue();
|
||||
context.getResources().getValue(fieldId, value, true);
|
||||
return value.type;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.ionicframework.cordova.webview;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
public class IonicWebView extends CordovaPlugin {
|
||||
|
||||
public static final String WEBVIEW_PREFS_NAME = "WebViewSettings";
|
||||
public static final String CDV_SERVER_PATH = "serverBasePath";
|
||||
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||
|
||||
if (action.equals("setServerBasePath")) {
|
||||
final String path = args.getString(0);
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
((IonicWebViewEngine)webView.getEngine()).setServerBasePath(path);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else if (action.equals("getServerBasePath")) {
|
||||
callbackContext.success(((IonicWebViewEngine)webView.getEngine()).getServerBasePath());
|
||||
return true;
|
||||
} else if (action.equals("persistServerBasePath")) {
|
||||
String path = ((IonicWebViewEngine)webView.getEngine()).getServerBasePath();
|
||||
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(CDV_SERVER_PATH, path);
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
package com.ionicframework.cordova.webview;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
import android.webkit.ServiceWorkerController;
|
||||
import android.webkit.ServiceWorkerClient;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import org.apache.cordova.ConfigXmlParser;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaPreferences;
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaWebViewEngine;
|
||||
import org.apache.cordova.NativeToJsMessageQueue;
|
||||
import org.apache.cordova.PluginManager;
|
||||
import org.apache.cordova.engine.SystemWebViewClient;
|
||||
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||
import org.apache.cordova.engine.SystemWebView;
|
||||
|
||||
public class IonicWebViewEngine extends SystemWebViewEngine {
|
||||
public static final String TAG = "IonicWebViewEngine";
|
||||
|
||||
private WebViewLocalServer localServer;
|
||||
private String CDV_LOCAL_SERVER;
|
||||
private String scheme;
|
||||
private static final String LAST_BINARY_VERSION_CODE = "lastBinaryVersionCode";
|
||||
private static final String LAST_BINARY_VERSION_NAME = "lastBinaryVersionName";
|
||||
|
||||
/**
|
||||
* Used when created via reflection.
|
||||
*/
|
||||
public IonicWebViewEngine(Context context, CordovaPreferences preferences) {
|
||||
super(new SystemWebView(context), preferences);
|
||||
Log.d(TAG, "Ionic Web View Engine Starting Right Up 1...");
|
||||
}
|
||||
|
||||
public IonicWebViewEngine(SystemWebView webView) {
|
||||
super(webView, null);
|
||||
Log.d(TAG, "Ionic Web View Engine Starting Right Up 2...");
|
||||
}
|
||||
|
||||
public IonicWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
|
||||
super(webView, preferences);
|
||||
Log.d(TAG, "Ionic Web View Engine Starting Right Up 3...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(CordovaWebView parentWebView, CordovaInterface cordova, final CordovaWebViewEngine.Client client,
|
||||
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||
NativeToJsMessageQueue nativeToJsMessageQueue) {
|
||||
ConfigXmlParser parser = new ConfigXmlParser();
|
||||
parser.parse(cordova.getActivity());
|
||||
|
||||
String hostname = preferences.getString("Hostname", "localhost");
|
||||
scheme = preferences.getString("Scheme", "http");
|
||||
CDV_LOCAL_SERVER = scheme + "://" + hostname;
|
||||
|
||||
localServer = new WebViewLocalServer(cordova.getActivity(), hostname, true, parser, scheme);
|
||||
localServer.hostAssets("www");
|
||||
|
||||
webView.setWebViewClient(new ServerClient(this, parser));
|
||||
|
||||
super.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue);
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
final WebSettings settings = webView.getSettings();
|
||||
int mode = preferences.getInteger("MixedContentMode", 0);
|
||||
settings.setMixedContentMode(mode);
|
||||
}
|
||||
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
|
||||
String path = prefs.getString(IonicWebView.CDV_SERVER_PATH, null);
|
||||
if (!isDeployDisabled() && !isNewBinary() && path != null && !path.isEmpty()) {
|
||||
setServerBasePath(path);
|
||||
}
|
||||
|
||||
boolean setAsServiceWorkerClient = preferences.getBoolean("ResolveServiceWorkerRequests", false);
|
||||
ServiceWorkerController controller = null;
|
||||
|
||||
if (setAsServiceWorkerClient && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
controller = ServiceWorkerController.getInstance();
|
||||
controller.setServiceWorkerClient(new ServiceWorkerClient(){
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
|
||||
return localServer.shouldInterceptRequest(request.getUrl(), request);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNewBinary() {
|
||||
String versionCode = "";
|
||||
String versionName = "";
|
||||
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
|
||||
String lastVersionCode = prefs.getString(LAST_BINARY_VERSION_CODE, null);
|
||||
String lastVersionName = prefs.getString(LAST_BINARY_VERSION_NAME, null);
|
||||
|
||||
try {
|
||||
PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0);
|
||||
versionCode = Integer.toString(pInfo.versionCode);
|
||||
versionName = pInfo.versionName;
|
||||
} catch(Exception ex) {
|
||||
Log.e(TAG, "Unable to get package info", ex);
|
||||
}
|
||||
|
||||
if (!versionCode.equals(lastVersionCode) || !versionName.equals(lastVersionName)) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(LAST_BINARY_VERSION_CODE, versionCode);
|
||||
editor.putString(LAST_BINARY_VERSION_NAME, versionName);
|
||||
editor.putString(IonicWebView.CDV_SERVER_PATH, "");
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isDeployDisabled() {
|
||||
return preferences.getBoolean("DisableDeploy", false);
|
||||
}
|
||||
private class ServerClient extends SystemWebViewClient {
|
||||
private ConfigXmlParser parser;
|
||||
|
||||
public ServerClient(SystemWebViewEngine parentEngine, ConfigXmlParser parser) {
|
||||
super(parentEngine);
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
return localServer.shouldInterceptRequest(request.getUrl(), request);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
return localServer.shouldInterceptRequest(Uri.parse(url), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
String launchUrl = parser.getLaunchUrl();
|
||||
if (!launchUrl.contains(WebViewLocalServer.httpsScheme) && !launchUrl.contains(WebViewLocalServer.httpScheme) && url.equals(launchUrl)) {
|
||||
view.stopLoading();
|
||||
// When using a custom scheme the app won't load if server start url doesn't end in /
|
||||
String startUrl = CDV_LOCAL_SERVER;
|
||||
if (!scheme.equalsIgnoreCase(WebViewLocalServer.httpsScheme) && !scheme.equalsIgnoreCase(WebViewLocalServer.httpScheme)) {
|
||||
startUrl += "/";
|
||||
}
|
||||
view.loadUrl(startUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
view.loadUrl("javascript:(function() { " +
|
||||
"window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "';" +
|
||||
"})()");
|
||||
}
|
||||
}
|
||||
|
||||
public void setServerBasePath(String path) {
|
||||
localServer.hostFiles(path);
|
||||
webView.loadUrl(CDV_LOCAL_SERVER);
|
||||
}
|
||||
|
||||
public String getServerBasePath() {
|
||||
return this.localServer.getBasePath();
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
package com.ionicframework.cordova.webview;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//package com.google.webviewlocalserver.third_party.android;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class UriMatcher {
|
||||
/**
|
||||
* Creates the root node of the URI tree.
|
||||
*
|
||||
* @param code the code to match for the root URI
|
||||
*/
|
||||
public UriMatcher(Object code) {
|
||||
mCode = code;
|
||||
mWhich = -1;
|
||||
mChildren = new ArrayList<UriMatcher>();
|
||||
mText = null;
|
||||
}
|
||||
|
||||
private UriMatcher() {
|
||||
mCode = null;
|
||||
mWhich = -1;
|
||||
mChildren = new ArrayList<UriMatcher>();
|
||||
mText = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a URI to match, and the code to return when this URI is
|
||||
* matched. URI nodes may be exact match string, the token "*"
|
||||
* that matches any text, or the token "#" that matches only
|
||||
* numbers.
|
||||
* <p>
|
||||
* Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
|
||||
* this method will accept a leading slash in the path.
|
||||
*
|
||||
* @param authority the authority to match
|
||||
* @param path the path to match. * may be used as a wild card for
|
||||
* any text, and # may be used as a wild card for numbers.
|
||||
* @param code the code that is returned when a URI is matched
|
||||
* against the given components. Must be positive.
|
||||
*/
|
||||
public void addURI(String scheme, String authority, String path, Object code) {
|
||||
if (code == null) {
|
||||
throw new IllegalArgumentException("Code can't be null");
|
||||
}
|
||||
|
||||
String[] tokens = null;
|
||||
if (path != null) {
|
||||
String newPath = path;
|
||||
// Strip leading slash if present.
|
||||
if (path.length() > 0 && path.charAt(0) == '/') {
|
||||
newPath = path.substring(1);
|
||||
}
|
||||
tokens = PATH_SPLIT_PATTERN.split(newPath);
|
||||
}
|
||||
|
||||
int numTokens = tokens != null ? tokens.length : 0;
|
||||
UriMatcher node = this;
|
||||
for (int i = -2; i < numTokens; i++) {
|
||||
String token;
|
||||
if (i == -2)
|
||||
token = scheme;
|
||||
else if (i == -1)
|
||||
token = authority;
|
||||
else
|
||||
token = tokens[i];
|
||||
ArrayList<UriMatcher> children = node.mChildren;
|
||||
int numChildren = children.size();
|
||||
UriMatcher child;
|
||||
int j;
|
||||
for (j = 0; j < numChildren; j++) {
|
||||
child = children.get(j);
|
||||
if (token.equals(child.mText)) {
|
||||
node = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == numChildren) {
|
||||
// Child not found, create it
|
||||
child = new UriMatcher();
|
||||
if (token.equals("**")) {
|
||||
child.mWhich = REST;
|
||||
} else if (token.equals("*")) {
|
||||
child.mWhich = TEXT;
|
||||
} else {
|
||||
child.mWhich = EXACT;
|
||||
}
|
||||
child.mText = token;
|
||||
node.mChildren.add(child);
|
||||
node = child;
|
||||
}
|
||||
}
|
||||
node.mCode = code;
|
||||
}
|
||||
|
||||
static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/");
|
||||
|
||||
/**
|
||||
* Try to match against the path in a url.
|
||||
*
|
||||
* @param uri The url whose path we will match against.
|
||||
* @return The code for the matched node (added using addURI),
|
||||
* or null if there is no matched node.
|
||||
*/
|
||||
public Object match(Uri uri) {
|
||||
final List<String> pathSegments = uri.getPathSegments();
|
||||
final int li = pathSegments.size();
|
||||
|
||||
UriMatcher node = this;
|
||||
|
||||
if (li == 0 && uri.getAuthority() == null) {
|
||||
return this.mCode;
|
||||
}
|
||||
|
||||
for (int i = -2; i < li; i++) {
|
||||
String u;
|
||||
if (i == -2)
|
||||
u = uri.getScheme();
|
||||
else if (i == -1)
|
||||
u = uri.getAuthority();
|
||||
else
|
||||
u = pathSegments.get(i);
|
||||
ArrayList<UriMatcher> list = node.mChildren;
|
||||
if (list == null) {
|
||||
break;
|
||||
}
|
||||
node = null;
|
||||
int lj = list.size();
|
||||
for (int j = 0; j < lj; j++) {
|
||||
UriMatcher n = list.get(j);
|
||||
which_switch:
|
||||
switch (n.mWhich) {
|
||||
case EXACT:
|
||||
if (n.mText.equals(u)) {
|
||||
node = n;
|
||||
}
|
||||
break;
|
||||
case TEXT:
|
||||
node = n;
|
||||
break;
|
||||
case REST:
|
||||
return n.mCode;
|
||||
}
|
||||
if (node != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return node.mCode;
|
||||
}
|
||||
|
||||
private static final int EXACT = 0;
|
||||
private static final int TEXT = 1;
|
||||
private static final int REST = 2;
|
||||
|
||||
private Object mCode;
|
||||
private int mWhich;
|
||||
private String mText;
|
||||
private ArrayList<UriMatcher> mChildren;
|
||||
}
|
||||
+645
@@ -0,0 +1,645 @@
|
||||
/*
|
||||
Copyright 2015 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package com.ionicframework.cordova.webview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
|
||||
import org.apache.cordova.ConfigXmlParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Helper class meant to be used with the android.webkit.WebView class to enable hosting assets,
|
||||
* resources and other data on 'virtual' http(s):// URL.
|
||||
* Hosting assets and resources on http(s):// URLs is desirable as it is compatible with the
|
||||
* Same-Origin policy.
|
||||
* <p>
|
||||
* This class is intended to be used from within the
|
||||
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView, String)} and
|
||||
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
|
||||
* android.webkit.WebResourceRequest)}
|
||||
* methods.
|
||||
*/
|
||||
public class WebViewLocalServer {
|
||||
private static String TAG = "WebViewAssetServer";
|
||||
private String basePath;
|
||||
public final static String httpScheme = "http";
|
||||
public final static String httpsScheme = "https";
|
||||
public final static String fileStart = "/_app_file_";
|
||||
public final static String contentStart = "/_app_content_";
|
||||
|
||||
private final UriMatcher uriMatcher;
|
||||
private final AndroidProtocolHandler protocolHandler;
|
||||
private final String authority;
|
||||
private final String customScheme;
|
||||
// Whether we're serving local files or proxying (for example, when doing livereload on a
|
||||
// non-local endpoint (will be false in that case)
|
||||
private boolean isAsset;
|
||||
// Whether to route all requests to paths without extensions back to `index.html`
|
||||
private final boolean html5mode;
|
||||
private ConfigXmlParser parser;
|
||||
|
||||
public String getAuthority() { return authority; }
|
||||
|
||||
/**
|
||||
* A handler that produces responses for paths on the virtual asset server.
|
||||
* <p>
|
||||
* Methods of this handler will be invoked on a background thread and care must be taken to
|
||||
* correctly synchronize access to any shared state.
|
||||
* <p>
|
||||
* On Android KitKat and above these methods may be called on more than one thread. This thread
|
||||
* may be different than the thread on which the shouldInterceptRequest method was invoke.
|
||||
* This means that on Android KitKat and above it is possible to block in this method without
|
||||
* blocking other resources from loading. The number of threads used to parallelize loading
|
||||
* is an internal implementation detail of the WebView and may change between updates which
|
||||
* means that the amount of time spend blocking in this method should be kept to an absolute
|
||||
* minimum.
|
||||
*/
|
||||
public abstract static class PathHandler {
|
||||
protected String mimeType;
|
||||
private String encoding;
|
||||
private String charset;
|
||||
private int statusCode;
|
||||
private String reasonPhrase;
|
||||
private Map<String, String> responseHeaders;
|
||||
|
||||
public PathHandler() {
|
||||
this(null, null, 200, "OK", null);
|
||||
}
|
||||
|
||||
public PathHandler(String encoding, String charset, int statusCode,
|
||||
String reasonPhrase, Map<String, String> responseHeaders) {
|
||||
this.encoding = encoding;
|
||||
this.charset = charset;
|
||||
this.statusCode = statusCode;
|
||||
this.reasonPhrase = reasonPhrase;
|
||||
Map<String, String> tempResponseHeaders;
|
||||
if (responseHeaders == null) {
|
||||
tempResponseHeaders = new HashMap<String, String>();
|
||||
} else {
|
||||
tempResponseHeaders = responseHeaders;
|
||||
}
|
||||
tempResponseHeaders.put("Cache-Control", "no-cache");
|
||||
this.responseHeaders = tempResponseHeaders;
|
||||
}
|
||||
|
||||
abstract public InputStream handle(Uri url);
|
||||
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public String getReasonPhrase() {
|
||||
return reasonPhrase;
|
||||
}
|
||||
|
||||
public Map<String, String> getResponseHeaders() {
|
||||
return responseHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the URLs used to host the assets in the WebView.
|
||||
*/
|
||||
public static class AssetHostingDetails {
|
||||
private Uri httpPrefix;
|
||||
private Uri httpsPrefix;
|
||||
|
||||
/*package*/ AssetHostingDetails(Uri httpPrefix, Uri httpsPrefix) {
|
||||
this.httpPrefix = httpPrefix;
|
||||
this.httpsPrefix = httpsPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the http: scheme prefix at which assets are hosted.
|
||||
*
|
||||
* @return the http: scheme prefix at which assets are hosted. Can return null.
|
||||
*/
|
||||
public Uri getHttpPrefix() {
|
||||
return httpPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the https: scheme prefix at which assets are hosted.
|
||||
*
|
||||
* @return the https: scheme prefix at which assets are hosted. Can return null.
|
||||
*/
|
||||
public Uri getHttpsPrefix() {
|
||||
return httpsPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
WebViewLocalServer(Context context, String authority, boolean html5mode, ConfigXmlParser parser, String customScheme) {
|
||||
uriMatcher = new UriMatcher(null);
|
||||
this.html5mode = html5mode;
|
||||
this.parser = parser;
|
||||
this.protocolHandler = new AndroidProtocolHandler(context.getApplicationContext());
|
||||
this.authority = authority;
|
||||
this.customScheme = customScheme;
|
||||
}
|
||||
|
||||
private static Uri parseAndVerifyUrl(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
Uri uri = Uri.parse(url);
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Malformed URL: " + url);
|
||||
return null;
|
||||
}
|
||||
String path = uri.getPath();
|
||||
if (path == null || path.length() == 0) {
|
||||
Log.e(TAG, "URL does not have a path: " + url);
|
||||
return null;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
private static WebResourceResponse createWebResourceResponse(String mimeType, String encoding, int statusCode, String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
int finalStatusCode = statusCode;
|
||||
try {
|
||||
if (data.available() == 0) {
|
||||
finalStatusCode = 404;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
finalStatusCode = 500;
|
||||
}
|
||||
return new WebResourceResponse(mimeType, encoding, finalStatusCode, reasonPhrase, responseHeaders, data);
|
||||
} else {
|
||||
return new WebResourceResponse(mimeType, encoding, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to retrieve the WebResourceResponse associated with the given <code>request</code>.
|
||||
* This method should be invoked from within
|
||||
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
|
||||
* android.webkit.WebResourceRequest)}.
|
||||
*
|
||||
* @param uri the request Uri to process.
|
||||
* @return a response if the request URL had a matching handler, null if no handler was found.
|
||||
*/
|
||||
public WebResourceResponse shouldInterceptRequest(Uri uri, WebResourceRequest request) {
|
||||
PathHandler handler;
|
||||
synchronized (uriMatcher) {
|
||||
handler = (PathHandler) uriMatcher.match(uri);
|
||||
}
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isLocalFile(uri) || uri.getAuthority().equals(this.authority)) {
|
||||
Log.d("SERVER", "Handling local request: " + uri.toString());
|
||||
return handleLocalRequest(uri, handler, request);
|
||||
} else {
|
||||
return handleProxyRequest(uri, handler);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLocalFile(Uri uri) {
|
||||
String path = uri.getPath();
|
||||
if (path.startsWith(contentStart) || path.startsWith(fileStart)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private WebResourceResponse handleLocalRequest(Uri uri, PathHandler handler, WebResourceRequest request) {
|
||||
String path = uri.getPath();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && request != null && request.getRequestHeaders().get("Range") != null) {
|
||||
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
|
||||
String mimeType = getMimeType(path, responseStream);
|
||||
Map<String, String> tempResponseHeaders = handler.getResponseHeaders();
|
||||
int statusCode = 206;
|
||||
try {
|
||||
int totalRange = responseStream.available();
|
||||
String rangeString = request.getRequestHeaders().get("Range");
|
||||
String[] parts = rangeString.split("=");
|
||||
String[] streamParts = parts[1].split("-");
|
||||
String fromRange = streamParts[0];
|
||||
int range = totalRange-1;
|
||||
if (streamParts.length > 1) {
|
||||
range = Integer.parseInt(streamParts[1]);
|
||||
}
|
||||
tempResponseHeaders.put("Accept-Ranges", "bytes");
|
||||
tempResponseHeaders.put("Content-Range", "bytes " + fromRange + "-" + range + "/" + totalRange);
|
||||
} catch (IOException e) {
|
||||
statusCode = 404;
|
||||
}
|
||||
return createWebResourceResponse(mimeType, handler.getEncoding(),
|
||||
statusCode, handler.getReasonPhrase(), tempResponseHeaders, responseStream);
|
||||
}
|
||||
if (isLocalFile(uri)) {
|
||||
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
|
||||
String mimeType = getMimeType(path, responseStream);
|
||||
return createWebResourceResponse(mimeType, handler.getEncoding(),
|
||||
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
|
||||
}
|
||||
|
||||
if (path.equals("") || path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
|
||||
InputStream stream;
|
||||
String launchURL = parser.getLaunchUrl();
|
||||
String launchFile = launchURL.substring(launchURL.lastIndexOf("/") + 1, launchURL.length());
|
||||
try {
|
||||
String startPath = this.basePath + "/" + launchFile;
|
||||
if (isAsset) {
|
||||
stream = protocolHandler.openAsset(startPath);
|
||||
} else {
|
||||
stream = protocolHandler.openFile(startPath);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to open " + launchFile, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return createWebResourceResponse("text/html", handler.getEncoding(),
|
||||
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
|
||||
}
|
||||
|
||||
int periodIndex = path.lastIndexOf(".");
|
||||
if (periodIndex >= 0) {
|
||||
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
|
||||
String mimeType = getMimeType(path, responseStream);
|
||||
return createWebResourceResponse(mimeType, handler.getEncoding(),
|
||||
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of reading files from the filesystem/assets, proxy through to the URL
|
||||
* and let an external server handle it.
|
||||
* @param uri
|
||||
* @param handler
|
||||
* @return
|
||||
*/
|
||||
private WebResourceResponse handleProxyRequest(Uri uri, PathHandler handler) {
|
||||
try {
|
||||
String path = uri.getPath();
|
||||
URL url = new URL(uri.toString());
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setReadTimeout(30 * 1000);
|
||||
conn.setConnectTimeout(30 * 1000);
|
||||
|
||||
InputStream stream = conn.getInputStream();
|
||||
|
||||
if (path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
|
||||
return createWebResourceResponse("text/html", handler.getEncoding(),
|
||||
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
|
||||
}
|
||||
|
||||
int periodIndex = path.lastIndexOf(".");
|
||||
if (periodIndex >= 0) {
|
||||
String ext = path.substring(path.lastIndexOf("."), path.length());
|
||||
|
||||
// TODO: Conjure up a bit more subtlety than this
|
||||
if (ext.equals(".html")) {
|
||||
}
|
||||
|
||||
String mimeType = getMimeType(path, stream);
|
||||
|
||||
return createWebResourceResponse(mimeType, handler.getEncoding(),
|
||||
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
|
||||
}
|
||||
|
||||
return createWebResourceResponse("", handler.getEncoding(),
|
||||
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
|
||||
|
||||
} catch (SocketTimeoutException ex) {
|
||||
// bridge.handleAppUrlLoadError(ex);
|
||||
} catch (Exception ex) {
|
||||
// bridge.handleAppUrlLoadError(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getMimeType(String path, InputStream stream) {
|
||||
String mimeType = null;
|
||||
try {
|
||||
mimeType = URLConnection.guessContentTypeFromName(path); // Does not recognize *.js
|
||||
if (mimeType != null && path.endsWith(".js") && mimeType.equals("image/x-icon")) {
|
||||
Log.d(IonicWebViewEngine.TAG, "We shouldn't be here");
|
||||
}
|
||||
if (mimeType == null) {
|
||||
if (path.endsWith(".js") || path.endsWith(".mjs")) {
|
||||
// Make sure JS files get the proper mimetype to support ES modules
|
||||
mimeType = "application/javascript";
|
||||
} else if (path.endsWith(".wasm")) {
|
||||
mimeType = "application/wasm";
|
||||
} else {
|
||||
mimeType = URLConnection.guessContentTypeFromStream(stream);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "Unable to get mime type" + path, ex);
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a handler for the given <code>uri</code>. The <code>handler</code> will be invoked
|
||||
* every time the <code>shouldInterceptRequest</code> method of the instance is called with
|
||||
* a matching <code>uri</code>.
|
||||
*
|
||||
* @param uri the uri to use the handler for. The scheme and authority (domain) will be matched
|
||||
* exactly. The path may contain a '*' element which will match a single element of
|
||||
* a path (so a handler registered for /a/* will be invoked for /a/b and /a/c.html
|
||||
* but not for /a/b/b) or the '**' element which will match any number of path
|
||||
* elements.
|
||||
* @param handler the handler to use for the uri.
|
||||
*/
|
||||
void register(Uri uri, PathHandler handler) {
|
||||
synchronized (uriMatcher) {
|
||||
uriMatcher.addURI(uri.getScheme(), uri.getAuthority(), uri.getPath(), handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hosts the application's assets on an http(s):// URL. Assets from the local path
|
||||
* <code>assetPath/...</code> will be available under
|
||||
* <code>http(s)://{uuid}.androidplatform.net/assets/...</code>.
|
||||
*
|
||||
* @param assetPath the local path in the application's asset folder which will be made
|
||||
* available by the server (for example "/www").
|
||||
*/
|
||||
public void hostAssets(String assetPath) {
|
||||
hostAssets(authority, assetPath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hosts the application's assets on an http(s):// URL. Assets from the local path
|
||||
* <code>assetPath/...</code> will be available under
|
||||
* <code>http(s)://{domain}/{virtualAssetPath}/...</code>.
|
||||
*
|
||||
* @param domain custom domain on which the assets should be hosted (for example "example.com").
|
||||
* @param assetPath the local path in the application's asset folder which will be made
|
||||
* available by the server (for example "/www").
|
||||
* @return prefixes under which the assets are hosted.
|
||||
*/
|
||||
public void hostAssets(final String domain,
|
||||
final String assetPath) {
|
||||
this.isAsset = true;
|
||||
this.basePath = assetPath;
|
||||
|
||||
createHostingDetails();
|
||||
}
|
||||
|
||||
private void createHostingDetails() {
|
||||
final String assetPath = this.basePath;
|
||||
|
||||
if (assetPath.indexOf('*') != -1) {
|
||||
throw new IllegalArgumentException("assetPath cannot contain the '*' character.");
|
||||
}
|
||||
|
||||
PathHandler handler = new PathHandler() {
|
||||
@Override
|
||||
public InputStream handle(Uri url) {
|
||||
InputStream stream = null;
|
||||
String path = url.getPath();
|
||||
try {
|
||||
if (path.startsWith(contentStart)) {
|
||||
stream = protocolHandler.openContentUrl(url);
|
||||
} else if (path.startsWith(fileStart) || !isAsset) {
|
||||
if (!path.startsWith(fileStart)) {
|
||||
path = basePath + url.getPath();
|
||||
}
|
||||
stream = protocolHandler.openFile(path);
|
||||
} else {
|
||||
stream = protocolHandler.openAsset(assetPath + path);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to open asset URL: " + url);
|
||||
return null;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
};
|
||||
|
||||
registerUriForScheme(httpScheme, handler, authority);
|
||||
registerUriForScheme(httpsScheme, handler, authority);
|
||||
if (!customScheme.equals(httpScheme) && !customScheme.equals(httpsScheme)) {
|
||||
registerUriForScheme(customScheme, handler, authority);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void registerUriForScheme(String scheme, PathHandler handler, String authority) {
|
||||
Uri.Builder uriBuilder = new Uri.Builder();
|
||||
uriBuilder.scheme(scheme);
|
||||
uriBuilder.authority(authority);
|
||||
uriBuilder.path("");
|
||||
Uri uriPrefix = uriBuilder.build();
|
||||
|
||||
register(Uri.withAppendedPath(uriPrefix, "/"), handler);
|
||||
register(Uri.withAppendedPath(uriPrefix, "**"), handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hosts the application's resources on an http(s):// URL. Resources
|
||||
* <code>http(s)://{uuid}.androidplatform.net/res/{resource_type}/{resource_name}</code>.
|
||||
*
|
||||
* @return prefixes under which the resources are hosted.
|
||||
*/
|
||||
public AssetHostingDetails hostResources() {
|
||||
return hostResources(authority, "/res", true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hosts the application's resources on an http(s):// URL. Resources
|
||||
* <code>http(s)://{uuid}.androidplatform.net/{virtualResourcesPath}/{resource_type}/{resource_name}</code>.
|
||||
*
|
||||
* @param virtualResourcesPath the path on the local server under which the resources
|
||||
* should be hosted.
|
||||
* @param enableHttp whether to enable hosting using the http scheme.
|
||||
* @param enableHttps whether to enable hosting using the https scheme.
|
||||
* @return prefixes under which the resources are hosted.
|
||||
*/
|
||||
public AssetHostingDetails hostResources(final String virtualResourcesPath, boolean enableHttp,
|
||||
boolean enableHttps) {
|
||||
return hostResources(authority, virtualResourcesPath, enableHttp, enableHttps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hosts the application's resources on an http(s):// URL. Resources
|
||||
* <code>http(s)://{domain}/{virtualResourcesPath}/{resource_type}/{resource_name}</code>.
|
||||
*
|
||||
* @param domain custom domain on which the assets should be hosted (for example "example.com").
|
||||
* If untrusted content is to be loaded into the WebView it is advised to make
|
||||
* this random.
|
||||
* @param virtualResourcesPath the path on the local server under which the resources
|
||||
* should be hosted.
|
||||
* @param enableHttp whether to enable hosting using the http scheme.
|
||||
* @param enableHttps whether to enable hosting using the https scheme.
|
||||
* @return prefixes under which the resources are hosted.
|
||||
*/
|
||||
public AssetHostingDetails hostResources(final String domain,
|
||||
final String virtualResourcesPath, boolean enableHttp,
|
||||
boolean enableHttps) {
|
||||
if (virtualResourcesPath.indexOf('*') != -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"virtualResourcesPath cannot contain the '*' character.");
|
||||
}
|
||||
|
||||
Uri.Builder uriBuilder = new Uri.Builder();
|
||||
uriBuilder.scheme(httpScheme);
|
||||
uriBuilder.authority(domain);
|
||||
uriBuilder.path(virtualResourcesPath);
|
||||
|
||||
Uri httpPrefix = null;
|
||||
Uri httpsPrefix = null;
|
||||
|
||||
PathHandler handler = new PathHandler() {
|
||||
@Override
|
||||
public InputStream handle(Uri url) {
|
||||
InputStream stream = protocolHandler.openResource(url);
|
||||
String mimeType = null;
|
||||
try {
|
||||
mimeType = URLConnection.guessContentTypeFromStream(stream);
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "Unable to get mime type" + url);
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
};
|
||||
|
||||
if (enableHttp) {
|
||||
httpPrefix = uriBuilder.build();
|
||||
register(Uri.withAppendedPath(httpPrefix, "**"), handler);
|
||||
}
|
||||
if (enableHttps) {
|
||||
uriBuilder.scheme(httpsScheme);
|
||||
httpsPrefix = uriBuilder.build();
|
||||
register(Uri.withAppendedPath(httpsPrefix, "**"), handler);
|
||||
}
|
||||
return new AssetHostingDetails(httpPrefix, httpsPrefix);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hosts the application's files on an http(s):// URL. Files from the basePath
|
||||
* <code>basePath/...</code> will be available under
|
||||
* <code>http(s)://{uuid}.androidplatform.net/...</code>.
|
||||
*
|
||||
* @param basePath the local path in the application's data folder which will be made
|
||||
* available by the server (for example "/www").
|
||||
*/
|
||||
public void hostFiles(final String basePath) {
|
||||
this.isAsset = false;
|
||||
this.basePath = basePath;
|
||||
createHostingDetails();
|
||||
}
|
||||
|
||||
/**
|
||||
* The KitKat WebView reads the InputStream on a separate threadpool. We can use that to
|
||||
* parallelize loading.
|
||||
*/
|
||||
private static abstract class LazyInputStream extends InputStream {
|
||||
protected final PathHandler handler;
|
||||
private InputStream is = null;
|
||||
|
||||
public LazyInputStream(PathHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
private InputStream getInputStream() {
|
||||
if (is == null) {
|
||||
is = handle();
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
protected abstract InputStream handle();
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
InputStream is = getInputStream();
|
||||
return (is != null) ? is.available() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
InputStream is = getInputStream();
|
||||
return (is != null) ? is.read() : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte b[]) throws IOException {
|
||||
InputStream is = getInputStream();
|
||||
return (is != null) ? is.read(b) : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte b[], int off, int len) throws IOException {
|
||||
InputStream is = getInputStream();
|
||||
return (is != null) ? is.read(b, off, len) : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
InputStream is = getInputStream();
|
||||
return (is != null) ? is.skip(n) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// For L and above.
|
||||
private static class LollipopLazyInputStream extends LazyInputStream {
|
||||
private Uri uri;
|
||||
private InputStream is;
|
||||
|
||||
public LollipopLazyInputStream(PathHandler handler, Uri uri) {
|
||||
super(handler);
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream handle() {
|
||||
return handler.handle(uri);
|
||||
}
|
||||
}
|
||||
|
||||
public String getBasePath(){
|
||||
return this.basePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
@interface CDVWKProcessPoolFactory : NSObject
|
||||
@property (nonatomic, retain) WKProcessPool* sharedPool;
|
||||
|
||||
+(instancetype) sharedFactory;
|
||||
-(WKProcessPool*) sharedProcessPool;
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#import "CDVWKProcessPoolFactory.h"
|
||||
|
||||
static CDVWKProcessPoolFactory *factory = nil;
|
||||
|
||||
@implementation CDVWKProcessPoolFactory
|
||||
|
||||
+ (instancetype)sharedFactory
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
factory = [[CDVWKProcessPoolFactory alloc] init];
|
||||
});
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_sharedPool = [[WKProcessPool alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (WKProcessPool*) sharedProcessPool {
|
||||
return _sharedPool;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <Cordova/CDV.h>
|
||||
|
||||
@interface CDVWKWebViewEngine : CDVPlugin <CDVWebViewEngineProtocol, WKScriptMessageHandler, WKNavigationDelegate>
|
||||
|
||||
@property (nonatomic, strong, readonly) id <WKUIDelegate> uiDelegate;
|
||||
@property (nonatomic, strong) NSString * basePath;
|
||||
|
||||
-(void)setServerBasePath:(CDVInvokedUrlCommand*)command;
|
||||
-(void)getServerBasePath:(CDVInvokedUrlCommand*)command;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,825 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import <Cordova/NSDictionary+CordovaPreferences.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <objc/message.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import "CDVWKWebViewEngine.h"
|
||||
#import "CDVWKWebViewUIDelegate.h"
|
||||
#import "CDVWKProcessPoolFactory.h"
|
||||
#import "IONAssetHandler.h"
|
||||
|
||||
#define CDV_BRIDGE_NAME @"cordova"
|
||||
#define CDV_IONIC_STOP_SCROLL @"stopScroll"
|
||||
#define CDV_SERVER_PATH @"serverBasePath"
|
||||
#define LAST_BINARY_VERSION_CODE @"lastBinaryVersionCode"
|
||||
#define LAST_BINARY_VERSION_NAME @"lastBinaryVersionName"
|
||||
|
||||
@implementation UIScrollView (BugIOS11)
|
||||
|
||||
+ (void)load {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class class = [self class];
|
||||
SEL originalSelector = @selector(init);
|
||||
SEL swizzledSelector = @selector(xxx_init);
|
||||
|
||||
Method originalMethod = class_getInstanceMethod(class, originalSelector);
|
||||
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
|
||||
|
||||
BOOL didAddMethod =
|
||||
class_addMethod(class,
|
||||
originalSelector,
|
||||
method_getImplementation(swizzledMethod),
|
||||
method_getTypeEncoding(swizzledMethod));
|
||||
|
||||
if (didAddMethod) {
|
||||
class_replaceMethod(class,
|
||||
swizzledSelector,
|
||||
method_getImplementation(originalMethod),
|
||||
method_getTypeEncoding(originalMethod));
|
||||
} else {
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Method Swizzling
|
||||
|
||||
- (id)xxx_init {
|
||||
id a = [self xxx_init];
|
||||
NSArray *stack = [NSThread callStackSymbols];
|
||||
for(NSString *trace in stack) {
|
||||
if([trace containsString:@"WebKit"]) {
|
||||
[a setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface CDVWKWeakScriptMessageHandler : NSObject <WKScriptMessageHandler>
|
||||
|
||||
@property (nonatomic, weak, readonly) id<WKScriptMessageHandler>scriptMessageHandler;
|
||||
|
||||
- (instancetype)initWithScriptMessageHandler:(id<WKScriptMessageHandler>)scriptMessageHandler;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface CDVWKWebViewEngine ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) UIView* engineWebView;
|
||||
@property (nonatomic, strong, readwrite) id <WKUIDelegate> uiDelegate;
|
||||
@property (nonatomic, weak) id <WKScriptMessageHandler> weakScriptMessageHandler;
|
||||
@property (nonatomic, readwrite) CGRect frame;
|
||||
@property (nonatomic, strong) NSString *userAgentCreds;
|
||||
@property (nonatomic, strong) IONAssetHandler * handler;
|
||||
|
||||
@property (nonatomic, readwrite) NSString *CDV_LOCAL_SERVER;
|
||||
@end
|
||||
|
||||
// expose private configuration value required for background operation
|
||||
@interface WKWebViewConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// see forwardingTargetForSelector: selector comment for the reason for this pragma
|
||||
#pragma clang diagnostic ignored "-Wprotocol"
|
||||
|
||||
@implementation CDVWKWebViewEngine
|
||||
|
||||
@synthesize engineWebView = _engineWebView;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
if (NSClassFromString(@"WKWebView") == nil) {
|
||||
return nil;
|
||||
}
|
||||
// add to keyWindow to ensure it is 'active'
|
||||
[UIApplication.sharedApplication.keyWindow addSubview:self.engineWebView];
|
||||
|
||||
self.frame = frame;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(NSString *) getStartPath {
|
||||
NSString * wwwPath = [[NSBundle mainBundle] pathForResource:@"www" ofType: nil];
|
||||
|
||||
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
|
||||
NSString * persistedPath = [userDefaults objectForKey:CDV_SERVER_PATH];
|
||||
if (![self isDeployDisabled] && ![self isNewBinary] && persistedPath && ![persistedPath isEqualToString:@""]) {
|
||||
NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||||
NSString * cordovaDataDirectory = [libPath stringByAppendingPathComponent:@"NoCloud"];
|
||||
NSString * snapshots = [cordovaDataDirectory stringByAppendingPathComponent:@"ionic_built_snapshots"];
|
||||
wwwPath = [snapshots stringByAppendingPathComponent:[persistedPath lastPathComponent]];
|
||||
}
|
||||
self.basePath = wwwPath;
|
||||
return wwwPath;
|
||||
}
|
||||
|
||||
-(BOOL) isNewBinary
|
||||
{
|
||||
NSString * versionCode = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
|
||||
NSString * versionName = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
|
||||
NSUserDefaults * prefs = [NSUserDefaults standardUserDefaults];
|
||||
NSString * lastVersionCode = [prefs stringForKey:LAST_BINARY_VERSION_CODE];
|
||||
NSString * lastVersionName = [prefs stringForKey:LAST_BINARY_VERSION_NAME];
|
||||
if (![versionCode isEqualToString:lastVersionCode] || ![versionName isEqualToString:lastVersionName]) {
|
||||
[prefs setObject:versionCode forKey:LAST_BINARY_VERSION_CODE];
|
||||
[prefs setObject:versionName forKey:LAST_BINARY_VERSION_NAME];
|
||||
[prefs setObject:@"" forKey:CDV_SERVER_PATH];
|
||||
[prefs synchronize];
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(BOOL) isDeployDisabled {
|
||||
return [[self.commandDelegate.settings objectForKey:[@"DisableDeploy" lowercaseString]] boolValue];
|
||||
}
|
||||
|
||||
- (WKWebViewConfiguration*) createConfigurationFromSettings:(NSDictionary*)settings
|
||||
{
|
||||
WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
|
||||
configuration.processPool = [[CDVWKProcessPoolFactory sharedFactory] sharedProcessPool];
|
||||
configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
|
||||
|
||||
if (settings == nil) {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
if(![settings cordovaBoolSettingForKey:@"WKSuspendInBackground" defaultValue:YES]){
|
||||
NSString* _BGStatus;
|
||||
if (@available(iOS 12.2, *)) {
|
||||
// do stuff for iOS 12.2 and newer
|
||||
NSLog(@"iOS 12.2+ detected");
|
||||
NSString* str = @"YWx3YXlzUnVuc0F0Rm9yZWdyb3VuZFByaW9yaXR5";
|
||||
NSData* data = [[NSData alloc] initWithBase64EncodedString:str options:0];
|
||||
_BGStatus = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
} else {
|
||||
// do stuff for iOS 12.1 and older
|
||||
NSLog(@"iOS Below 12.2 detected");
|
||||
NSString* str = @"X2Fsd2F5c1J1bnNBdEZvcmVncm91bmRQcmlvcml0eQ==";
|
||||
NSData* data = [[NSData alloc] initWithBase64EncodedString:str options:0];
|
||||
_BGStatus = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
[configuration setValue:[NSNumber numberWithBool:YES]
|
||||
forKey:_BGStatus];
|
||||
}
|
||||
NSString *userAgent = configuration.applicationNameForUserAgent;
|
||||
if (
|
||||
[settings cordovaSettingForKey:@"OverrideUserAgent"] == nil &&
|
||||
[settings cordovaSettingForKey:@"AppendUserAgent"] != nil
|
||||
) {
|
||||
userAgent = [NSString stringWithFormat:@"%@ %@", userAgent, [settings cordovaSettingForKey:@"AppendUserAgent"]];
|
||||
}
|
||||
configuration.applicationNameForUserAgent = userAgent;
|
||||
configuration.allowsInlineMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowInlineMediaPlayback" defaultValue:YES];
|
||||
configuration.suppressesIncrementalRendering = [settings cordovaBoolSettingForKey:@"SuppressesIncrementalRendering" defaultValue:NO];
|
||||
configuration.allowsAirPlayForMediaPlayback = [settings cordovaBoolSettingForKey:@"MediaPlaybackAllowsAirPlay" defaultValue:YES];
|
||||
return configuration;
|
||||
}
|
||||
|
||||
- (void)pluginInitialize
|
||||
{
|
||||
// viewController would be available now. we attempt to set all possible delegates to it, by default
|
||||
NSDictionary* settings = self.commandDelegate.settings;
|
||||
NSString *bind = [settings cordovaSettingForKey:@"Hostname"];
|
||||
if(bind == nil){
|
||||
bind = @"localhost";
|
||||
}
|
||||
NSString *scheme = [settings cordovaSettingForKey:@"iosScheme"];
|
||||
if(scheme == nil || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"file"]){
|
||||
scheme = @"ionic";
|
||||
}
|
||||
self.CDV_LOCAL_SERVER = [NSString stringWithFormat:@"%@://%@", scheme, bind];
|
||||
|
||||
self.uiDelegate = [[CDVWKWebViewUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]];
|
||||
|
||||
CDVWKWeakScriptMessageHandler *weakScriptMessageHandler = [[CDVWKWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self];
|
||||
|
||||
WKUserContentController* userContentController = [[WKUserContentController alloc] init];
|
||||
[userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_BRIDGE_NAME];
|
||||
[userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_IONIC_STOP_SCROLL];
|
||||
|
||||
// Inject XHR Polyfill
|
||||
NSLog(@"CDVWKWebViewEngine: trying to inject XHR polyfill");
|
||||
WKUserScript *wkScript = [self wkPluginScript];
|
||||
if (wkScript) {
|
||||
[userContentController addUserScript:wkScript];
|
||||
}
|
||||
|
||||
WKUserScript *configScript = [self configScript];
|
||||
if (configScript) {
|
||||
[userContentController addUserScript:configScript];
|
||||
}
|
||||
|
||||
BOOL autoCordova = [settings cordovaBoolSettingForKey:@"AutoInjectCordova" defaultValue:NO];
|
||||
if (autoCordova){
|
||||
NSLog(@"CDVWKWebViewEngine: trying to inject XHR polyfill");
|
||||
WKUserScript *cordova = [self autoCordovify];
|
||||
if (cordova) {
|
||||
[userContentController addUserScript:cordova];
|
||||
}
|
||||
}
|
||||
|
||||
BOOL audioCanMix = [settings cordovaBoolSettingForKey:@"AudioCanMix" defaultValue:NO];
|
||||
if (audioCanMix) {
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
|
||||
withOptions:AVAudioSessionCategoryOptionMixWithOthers
|
||||
error:nil];
|
||||
}
|
||||
|
||||
WKWebViewConfiguration* configuration = [self createConfigurationFromSettings:settings];
|
||||
configuration.userContentController = userContentController;
|
||||
|
||||
self.handler = [[IONAssetHandler alloc] initWithBasePath:[self getStartPath] andScheme:scheme];
|
||||
[configuration setURLSchemeHandler:self.handler forURLScheme:scheme];
|
||||
|
||||
// re-create WKWebView, since we need to update configuration
|
||||
// remove from keyWindow before recreating
|
||||
[self.engineWebView removeFromSuperview];
|
||||
WKWebView* wkWebView = [[WKWebView alloc] initWithFrame:self.frame configuration:configuration];
|
||||
|
||||
[wkWebView.scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
|
||||
|
||||
wkWebView.UIDelegate = self.uiDelegate;
|
||||
self.engineWebView = wkWebView;
|
||||
// add to keyWindow to ensure it is 'active'
|
||||
[UIApplication.sharedApplication.keyWindow addSubview:self.engineWebView];
|
||||
|
||||
NSString * overrideUserAgent = [settings cordovaSettingForKey:@"OverrideUserAgent"];
|
||||
if (overrideUserAgent != nil) {
|
||||
wkWebView.customUserAgent = overrideUserAgent;
|
||||
}
|
||||
|
||||
if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) {
|
||||
wkWebView.UIDelegate = (id <WKUIDelegate>)self.viewController;
|
||||
}
|
||||
|
||||
if ([self.viewController conformsToProtocol:@protocol(WKNavigationDelegate)]) {
|
||||
wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self.viewController;
|
||||
} else {
|
||||
wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self;
|
||||
}
|
||||
|
||||
if ([self.viewController conformsToProtocol:@protocol(WKScriptMessageHandler)]) {
|
||||
[wkWebView.configuration.userContentController addScriptMessageHandler:(id < WKScriptMessageHandler >)self.viewController name:CDV_BRIDGE_NAME];
|
||||
}
|
||||
|
||||
[self keyboardDisplayDoesNotRequireUserAction];
|
||||
|
||||
if ([settings cordovaBoolSettingForKey:@"KeyboardAppearanceDark" defaultValue:NO]) {
|
||||
[self setKeyboardAppearanceDark];
|
||||
}
|
||||
|
||||
[self updateSettings:settings];
|
||||
|
||||
// check if content thread has died on resume
|
||||
NSLog(@"%@", @"CDVWKWebViewEngine will reload WKWebView if required on resume");
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(onAppWillEnterForeground:)
|
||||
name:UIApplicationWillEnterForegroundNotification object:nil];
|
||||
|
||||
// If less than ios 13.4
|
||||
if (@available(iOS 13.4, *)) {} else {
|
||||
// For keyboard dismissal leaving viewport shifted (can potentially be removed when apple releases the fix for the issue discussed here: https://github.com/apache/cordova-ios/issues/417#issuecomment-423340885)
|
||||
// Apple has released a fix in 13.4, but not in 12.x (as of 12.4.6)
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(keyboardWillHide)
|
||||
name:UIKeyboardWillHideNotification object:nil];
|
||||
}
|
||||
|
||||
NSLog(@"Using Ionic WKWebView");
|
||||
|
||||
}
|
||||
|
||||
// https://github.com/Telerik-Verified-Plugins/WKWebView/commit/04e8296adeb61f289f9c698045c19b62d080c7e3#L609-L620
|
||||
- (void) keyboardDisplayDoesNotRequireUserAction {
|
||||
Class class = NSClassFromString(@"WKContentView");
|
||||
NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
|
||||
NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
|
||||
NSOperatingSystemVersion iOS_13_0_0 = (NSOperatingSystemVersion){13, 0, 0};
|
||||
char * methodSignature = "_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:";
|
||||
|
||||
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_13_0_0]) {
|
||||
methodSignature = "_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:";
|
||||
} else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
|
||||
methodSignature = "_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:";
|
||||
}
|
||||
|
||||
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
|
||||
SEL selector = sel_getUid(methodSignature);
|
||||
Method method = class_getInstanceMethod(class, selector);
|
||||
IMP original = method_getImplementation(method);
|
||||
IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
|
||||
((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
|
||||
});
|
||||
method_setImplementation(method, override);
|
||||
} else {
|
||||
SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
|
||||
Method method = class_getInstanceMethod(class, selector);
|
||||
IMP original = method_getImplementation(method);
|
||||
IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
|
||||
((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
|
||||
});
|
||||
method_setImplementation(method, override);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKeyboardAppearanceDark
|
||||
{
|
||||
IMP darkImp = imp_implementationWithBlock(^(id _s) {
|
||||
return UIKeyboardAppearanceDark;
|
||||
});
|
||||
for (NSString* classString in @[@"WKContentView", @"UITextInputTraits"]) {
|
||||
Class c = NSClassFromString(classString);
|
||||
Method m = class_getInstanceMethod(c, @selector(keyboardAppearance));
|
||||
if (m != NULL) {
|
||||
method_setImplementation(m, darkImp);
|
||||
} else {
|
||||
class_addMethod(c, @selector(keyboardAppearance), darkImp, "l@:");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)onAppWillEnterForeground:(NSNotification *)notification {
|
||||
if ([self shouldReloadWebView]) {
|
||||
NSLog(@"%@", @"CDVWKWebViewEngine reloading!");
|
||||
[(WKWebView*)_engineWebView reload];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-(void)keyboardWillHide
|
||||
{
|
||||
// For keyboard dismissal leaving viewport shifted (can potentially be removed when apple releases the fix for the issue discussed here: https://github.com/apache/cordova-ios/issues/417#issuecomment-423340885)
|
||||
UIScrollView * scrollView = self.webView.scrollView;
|
||||
// Calculate some vars for convenience
|
||||
CGFloat contentLengthWithInsets = scrollView.contentSize.height + scrollView.adjustedContentInset.top + scrollView.adjustedContentInset.bottom;
|
||||
CGFloat contentOffsetY = scrollView.contentOffset.y;
|
||||
CGFloat screenHeight = scrollView.frame.size.height;
|
||||
CGFloat maxAllowedOffsetY = fmax(contentLengthWithInsets - screenHeight, 0); // 0 is for the case where content is shorter than screen
|
||||
|
||||
// If the keyboard allowed the user to get to an offset beyond the max
|
||||
if (contentOffsetY > maxAllowedOffsetY) {
|
||||
// Reset the scroll to the max allowed so that there is no additional empty white space at the bottom where the keyboard occupied!
|
||||
CGPoint bottomOfPage = CGPointMake(scrollView.contentOffset.x, maxAllowedOffsetY);
|
||||
[scrollView setContentOffset:bottomOfPage];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldReloadWebView
|
||||
{
|
||||
WKWebView* wkWebView = (WKWebView*)_engineWebView;
|
||||
return [self shouldReloadWebView:wkWebView.URL title:wkWebView.title];
|
||||
}
|
||||
|
||||
- (BOOL)shouldReloadWebView:(NSURL *)location title:(NSString*)title
|
||||
{
|
||||
BOOL title_is_nil = (title == nil);
|
||||
BOOL location_is_blank = [[location absoluteString] isEqualToString:@"about:blank"];
|
||||
|
||||
BOOL reload = (title_is_nil || location_is_blank);
|
||||
|
||||
#ifdef DEBUG
|
||||
NSLog(@"%@", @"CDVWKWebViewEngine shouldReloadWebView::");
|
||||
NSLog(@"CDVWKWebViewEngine shouldReloadWebView title: %@", title);
|
||||
NSLog(@"CDVWKWebViewEngine shouldReloadWebView location: %@", [location absoluteString]);
|
||||
NSLog(@"CDVWKWebViewEngine shouldReloadWebView reload: %u", reload);
|
||||
#endif
|
||||
|
||||
return reload;
|
||||
}
|
||||
|
||||
|
||||
- (id)loadRequest:(NSURLRequest *)request
|
||||
{
|
||||
if (request.URL.fileURL) {
|
||||
NSURL* startURL = [NSURL URLWithString:((CDVViewController *)self.viewController).startPage];
|
||||
NSString* startFilePath = [self.commandDelegate pathForResource:[startURL path]];
|
||||
NSURL *url = [[NSURL URLWithString:self.CDV_LOCAL_SERVER] URLByAppendingPathComponent:request.URL.path];
|
||||
if ([request.URL.path isEqualToString:startFilePath]) {
|
||||
url = [NSURL URLWithString:self.CDV_LOCAL_SERVER];
|
||||
}
|
||||
if(request.URL.query) {
|
||||
url = [NSURL URLWithString:[@"?" stringByAppendingString:request.URL.query] relativeToURL:url];
|
||||
}
|
||||
if(request.URL.fragment) {
|
||||
url = [NSURL URLWithString:[@"#" stringByAppendingString:request.URL.fragment] relativeToURL:url];
|
||||
}
|
||||
request = [NSURLRequest requestWithURL:url];
|
||||
}
|
||||
return [(WKWebView*)_engineWebView loadRequest:request];
|
||||
}
|
||||
|
||||
- (id)loadHTMLString:(NSString *)string baseURL:(NSURL*)baseURL
|
||||
{
|
||||
return [(WKWebView*)_engineWebView loadHTMLString:string baseURL:baseURL];
|
||||
}
|
||||
|
||||
- (NSURL*) URL
|
||||
{
|
||||
return [(WKWebView*)_engineWebView URL];
|
||||
}
|
||||
|
||||
- (BOOL)canLoadRequest:(NSURLRequest *)request
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
- (void)updateSettings:(NSDictionary *)settings
|
||||
{
|
||||
WKWebView* wkWebView = (WKWebView *)_engineWebView;
|
||||
|
||||
// By default, DisallowOverscroll is false (thus bounce is allowed)
|
||||
BOOL bounceAllowed = !([settings cordovaBoolSettingForKey:@"DisallowOverscroll" defaultValue:NO]);
|
||||
|
||||
// prevent webView from bouncing
|
||||
if (!bounceAllowed) {
|
||||
if ([wkWebView respondsToSelector:@selector(scrollView)]) {
|
||||
((UIScrollView*)[wkWebView scrollView]).bounces = NO;
|
||||
} else {
|
||||
for (id subview in wkWebView.subviews) {
|
||||
if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
|
||||
((UIScrollView*)subview).bounces = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wkWebView.configuration.preferences.minimumFontSize = [settings cordovaFloatSettingForKey:@"MinimumFontSize" defaultValue:0.0];
|
||||
wkWebView.allowsLinkPreview = [settings cordovaBoolSettingForKey:@"AllowLinkPreview" defaultValue:NO];
|
||||
wkWebView.scrollView.scrollEnabled = [settings cordovaBoolSettingForKey:@"ScrollEnabled" defaultValue:NO];
|
||||
wkWebView.allowsBackForwardNavigationGestures = [settings cordovaBoolSettingForKey:@"AllowBackForwardNavigationGestures" defaultValue:NO];
|
||||
}
|
||||
|
||||
- (void)updateWithInfo:(NSDictionary *)info
|
||||
{
|
||||
NSDictionary* scriptMessageHandlers = [info objectForKey:kCDVWebViewEngineScriptMessageHandlers];
|
||||
NSDictionary* settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences];
|
||||
id navigationDelegate = [info objectForKey:kCDVWebViewEngineWKNavigationDelegate];
|
||||
id uiDelegate = [info objectForKey:kCDVWebViewEngineWKUIDelegate];
|
||||
|
||||
WKWebView* wkWebView = (WKWebView*)_engineWebView;
|
||||
|
||||
if (scriptMessageHandlers && [scriptMessageHandlers isKindOfClass:[NSDictionary class]]) {
|
||||
NSArray* allKeys = [scriptMessageHandlers allKeys];
|
||||
|
||||
for (NSString* key in allKeys) {
|
||||
id object = [scriptMessageHandlers objectForKey:key];
|
||||
if ([object conformsToProtocol:@protocol(WKScriptMessageHandler)]) {
|
||||
[wkWebView.configuration.userContentController addScriptMessageHandler:object name:key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (navigationDelegate && [navigationDelegate conformsToProtocol:@protocol(WKNavigationDelegate)]) {
|
||||
wkWebView.navigationDelegate = navigationDelegate;
|
||||
}
|
||||
|
||||
if (uiDelegate && [uiDelegate conformsToProtocol:@protocol(WKUIDelegate)]) {
|
||||
wkWebView.UIDelegate = uiDelegate;
|
||||
}
|
||||
|
||||
if (settings && [settings isKindOfClass:[NSDictionary class]]) {
|
||||
[self updateSettings:settings];
|
||||
}
|
||||
}
|
||||
|
||||
// This forwards the methods that are in the header that are not implemented here.
|
||||
// loadHTMLString:baseURL:
|
||||
// loadRequest:
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
return _engineWebView;
|
||||
}
|
||||
|
||||
- (UIView *)webView
|
||||
{
|
||||
return self.engineWebView;
|
||||
}
|
||||
|
||||
- (WKUserScript *)wkPluginScript
|
||||
{
|
||||
NSString *scriptFile = [[NSBundle mainBundle] pathForResource:@"www/wk-plugin" ofType:@"js"];
|
||||
if (scriptFile == nil) {
|
||||
NSLog(@"CDVWKWebViewEngine: WK plugin was not found");
|
||||
return nil;
|
||||
}
|
||||
NSError *error = nil;
|
||||
NSString *source = [NSString stringWithContentsOfFile:scriptFile encoding:NSUTF8StringEncoding error:&error];
|
||||
if (source == nil || error != nil) {
|
||||
NSLog(@"CDVWKWebViewEngine: WK plugin can not be loaded: %@", error);
|
||||
return nil;
|
||||
}
|
||||
source = [source stringByAppendingString:[NSString stringWithFormat:@"window.WEBVIEW_SERVER_URL = '%@';", self.CDV_LOCAL_SERVER]];
|
||||
|
||||
return [[WKUserScript alloc] initWithSource:source
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
||||
forMainFrameOnly:YES];
|
||||
}
|
||||
|
||||
- (WKUserScript *)configScript
|
||||
{
|
||||
Class keyboard = NSClassFromString(@"CDVIonicKeyboard");
|
||||
BOOL keyboardPlugin = keyboard != nil;
|
||||
if(!keyboardPlugin) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
BOOL keyboardResizes = [self.commandDelegate.settings cordovaBoolSettingForKey:@"KeyboardResize" defaultValue:YES];
|
||||
NSString *source = [NSString stringWithFormat:
|
||||
@"window.Ionic = window.Ionic || {};"
|
||||
@"window.Ionic.keyboardPlugin=true;"
|
||||
@"window.Ionic.keyboardResizes=%@",
|
||||
keyboardResizes ? @"true" : @"false"];
|
||||
|
||||
return [[WKUserScript alloc] initWithSource:source
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
||||
forMainFrameOnly:YES];
|
||||
}
|
||||
|
||||
- (WKUserScript *)autoCordovify
|
||||
{
|
||||
NSURL *cordovaURL = [[NSBundle mainBundle] URLForResource:@"www/cordova" withExtension:@"js"];
|
||||
if (cordovaURL == nil) {
|
||||
NSLog(@"CDVWKWebViewEngine: cordova.js WAS NOT FOUND");
|
||||
return nil;
|
||||
}
|
||||
NSError *error = nil;
|
||||
NSString *source = [NSString stringWithContentsOfURL:cordovaURL encoding:NSUTF8StringEncoding error:&error];
|
||||
if (source == nil || error != nil) {
|
||||
NSLog(@"CDVWKWebViewEngine: cordova.js can not be loaded: %@", error);
|
||||
return nil;
|
||||
}
|
||||
NSLog(@"CDVWKWebViewEngine: auto injecting cordova");
|
||||
NSString *cordovaPath = [self.CDV_LOCAL_SERVER stringByAppendingString:cordovaURL.URLByDeletingLastPathComponent.path];
|
||||
NSString *replacement = [NSString stringWithFormat:@"var pathPrefix = '%@/';", cordovaPath];
|
||||
source = [source stringByReplacingOccurrencesOfString:@"var pathPrefix = findCordovaPath();" withString:replacement];
|
||||
|
||||
return [[WKUserScript alloc] initWithSource:source
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
||||
forMainFrameOnly:YES];
|
||||
}
|
||||
|
||||
#pragma mark WKScriptMessageHandler implementation
|
||||
|
||||
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
|
||||
{
|
||||
if ([message.name isEqualToString:CDV_BRIDGE_NAME]) {
|
||||
[self handleCordovaMessage: message];
|
||||
} else if ([message.name isEqualToString:CDV_IONIC_STOP_SCROLL]) {
|
||||
[self handleStopScroll];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleCordovaMessage:(WKScriptMessage*)message
|
||||
{
|
||||
CDVViewController *vc = (CDVViewController*)self.viewController;
|
||||
|
||||
NSArray *jsonEntry = message.body; // NSString:callbackId, NSString:service, NSString:action, NSArray:args
|
||||
CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
|
||||
CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName);
|
||||
|
||||
if (![vc.commandQueue execute:command]) {
|
||||
#ifdef DEBUG
|
||||
NSError* error = nil;
|
||||
NSString* commandJson = nil;
|
||||
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonEntry
|
||||
options:0
|
||||
error:&error];
|
||||
|
||||
if (error == nil) {
|
||||
commandJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
static NSUInteger maxLogLength = 1024;
|
||||
NSString* commandString = ([commandJson length] > maxLogLength) ?
|
||||
[NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] :
|
||||
commandJson;
|
||||
|
||||
NSLog(@"FAILED pluginJSON = %@", commandString);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleStopScroll
|
||||
{
|
||||
WKWebView* wkWebView = (WKWebView*)_engineWebView;
|
||||
NSLog(@"CDVWKWebViewEngine: handleStopScroll");
|
||||
[self recursiveStopScroll:[wkWebView scrollView]];
|
||||
[wkWebView evaluateJavaScript:@"window.IonicStopScroll.fire()" completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)recursiveStopScroll:(UIView *)node
|
||||
{
|
||||
if([node isKindOfClass: [UIScrollView class]]) {
|
||||
UIScrollView *nodeAsScroll = (UIScrollView *)node;
|
||||
|
||||
if([nodeAsScroll isScrollEnabled] && ![nodeAsScroll isHidden]) {
|
||||
[nodeAsScroll setScrollEnabled: NO];
|
||||
[nodeAsScroll setScrollEnabled: YES];
|
||||
}
|
||||
}
|
||||
|
||||
// iterate tree recursivelly
|
||||
for (UIView *child in [node subviews]) {
|
||||
[self recursiveStopScroll:child];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark WKNavigationDelegate implementation
|
||||
|
||||
- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:webView]];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
|
||||
{
|
||||
#ifndef __CORDOVA_6_0_0
|
||||
CDVViewController* vc = (CDVViewController*)self.viewController;
|
||||
[CDVUserAgentUtil releaseLock:vc.userAgentLockToken];
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:webView]];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView*)theWebView didFailProvisionalNavigation:(WKNavigation*)navigation withError:(NSError*)error
|
||||
{
|
||||
[self webView:theWebView didFailNavigation:navigation withError:error];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigation withError:(NSError*)error
|
||||
{
|
||||
CDVViewController* vc = (CDVViewController*)self.viewController;
|
||||
#ifndef __CORDOVA_6_0_0
|
||||
[CDVUserAgentUtil releaseLock:vc.userAgentLockToken];
|
||||
#endif
|
||||
|
||||
NSString* message = [NSString stringWithFormat:@"Failed to load webpage with error: %@", [error localizedDescription]];
|
||||
NSLog(@"%@", message);
|
||||
|
||||
NSURL* errorUrl = vc.errorURL;
|
||||
if (errorUrl) {
|
||||
NSCharacterSet *charSet = [NSCharacterSet URLFragmentAllowedCharacterSet];
|
||||
errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [message stringByAddingPercentEncodingWithAllowedCharacters:charSet]] relativeToURL:errorUrl];
|
||||
NSLog(@"%@", [errorUrl absoluteString]);
|
||||
[theWebView loadRequest:[NSURLRequest requestWithURL:errorUrl]];
|
||||
}
|
||||
#ifdef DEBUG
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] message:message preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:nil]];
|
||||
[vc presentViewController:alertController animated:YES completion:nil];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
|
||||
{
|
||||
[webView reload];
|
||||
}
|
||||
|
||||
- (BOOL)defaultResourcePolicyForURL:(NSURL*)url
|
||||
{
|
||||
// all file:// urls are allowed
|
||||
if ([url isFileURL]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
|
||||
{
|
||||
NSURL* url = [navigationAction.request URL];
|
||||
CDVViewController* vc = (CDVViewController*)self.viewController;
|
||||
|
||||
/*
|
||||
* Give plugins the chance to handle the url
|
||||
*/
|
||||
BOOL anyPluginsResponded = NO;
|
||||
BOOL shouldAllowRequest = NO;
|
||||
|
||||
for (NSString* pluginName in vc.pluginObjects) {
|
||||
CDVPlugin* plugin = [vc.pluginObjects objectForKey:pluginName];
|
||||
SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:");
|
||||
if ([plugin respondsToSelector:selector]) {
|
||||
anyPluginsResponded = YES;
|
||||
// https://issues.apache.org/jira/browse/CB-12497
|
||||
int navType = (int)navigationAction.navigationType;
|
||||
if (WKNavigationTypeOther == navigationAction.navigationType) {
|
||||
#ifdef __CORDOVA_6_0_0
|
||||
navType = -1;
|
||||
#else
|
||||
navType = 5;
|
||||
#endif
|
||||
}
|
||||
shouldAllowRequest = (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, navigationAction.request, navType));
|
||||
if (!shouldAllowRequest) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyPluginsResponded) {
|
||||
/*
|
||||
* Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview.
|
||||
*/
|
||||
shouldAllowRequest = [self defaultResourcePolicyForURL:url];
|
||||
if (!shouldAllowRequest) {
|
||||
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAllowRequest) {
|
||||
NSString *scheme = url.scheme;
|
||||
if ([scheme isEqualToString:@"tel"] ||
|
||||
[scheme isEqualToString:@"mailto"] ||
|
||||
[scheme isEqualToString:@"facetime"] ||
|
||||
[scheme isEqualToString:@"sms"] ||
|
||||
[scheme isEqualToString:@"maps"]) {
|
||||
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
||||
decisionHandler(WKNavigationActionPolicyCancel);
|
||||
} else {
|
||||
decisionHandler(WKNavigationActionPolicyAllow);
|
||||
}
|
||||
} else {
|
||||
decisionHandler(WKNavigationActionPolicyCancel);
|
||||
}
|
||||
}
|
||||
|
||||
-(void)getServerBasePath:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:self.basePath] callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
-(void)setServerBasePath:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
NSString * path = [command argumentAtIndex:0];
|
||||
self.basePath = path;
|
||||
[self.handler setAssetPath:path];
|
||||
|
||||
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.CDV_LOCAL_SERVER]];
|
||||
[(WKWebView*)_engineWebView loadRequest:request];
|
||||
}
|
||||
|
||||
-(void)persistServerBasePath:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
|
||||
[userDefaults setObject:[self.basePath lastPathComponent] forKey:CDV_SERVER_PATH];
|
||||
[userDefaults synchronize];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CDVWKWeakScriptMessageHandler
|
||||
|
||||
@implementation CDVWKWeakScriptMessageHandler
|
||||
|
||||
- (instancetype)initWithScriptMessageHandler:(id<WKScriptMessageHandler>)scriptMessageHandler
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_scriptMessageHandler = scriptMessageHandler;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
|
||||
{
|
||||
[self.scriptMessageHandler userContentController:userContentController didReceiveScriptMessage:message];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
@interface CDVWKWebViewUIDelegate : NSObject <WKUIDelegate>
|
||||
|
||||
@property (nonatomic, copy) NSString* title;
|
||||
|
||||
- (instancetype)initWithTitle:(NSString*)title;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVWKWebViewUIDelegate.h"
|
||||
|
||||
@implementation CDVWKWebViewUIDelegate
|
||||
|
||||
- (instancetype)initWithTitle:(NSString*)title
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.title = title;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message
|
||||
initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(void))completionHandler
|
||||
{
|
||||
UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction* action)
|
||||
{
|
||||
completionHandler();
|
||||
[alert dismissViewControllerAnimated:YES completion:nil];
|
||||
}];
|
||||
|
||||
[alert addAction:ok];
|
||||
|
||||
UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||||
|
||||
[rootController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView*)webView runJavaScriptConfirmPanelWithMessage:(NSString*)message
|
||||
initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(BOOL result))completionHandler
|
||||
{
|
||||
UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction* action)
|
||||
{
|
||||
completionHandler(YES);
|
||||
[alert dismissViewControllerAnimated:YES completion:nil];
|
||||
}];
|
||||
|
||||
[alert addAction:ok];
|
||||
|
||||
UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction* action)
|
||||
{
|
||||
completionHandler(NO);
|
||||
[alert dismissViewControllerAnimated:YES completion:nil];
|
||||
}];
|
||||
[alert addAction:cancel];
|
||||
|
||||
UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||||
|
||||
[rootController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView*)webView runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
|
||||
defaultText:(NSString*)defaultText initiatedByFrame:(WKFrameInfo*)frame
|
||||
completionHandler:(void (^)(NSString* result))completionHandler
|
||||
{
|
||||
UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
|
||||
message:prompt
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction* action)
|
||||
{
|
||||
completionHandler(((UITextField*)alert.textFields[0]).text);
|
||||
[alert dismissViewControllerAnimated:YES completion:nil];
|
||||
}];
|
||||
|
||||
[alert addAction:ok];
|
||||
|
||||
UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction* action)
|
||||
{
|
||||
completionHandler(nil);
|
||||
[alert dismissViewControllerAnimated:YES completion:nil];
|
||||
}];
|
||||
[alert addAction:cancel];
|
||||
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField* textField) {
|
||||
textField.text = defaultText;
|
||||
}];
|
||||
|
||||
UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||||
|
||||
[rootController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
@interface IONAssetHandler : NSObject <WKURLSchemeHandler>
|
||||
|
||||
@property (nonatomic, strong) NSString * basePath;
|
||||
@property (nonatomic, strong) NSString * scheme;
|
||||
|
||||
-(void)setAssetPath:(NSString *)assetPath;
|
||||
- (instancetype)initWithBasePath:(NSString *)basePath andScheme:(NSString *)scheme;
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,92 @@
|
||||
#import "IONAssetHandler.h"
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import "CDVWKWebViewEngine.h"
|
||||
|
||||
@implementation IONAssetHandler
|
||||
|
||||
-(void)setAssetPath:(NSString *)assetPath {
|
||||
self.basePath = assetPath;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBasePath:(NSString *)basePath andScheme:(NSString *)scheme {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_basePath = basePath;
|
||||
_scheme = scheme;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
|
||||
{
|
||||
NSString * startPath = @"";
|
||||
NSURL * url = urlSchemeTask.request.URL;
|
||||
NSString * stringToLoad = url.path;
|
||||
NSString * scheme = url.scheme;
|
||||
|
||||
if ([scheme isEqualToString:self.scheme]) {
|
||||
if ([stringToLoad hasPrefix:@"/_app_file_"]) {
|
||||
startPath = [stringToLoad stringByReplacingOccurrencesOfString:@"/_app_file_" withString:@""];
|
||||
} else {
|
||||
startPath = self.basePath ? self.basePath : @"";
|
||||
if ([stringToLoad isEqualToString:@""] || [url.pathExtension isEqualToString:@""]) {
|
||||
startPath = [startPath stringByAppendingString:@"/index.html"];
|
||||
} else {
|
||||
startPath = [startPath stringByAppendingString:stringToLoad];
|
||||
}
|
||||
}
|
||||
}
|
||||
NSError * fileError = nil;
|
||||
NSData * data = nil;
|
||||
if ([self isMediaExtension:url.pathExtension]) {
|
||||
data = [NSData dataWithContentsOfFile:startPath options:NSDataReadingMappedIfSafe error:&fileError];
|
||||
}
|
||||
if (!data || fileError) {
|
||||
data = [[NSData alloc] initWithContentsOfFile:startPath];
|
||||
}
|
||||
NSInteger statusCode = 200;
|
||||
if (!data) {
|
||||
statusCode = 404;
|
||||
}
|
||||
NSURL * localUrl = [NSURL URLWithString:url.absoluteString];
|
||||
NSString * mimeType = [self getMimeType:url.pathExtension];
|
||||
id response = nil;
|
||||
if (data && [self isMediaExtension:url.pathExtension]) {
|
||||
response = [[NSURLResponse alloc] initWithURL:localUrl MIMEType:mimeType expectedContentLength:data.length textEncodingName:nil];
|
||||
} else {
|
||||
NSDictionary * headers = @{ @"Content-Type" : mimeType, @"Cache-Control": @"no-cache"};
|
||||
response = [[NSHTTPURLResponse alloc] initWithURL:localUrl statusCode:statusCode HTTPVersion:nil headerFields:headers];
|
||||
}
|
||||
|
||||
[urlSchemeTask didReceiveResponse:response];
|
||||
[urlSchemeTask didReceiveData:data];
|
||||
[urlSchemeTask didFinish];
|
||||
|
||||
}
|
||||
|
||||
- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask
|
||||
{
|
||||
NSLog(@"stop");
|
||||
}
|
||||
|
||||
-(NSString *) getMimeType:(NSString *)fileExtension {
|
||||
if (fileExtension && ![fileExtension isEqualToString:@""]) {
|
||||
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
|
||||
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
|
||||
return contentType ? contentType : @"application/octet-stream";
|
||||
} else {
|
||||
return @"text/html";
|
||||
}
|
||||
}
|
||||
|
||||
-(BOOL) isMediaExtension:(NSString *) pathExtension {
|
||||
NSArray * mediaExtensions = @[@"m4v", @"mov", @"mp4",
|
||||
@"aac", @"ac3", @"aiff", @"au", @"flac", @"m4a", @"mp3", @"wav"];
|
||||
if ([mediaExtensions containsObject:pathExtension.lowercaseString]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2012 Niklas von Hertzen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
(function _wk_plugin() {
|
||||
// Check if we are running in WKWebView
|
||||
if (!window.webkit || !window.webkit.messageHandlers) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Ionic
|
||||
window.Ionic = window.Ionic || {};
|
||||
|
||||
function normalizeURL(url) {
|
||||
console.warn('normalizeURL is deprecated, use window.Ionic.WebView.convertFileSrc');
|
||||
return window.Ionic.WebView.convertFileSrc(url);
|
||||
}
|
||||
if (typeof window.wkRewriteURL === 'undefined') {
|
||||
window.wkRewriteURL = function (url) {
|
||||
console.warn('wkRewriteURL is deprecated, use window.Ionic.WebView.convertFileSrc instead');
|
||||
return window.Ionic.WebView.convertFileSrc(url);
|
||||
}
|
||||
}
|
||||
window.Ionic.normalizeURL = normalizeURL;
|
||||
|
||||
var stopScrollHandler = window.webkit.messageHandlers.stopScroll;
|
||||
if (!stopScrollHandler) {
|
||||
console.error('Can not find stopScroll handler');
|
||||
return;
|
||||
}
|
||||
|
||||
var stopScrollFunc = null;
|
||||
var stopScroll = {
|
||||
stop: function stop(callback) {
|
||||
if (!stopScrollFunc) {
|
||||
stopScrollFunc = callback;
|
||||
stopScrollHandler.postMessage('');
|
||||
}
|
||||
},
|
||||
fire: function fire() {
|
||||
stopScrollFunc && stopScrollFunc();
|
||||
stopScrollFunc = null;
|
||||
},
|
||||
cancel: function cancel() {
|
||||
stopScrollFunc = null;
|
||||
}
|
||||
};
|
||||
|
||||
window.Ionic.StopScroll = stopScroll;
|
||||
// deprecated
|
||||
window.IonicStopScroll = stopScroll;
|
||||
|
||||
console.debug("Ionic Stop Scroll injected!");
|
||||
})();
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates the exec bridge used to notify the native code of
|
||||
* commands.
|
||||
*/
|
||||
var cordova = require('cordova');
|
||||
var utils = require('cordova/utils');
|
||||
var base64 = require('cordova/base64');
|
||||
|
||||
function massageArgsJsToNative (args) {
|
||||
if (!args || utils.typeName(args) !== 'Array') {
|
||||
return args;
|
||||
}
|
||||
var ret = [];
|
||||
args.forEach(function (arg, i) {
|
||||
if (utils.typeName(arg) === 'ArrayBuffer') {
|
||||
ret.push({
|
||||
'CDVType': 'ArrayBuffer',
|
||||
'data': base64.fromArrayBuffer(arg)
|
||||
});
|
||||
} else {
|
||||
ret.push(arg);
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function massageMessageNativeToJs (message) {
|
||||
if (message.CDVType === 'ArrayBuffer') {
|
||||
var stringToArrayBuffer = function (str) {
|
||||
var ret = new Uint8Array(str.length);
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
ret[i] = str.charCodeAt(i);
|
||||
}
|
||||
return ret.buffer;
|
||||
};
|
||||
var base64ToArrayBuffer = function (b64) {
|
||||
return stringToArrayBuffer(atob(b64)); // eslint-disable-line no-undef
|
||||
};
|
||||
message = base64ToArrayBuffer(message.data);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function convertMessageToArgsNativeToJs (message) {
|
||||
var args = [];
|
||||
if (!message || !message.hasOwnProperty('CDVType')) {
|
||||
args.push(message);
|
||||
} else if (message.CDVType === 'MultiPart') {
|
||||
message.messages.forEach(function (e) {
|
||||
args.push(massageMessageNativeToJs(e));
|
||||
});
|
||||
} else {
|
||||
args.push(massageMessageNativeToJs(message));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
var iOSExec = function () {
|
||||
// detect change in bridge, if there is a change, we forward to new bridge
|
||||
|
||||
// if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.cordova && window.webkit.messageHandlers.cordova.postMessage) {
|
||||
// bridgeMode = jsToNativeModes.WK_WEBVIEW_BINDING;
|
||||
// }
|
||||
|
||||
var successCallback, failCallback, service, action, actionArgs;
|
||||
var callbackId = null;
|
||||
if (typeof arguments[0] !== 'string') {
|
||||
// FORMAT ONE
|
||||
successCallback = arguments[0];
|
||||
failCallback = arguments[1];
|
||||
service = arguments[2];
|
||||
action = arguments[3];
|
||||
actionArgs = arguments[4];
|
||||
|
||||
// Since we need to maintain backwards compatibility, we have to pass
|
||||
// an invalid callbackId even if no callback was provided since plugins
|
||||
// will be expecting it. The Cordova.exec() implementation allocates
|
||||
// an invalid callbackId and passes it even if no callbacks were given.
|
||||
callbackId = 'INVALID';
|
||||
} else {
|
||||
throw new Error('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' + // eslint-disable-line
|
||||
'cordova.exec(null, null, \'Service\', \'action\', [ arg1, arg2 ]);');
|
||||
}
|
||||
|
||||
// If actionArgs is not provided, default to an empty array
|
||||
actionArgs = actionArgs || [];
|
||||
|
||||
// Register the callbacks and add the callbackId to the positional
|
||||
// arguments if given.
|
||||
if (successCallback || failCallback) {
|
||||
callbackId = service + cordova.callbackId++;
|
||||
cordova.callbacks[callbackId] =
|
||||
{success: successCallback, fail: failCallback};
|
||||
}
|
||||
|
||||
actionArgs = massageArgsJsToNative(actionArgs);
|
||||
|
||||
// CB-10133 DataClone DOM Exception 25 guard (fast function remover)
|
||||
var command = [callbackId, service, action, JSON.parse(JSON.stringify(actionArgs))];
|
||||
window.webkit.messageHandlers.cordova.postMessage(command);
|
||||
};
|
||||
|
||||
iOSExec.nativeCallback = function (callbackId, status, message, keepCallback, debug) {
|
||||
var success = status === 0 || status === 1;
|
||||
var args = convertMessageToArgsNativeToJs(message);
|
||||
Promise.resolve().then(function () {
|
||||
cordova.callbackFromNative(callbackId, success, status, args, keepCallback); // eslint-disable-line
|
||||
});
|
||||
};
|
||||
|
||||
// for backwards compatibility
|
||||
iOSExec.nativeEvalAndFetch = function (func) {
|
||||
try {
|
||||
func();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Proxy the exec for bridge changes. See CB-10106
|
||||
|
||||
function cordovaExec () {
|
||||
var cexec = require('cordova/exec');
|
||||
var cexec_valid = (typeof cexec.nativeFetchMessages === 'function') && (typeof cexec.nativeEvalAndFetch === 'function') && (typeof cexec.nativeCallback === 'function');
|
||||
return (cexec_valid && execProxy !== cexec) ? cexec : iOSExec;
|
||||
}
|
||||
|
||||
function execProxy () {
|
||||
cordovaExec().apply(null, arguments);
|
||||
}
|
||||
|
||||
execProxy.nativeFetchMessages = function () {
|
||||
return cordovaExec().nativeFetchMessages.apply(null, arguments);
|
||||
};
|
||||
|
||||
execProxy.nativeEvalAndFetch = function () {
|
||||
return cordovaExec().nativeEvalAndFetch.apply(null, arguments);
|
||||
};
|
||||
|
||||
execProxy.nativeCallback = function () {
|
||||
return cordovaExec().nativeCallback.apply(null, arguments);
|
||||
};
|
||||
|
||||
module.exports = execProxy;
|
||||
|
||||
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.cordova && window.webkit.messageHandlers.cordova.postMessage) {
|
||||
// unregister the old bridge
|
||||
cordova.define.remove('cordova/exec');
|
||||
// redefine bridge to our new bridge
|
||||
cordova.define('cordova/exec', function (require, exports, module) {
|
||||
module.exports = execProxy;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
var exec = require('cordova/exec');
|
||||
|
||||
var WebView = {
|
||||
convertFileSrc: function(url) {
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
if (url.indexOf('/')===0) {
|
||||
return window.WEBVIEW_SERVER_URL + '/_app_file_' + url;
|
||||
}
|
||||
if (url.indexOf('file://')===0) {
|
||||
return window.WEBVIEW_SERVER_URL + url.replace('file://', '/_app_file_');
|
||||
}
|
||||
if (url.indexOf('content://')===0) {
|
||||
return window.WEBVIEW_SERVER_URL + url.replace('content:/', '/_app_content_');
|
||||
}
|
||||
return url;
|
||||
},
|
||||
setServerBasePath: function(path) {
|
||||
exec(null, null, 'IonicWebView', 'setServerBasePath', [path]);
|
||||
},
|
||||
getServerBasePath: function(callback) {
|
||||
exec(callback, null, 'IonicWebView', 'getServerBasePath', []);
|
||||
},
|
||||
persistServerBasePath: function() {
|
||||
exec(null, null, 'IonicWebView', 'persistServerBasePath', []);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebView;
|
||||
Reference in New Issue
Block a user