mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
185
Commits
docusaurus-A
...
v0.48.2
@@ -12,3 +12,4 @@ website/core/metadata.js
|
||||
website/core/metadata-blog.js
|
||||
website/src/react-native/docs/
|
||||
website/src/react-native/blog/
|
||||
danger/
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
|
||||
"react/display-name": 0,
|
||||
"react/jsx-boolean-value": 0,
|
||||
"react/jsx-no-comment-textnodes": 1,
|
||||
"react/jsx-no-duplicate-props": 2,
|
||||
"react/jsx-no-undef": 1,
|
||||
"react/jsx-sort-props": 0,
|
||||
|
||||
+9
-3
@@ -8,6 +8,9 @@
|
||||
; Ignore the website subdir
|
||||
<PROJECT_ROOT>/website/.*
|
||||
|
||||
; Ignore the Dangerfile
|
||||
<PROJECT_ROOT>/danger/dangerfile.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
@@ -19,6 +22,9 @@
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
@@ -38,12 +44,12 @@ suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-1]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-1]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
[version]
|
||||
^0.49.1
|
||||
^0.51.0
|
||||
|
||||
+17
-6
@@ -1,9 +1,20 @@
|
||||
docs/* @hramos
|
||||
blog/* @hramos
|
||||
docs/* @facebook/react-native
|
||||
blog/* @facebook/react-native
|
||||
Libraries/Animated/* @janicduplessis
|
||||
Libraries/NativeAnimation/* @janicduplessis
|
||||
Libraries/Image/* @shergin
|
||||
Libraries/Text/* @shergin
|
||||
React/Base/* @shergin @javache
|
||||
React/Views/* @shergin @javache
|
||||
React/Modules/* @shergin @javache
|
||||
React/CxxBridge/* @javache
|
||||
React/Executors/* @javache
|
||||
ReactAndroid/src/main/java/com/facebook/react/animated/* @janicduplessis
|
||||
website/* @hramos
|
||||
website/showcase.json @hramos
|
||||
package.json @hramos @ericnakagawa
|
||||
website/package.json @hramos @ericnakagawa
|
||||
ReactCommon/* @javache
|
||||
website/* @facebook/react-native
|
||||
website/showcase.json @facebook/react-native
|
||||
package.json @facebook/react-native
|
||||
website/package.json @facebook/react-native
|
||||
local-cli/core/* @grabbou @kureev
|
||||
local-cli/link/* @grabbou @kureev
|
||||
local-cli/unlink/* @grabbou @kureev
|
||||
|
||||
@@ -16,20 +16,20 @@
|
||||
If you answered "No":
|
||||
|
||||
We use GitHub Issues exclusively for tracking bugs in React Native. If you're looking for help,
|
||||
check out the How to Get In Touch section of the following guide:
|
||||
https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#how-to-get-in-touch
|
||||
check out the How to Get In Touch section of the following guide:
|
||||
https://facebook.github.io/react-native/docs/contributing.html#how-to-get-in-touch
|
||||
|
||||
Now scroll below!
|
||||
-->
|
||||
|
||||
|
||||
### Have you read the Bugs section of the Contributing to React Native Guide?
|
||||
### Have you read the Bugs section of the How to Contribute guide?
|
||||
|
||||
(Write your answer here.)
|
||||
|
||||
<!--
|
||||
Please read through the bug reporting guidelines thoroughly:
|
||||
https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#bugs
|
||||
https://facebook.github.io/react-native/docs/contributing.html#bugs
|
||||
-->
|
||||
|
||||
### Environment
|
||||
@@ -41,15 +41,15 @@
|
||||
1. `react-native -v`:
|
||||
2. `node -v`:
|
||||
3. `npm -v`:
|
||||
4. `yarn --version` (if you use Yarn):
|
||||
4. `yarn --version`<!-- (if you use Yarn) -->:
|
||||
|
||||
Then, specify:
|
||||
|
||||
<!-- (What platform are you building for? Choose any from iOS, Android, AppleTV.) -->
|
||||
- Target Platform:
|
||||
- Target Platform:
|
||||
|
||||
<!-- Which operating system are you using? Specify macOS, Windows, or Linux, along with specific release versions -->
|
||||
- Development Operating System:
|
||||
- Development Operating System:
|
||||
|
||||
<!-- Include any additional relevant information. Are you using Xcode or Android Studio to build native code? Is the issue specific to a particular iOS or Android SDK? -->
|
||||
- Build tools:
|
||||
|
||||
@@ -47,6 +47,7 @@ local.properties
|
||||
node_modules
|
||||
*.log
|
||||
.nvm
|
||||
/danger/node_modules/
|
||||
|
||||
# OS X
|
||||
.DS_Store
|
||||
|
||||
@@ -6,6 +6,7 @@ install:
|
||||
- nvm install 7
|
||||
- rm -Rf "${TMPDIR}/jest_preprocess_cache"
|
||||
- brew install yarn --ignore-dependencies
|
||||
- brew install watchman
|
||||
- yarn install
|
||||
|
||||
script:
|
||||
|
||||
+80
-56
@@ -1,16 +1,48 @@
|
||||
# Contributing to React Native
|
||||
|
||||
React Native is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody on https://facebook.com. We're still working out the kinks to make contributing to this project as easy and transparent as possible, but we're not quite there yet. Hopefully this document makes the process for contributing clear and preempts some questions you may have.
|
||||
<!-- generated_contributing_start -->
|
||||
React Native is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody using Facebook's mobile apps. If you're interested in contributing to React Native, hopefully this document makes the process for contributing clear.
|
||||
|
||||
## Code of Conduct
|
||||
Core contributors to React Native meet monthly and post their meeting notes on the [React Native blog](https://facebook.github.io/react-native/blog). You can also find ad hoc discussions in the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook group.
|
||||
|
||||
## [Code of Conduct](https://code.facebook.com/codeofconduct)
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated.
|
||||
|
||||
## Our Development Process
|
||||
## How to contribute
|
||||
|
||||
There are many ways to contribute to React Native, and many of them do not involve writing any code. Here's a few ideas to get started:
|
||||
|
||||
* Simply start using React Native. Go through the [Getting Started](http://facebook.github.io/react-native/docs/getting-started.html) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](http://facebook.github.io/react-native/docs/contributing.html#reporting-new-issues).
|
||||
* Look through the [open issues](https://github.com/facebook/react-native/issues). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](http://facebook.github.io/react-native/docs/contributing.html#triaging-issues-and-pull-requests).
|
||||
* If you find an issue you would like to fix, [open a pull request](http://facebook.github.io/react-native/docs/contributing.html#your-first-pull-request). Issues tagged as [`Good First Task`](https://github.com/facebook/react-native/labels/Good%20First%20Task) are a good place to get started.
|
||||
* Read through the [React Native docs](http://facebook.github.io/react-native/docs). If you find anything that is confusing or can be improved, you can make edits by clicking "Improve this page" at the bottom of most docs.
|
||||
* Browse [Stack Overflow](https://stackoverflow.com/questions/tagged/react-native) and answer questions. This will help you get familiarized with common pitfalls or misunderstandings, which can be useful when contributing updates to the documentation.
|
||||
* Take a look at the [features requested](https://react-native.canny.io/feature-requests) by others in the community and consider opening a pull request if you see something you want to work on.
|
||||
|
||||
Contributions are very welcome. If you think you need help planning your contribution, please hop into [#react-native](https://discord.gg/0ZcbPKXt5bZjGY5n) and let people know you're looking for a mentor.
|
||||
|
||||
### Triaging issues and pull requests
|
||||
|
||||
One great way you can contribute to the project without writing any code is to help triage issues and pull requests as they come in.
|
||||
|
||||
* Ask for more information if the issue does not provide all the details required by the template.
|
||||
* Suggest [labels](https://github.com/facebook/react-native/labels) that can help categorize issues.
|
||||
* Flag issues that are stale or that should be closed.
|
||||
* Ask for test plans and review code.
|
||||
|
||||
Adding labels, closing and reopening issues, and merging pull requests is, as you may expect, limited to a subset of contributors. Simply commenting on the issue or pull request can still go a long way towards helping us keep the number of outstanding issues under control.
|
||||
|
||||
Once you have become an active contributor in the community, you may gain access to the Facebook GitHub Bot, allowing you to perform some of these operations yourself. You can learn more about the bot in the [maintainer's guide](docs/maintainers.html#facebook-github-bot).
|
||||
|
||||
|
||||
## Our development process
|
||||
|
||||
Some of the core team will be working directly on [GitHub](https://github.com/facebook/react-native). These changes will be public from the beginning. Other changesets will come via a bridge with Facebook's internal source control. This is a necessity as it allows engineers at Facebook outside of the core team to move fast and contribute from an environment they are comfortable in.
|
||||
|
||||
## Branch Organization
|
||||
When a change made on GitHub is approved, it will first be imported into Facebook's internal source control. The change will eventually sync back to GitHub as a single commit once it has passed all internal tests.
|
||||
|
||||
### Branch organization
|
||||
|
||||
We will do our best to keep `master` in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We will do our best to [communicate these changes](https://github.com/facebook/react-native/releases) and version appropriately so you can lock into a specific version if need be.
|
||||
|
||||
@@ -18,69 +50,61 @@ To see what changes are coming and provide better feedback to React Native contr
|
||||
|
||||
## Bugs
|
||||
|
||||
#### Where to Find Known Issues
|
||||
We use [GitHub Issues](https://github.com/facebook/react-native/issues) for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you a are certain this is a new, unreported bug, you can submit a [bug report](http://facebook.github.io/react-native/docs/contributing.html#reporting-new-issues).
|
||||
|
||||
We are using [GitHub Issues](https://github.com/facebook/react-native/issues) for our public bugs. Before filing a new task, try to make sure your problem doesn't already exist.
|
||||
If you have questions about using React Native, the [help page](http://facebook.github.io/react-native/support.html) list various resources that should help you get started.
|
||||
|
||||
Questions and feature requests are tracked elsewhere:
|
||||
We also have a [place where you can request features or enhancements](https://react-native.canny.io/feature-requests). If you see anything you'd like to be implemented, vote it up and explain your use case.
|
||||
|
||||
- Have a question? [Ask on Stack Overflow](http://stackoverflow.com/questions/tagged/react-native).
|
||||
- If you have a question regarding future plans, check out the [roadmap](https://github.com/facebook/react-native/wiki/Roadmap).
|
||||
- Have a feature request that is not covered in the roadmap? [Add it here](https://react-native.canny.io/feature-requests).
|
||||
## Reporting new issues
|
||||
|
||||
#### Reporting New Issues
|
||||
When [opening a new issue](https://github.com/facebook/react-native/issues/new), always make sure to fill out the [issue template](https://raw.githubusercontent.com/facebook/react-native/master/.github/ISSUE_TEMPLATE.md). **This step is very important!** Not doing so may result in your issue getting closed. Don't take this personally if this happens, and feel free to open a new issue once you've gathered all the information required by the template.
|
||||
|
||||
The best way to get your bug fixed is to provide a reduced test case. Please provide either a [Sketch](https://sketch.expo.io/) or a public repository with a runnable example.
|
||||
* **One issue, one bug:** Please report a single bug per issue.
|
||||
* **Provide a Snack:** The best way to get attention on your issue is to provide a reduced test case. You can use [Snack](https://snack.expo.io/) to demonstrate the issue.
|
||||
* **Provide reproduction steps:** List all the steps necessary to reproduce the issue. Provide a Snack or upload a sample project to GitHub. The person reading your bug report should be able to follow these steps to reproduce your issue with minimal effort.
|
||||
* **Try out the latest version:** Verify that the issue can be reproduced locally by updating your project to use [React Native from `master`](http://facebook.github.io/react-native/versions.html). The bug may have already been fixed!
|
||||
|
||||
Please report a single bug per issue. Always provide reproduction steps. You can use Snack in many cases to demonstrate an issue: https://snack.expo.io/. If the bug cannot be reproduced using Snack, verify that the issue can be reproduced locally by targeting the latest release candidate. Ideally, check if the issue is present in master as well.
|
||||
We're not able to provide support through GitHub Issues. If you're looking for help with your code, consider asking on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) or reaching out to the community through [other channels](https://facebook.github.io/react-native/support.html).
|
||||
|
||||
Do not forget to include sample code that reproduces the issue. Only open issues for bugs affecting either the latest stable release, or the current release candidate, or master (see http://facebook.github.io/react-native/versions.html). If it is not clear from your report that the issue can be reproduced in one of these releases, your issue will be closed.
|
||||
|
||||
We're not able to provide support through GitHub Issues. If you're looking for help with your code, consider asking on Stack Overflow: http://stackoverflow.com/questions/tagged/react-native
|
||||
|
||||
#### Security Bugs
|
||||
### Security bugs
|
||||
|
||||
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. With that in mind, please do not file public issues; go through the process outlined on that page.
|
||||
|
||||
## How to Get in Touch
|
||||
## Pull requests
|
||||
|
||||
Many React Native users are active on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native).
|
||||
### Your first pull request
|
||||
|
||||
If you want to get a general sense of what React Native folks talk about, check out the [React Native Community](https://www.facebook.com/groups/react.native.community) Facebook group.
|
||||
So you have decided to contribute code back to upstream by opening a pull request. You've invested a good chunk of time, and we appreciate it. We will do our best to work with you and get the PR looked at.
|
||||
|
||||
There is also [an active community of React and React Native users on the Discord chat platform](https://discord.gg/0ZcbPKXt5bZjGY5n) in case you need help.
|
||||
Working on your first Pull Request? You can learn how from this free video series:
|
||||
|
||||
The React Native team sends out periodical updates through the following channels:
|
||||
[**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||
|
||||
* [Blog](https://facebook.github.io/react-native/blog/)
|
||||
* [Twitter](https://www.twitter.com/reactnative)
|
||||
We have a list of [beginner friendly issues](https://github.com/facebook/react-native/labels/Good%20First%20Task) to help you get your feet wet in the React Native codebase and familiar with our contribution process. This is a great place to get started.
|
||||
|
||||
Core contributors to React Native meet monthly and post their meeting notes on the React Native blog. You can also find ad hoc discussions in the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook group.
|
||||
### Proposing a change
|
||||
|
||||
## Proposing a Change
|
||||
If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, we have a [place to track feature requests](https://react-native.canny.io/feature-requests).
|
||||
|
||||
If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend [filing an issue](https://github.com/facebook/react-native/issues/new). This lets us reach an agreement on your proposal before you put significant effort into it.
|
||||
|
||||
If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend to file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
If you send a pull request, please do it against the master branch. We maintain stable branches for stable releases separately but we don't accept pull requests to them directly. Instead, we cherry-pick non-breaking changes from master to the latest stable version.
|
||||
|
||||
The core team will be monitoring for pull requests. When we get one, we'll run some Facebook-specific integration tests on it first. From here, we'll need to get another person to sign off on the changes and then merge the pull request. For API changes we may need to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback throughout the process.
|
||||
### Sending a pull request
|
||||
|
||||
Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it.
|
||||
|
||||
*Before* submitting a pull request, please make sure the following is done…
|
||||
**Before submitting a pull request**, please make sure the following is done:
|
||||
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
1. Fork [the repository](https://github.com/facebook/react-native) and create your branch from `master`.
|
||||
2. Add the copyright notice to the top of any new files you've added.
|
||||
3. Describe your **test plan** in your commit.
|
||||
4. Ensure **tests pass** on Travis and Circle CI.
|
||||
5. Make sure your code lints (`node linter.js <files touched>`).
|
||||
6. If you haven't already, sign the CLA: https://code.facebook.com/cla
|
||||
7. Squash your commits (`git rebase -i`).
|
||||
One intent alongside one commit makes it clearer for people to review and easier to understand your intention.
|
||||
3. Describe your [**test plan**](https://facebook.github.io/react-native/docs/contributing.html#test-plan) in your commit.
|
||||
4. Ensure [**tests pass**](https://facebook.github.io/react-native/docs/contributing.html#contrinuous-integration-tests) on both Travis and Circle CI.
|
||||
5. Make sure your code lints (`npm run lint`).
|
||||
6. If you haven't already, [sign the CLA](https://code.facebook.com/cla).
|
||||
|
||||
All pull requests should be opened against the `master` branch.
|
||||
|
||||
> **Note:** It is not necessary to keep clicking `Merge master to your branch` on the PR page. You would want to merge master if there are conflicts or tests are failing. The Facebook-GitHub-Bot ultimately squashes all commits to a single one before merging your PR.
|
||||
|
||||
@@ -88,18 +112,15 @@ Small pull requests are much easier to review and more likely to get merged. Mak
|
||||
|
||||
A good test plan has the exact commands you ran and their output, provides screenshots or videos if the pull request changes UI or updates the website.
|
||||
|
||||
- If you've added code that should be tested, add tests!
|
||||
- If you've changed APIs, update the documentation.
|
||||
- If you've updated the docs, verify the website locally and submit screenshots if applicable (see `website/README.md`)
|
||||
* If you've added code that should be tested, add tests!
|
||||
* If you've changed APIs, update the documentation.
|
||||
* If you've updated the docs, verify the website locally and submit screenshots if applicable (see [website/README.md](https://github.com/facebook/react-native/blob/master/website/README.md))
|
||||
|
||||
See "What is a Test Plan?" to learn more:
|
||||
https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9
|
||||
See [What is a Test Plan?](https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9) to learn more.
|
||||
|
||||
#### Continuous integration tests
|
||||
|
||||
Make sure all **tests pass** on both [Travis][travis] and [Circle CI][circle]. PRs that break tests are unlikely to be merged.
|
||||
|
||||
You can learn more about running tests and contributing to React Native here: https://facebook.github.io/react-native/docs/testing.html
|
||||
Make sure all **tests pass** on both [Travis][travis] and [Circle CI][circle]. PRs that break tests are unlikely to be merged. Learn more about [testing your changes here](https://facebook.github.io/react-native/docs/testing.html).
|
||||
|
||||
[travis]: https://travis-ci.org/facebook/react-native
|
||||
[circle]: http://circleci.com/gh/facebook/react-native
|
||||
@@ -111,10 +132,10 @@ When adding a new breaking change, follow this template in your pull request:
|
||||
```
|
||||
### New breaking change here
|
||||
|
||||
- **Who does this affect**:
|
||||
- **How to migrate**:
|
||||
- **Why make this breaking change**:
|
||||
- **Severity (number of people affected x effort)**:
|
||||
* **Who does this affect**:
|
||||
* **How to migrate**:
|
||||
* **Why make this breaking change**:
|
||||
* **Severity (number of people affected x effort)**:
|
||||
```
|
||||
|
||||
If your pull request is merged, a core contributor will update the [list of breaking changes](https://github.com/facebook/react-native/wiki/Breaking-Changes) which is then used to populate the release notes.
|
||||
@@ -136,15 +157,17 @@ Copy and paste this to the top of your new file(s):
|
||||
|
||||
If you've added a new module, add a `@providesModule <moduleName>` at the end of the comment. This will allow the haste package manager to find it.
|
||||
|
||||
### Contributor License Agreement (CLA)
|
||||
#### Contributor License Agreement (CLA)
|
||||
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, just let us know that you have completed the CLA and we can cross-check with your GitHub username.
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, the Facebook GitHub Bot will reply with a link to the CLA form. You may also [complete your CLA here](https://code.facebook.com/cla).
|
||||
|
||||
Complete your CLA here: https://code.facebook.com/cla
|
||||
### What happens next?
|
||||
|
||||
The core team will be monitoring for pull requests. Read [what to expect from maintainers](https://facebook.github.io/react-native/docs/maintainers.html#handling-pull-requests) to understand what may happen after you open a pull request.
|
||||
|
||||
## Style Guide
|
||||
|
||||
Our linter will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `node linter.js <files touched>`.
|
||||
Our linter will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `npm run lint`.
|
||||
|
||||
However, there are still some styles that the linter cannot pick up.
|
||||
|
||||
@@ -194,3 +217,4 @@ However, there are still some styles that the linter cannot pick up.
|
||||
## License
|
||||
|
||||
By contributing to React Native, you agree that your contributions will be licensed under its BSD license.
|
||||
<!-- generated_contributing_end -->
|
||||
|
||||
@@ -33,6 +33,7 @@ var TESTS = [
|
||||
require('./ImageCachePolicyTest'),
|
||||
require('./ImageSnapshotTest'),
|
||||
require('./PromiseTest'),
|
||||
require('./WebViewTest'),
|
||||
require('./SyncMethodTest'),
|
||||
require('./WebSocketTest'),
|
||||
require('./AccessibilityManagerTest'),
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
WebView,
|
||||
} = ReactNative;
|
||||
|
||||
var { TestModule } = ReactNative.NativeModules;
|
||||
|
||||
class WebViewTest extends React.Component {
|
||||
|
||||
render() {
|
||||
var firstMessageReceived = false;
|
||||
var secondMessageReceived = false;
|
||||
function processMessage(e) {
|
||||
var message = e.nativeEvent.data;
|
||||
if (message === 'First') {firstMessageReceived = true;}
|
||||
if (message === 'Second') {secondMessageReceived = true;}
|
||||
|
||||
// got both messages
|
||||
if (firstMessageReceived && secondMessageReceived) {TestModule.markTestPassed(true);}
|
||||
// wait for next message
|
||||
else if (firstMessageReceived && !secondMessageReceived) {return;}
|
||||
// first message got lost
|
||||
else if (!firstMessageReceived && secondMessageReceived) {throw new Error('First message got lost');}
|
||||
}
|
||||
var html = 'Hello world'
|
||||
+ '<script>'
|
||||
+ "window.setTimeout(function(){window.postMessage('First'); window.postMessage('Second')}, 0)"
|
||||
+ '</script>';
|
||||
|
||||
// fail if messages didn't get through;
|
||||
window.setTimeout(function() { throw new Error(firstMessageReceived ? 'Both messages got lost' : 'Second message got lost');}, 10000);
|
||||
|
||||
var source = {
|
||||
html: html,
|
||||
};
|
||||
|
||||
return (
|
||||
<WebView
|
||||
source={source}
|
||||
onMessage = {processMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WebViewTest.displayName = 'WebViewTest';
|
||||
|
||||
module.exports = WebViewTest;
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#import "JSContextRef.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void nativeProfilerEnableBytecode(void);
|
||||
void nativeProfilerStart(JSContextRef ctx, const char *title);
|
||||
void nativeProfilerEnd(JSContextRef ctx, const char *title, const char *filename);
|
||||
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
#include "JSCLegacyProfiler.h"
|
||||
|
||||
#include "APICast.h"
|
||||
#include "LegacyProfiler.h"
|
||||
#include "OpaqueJSString.h"
|
||||
#include "JSProfilerPrivate.h"
|
||||
#include "JSStringRef.h"
|
||||
#include "String.h"
|
||||
#include "Options.h"
|
||||
|
||||
enum json_gen_status {
|
||||
json_gen_status_ok = 0,
|
||||
json_gen_status_error = 1,
|
||||
};
|
||||
|
||||
enum json_entry {
|
||||
json_entry_key,
|
||||
json_entry_value,
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
struct json_state {
|
||||
FILE *fileOut;
|
||||
bool hasFirst;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
typedef json_state *json_gen;
|
||||
|
||||
static void json_escaped_cstring_printf(json_gen gen, const char *str) {
|
||||
const char *cursor = str;
|
||||
fputc('"', gen->fileOut);
|
||||
while (*cursor) {
|
||||
const char *escape = nullptr;
|
||||
switch (*cursor) {
|
||||
case '"':
|
||||
escape = "\\\"";
|
||||
break;
|
||||
case '\b':
|
||||
escape = "\\b";
|
||||
break;
|
||||
case '\f':
|
||||
escape = "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
escape = "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
escape = "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
escape = "\\t";
|
||||
break;
|
||||
case '\\':
|
||||
escape = "\\\\";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (escape != nullptr) {
|
||||
fwrite(escape, 1, strlen(escape), gen->fileOut);
|
||||
} else {
|
||||
fputc(*cursor, gen->fileOut);
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
fputc('"', gen->fileOut);
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_key_cstring(json_gen gen, const char *buffer) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
gen->hasFirst = true;
|
||||
|
||||
json_escaped_cstring_printf(gen, buffer);
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_map_open(json_gen gen, json_entry entryType) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (entryType == json_entry_value) {
|
||||
fprintf(gen->fileOut, ":");
|
||||
} else if (entryType == json_entry_key) {
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
}
|
||||
fprintf(gen->fileOut, "{");
|
||||
gen->hasFirst = false;
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_map_close(json_gen gen) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
fprintf(gen->fileOut, "}");
|
||||
gen->hasFirst = true;
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_array_open(json_gen gen, json_entry entryType) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (entryType == json_entry_value) {
|
||||
fprintf(gen->fileOut, ":");
|
||||
} else if (entryType == json_entry_key) {
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
}
|
||||
fprintf(gen->fileOut, "[");
|
||||
gen->hasFirst = false;
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_array_close(json_gen gen) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
fprintf(gen->fileOut, "]");
|
||||
gen->hasFirst = true;
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_keyvalue_cstring(json_gen gen, const char *key, const char *value) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
gen->hasFirst = true;
|
||||
|
||||
fprintf(gen->fileOut, "\"%s\" : ", key);
|
||||
json_escaped_cstring_printf(gen, value);
|
||||
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
|
||||
static json_gen_status json_gen_keyvalue_integer(json_gen gen, const char *key, int value) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
gen->hasFirst = true;
|
||||
|
||||
fprintf(gen->fileOut, "\"%s\": %d", key, value);
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status json_gen_keyvalue_double(json_gen gen, const char *key, double value) {
|
||||
if (gen->fileOut == nullptr) {
|
||||
return json_gen_status_error;
|
||||
}
|
||||
|
||||
if (gen->hasFirst) {
|
||||
fprintf(gen->fileOut, ",");
|
||||
}
|
||||
gen->hasFirst = true;
|
||||
|
||||
fprintf(gen->fileOut, "\"%s\": %.20g", key, value);
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen json_gen_alloc(const char *fileName) {
|
||||
json_gen gen = (json_gen)malloc(sizeof(json_state));
|
||||
memset(gen, 0, sizeof(json_state));
|
||||
gen->fileOut = fopen(fileName, "wb");
|
||||
return gen;
|
||||
}
|
||||
|
||||
static void json_gen_free(json_gen gen) {
|
||||
if (gen->fileOut) {
|
||||
fclose(gen->fileOut);
|
||||
}
|
||||
free(gen);
|
||||
}
|
||||
|
||||
#define GEN_AND_CHECK(expr) \
|
||||
do { \
|
||||
json_gen_status GEN_AND_CHECK_status = (expr); \
|
||||
if (GEN_AND_CHECK_status != json_gen_status_ok) { \
|
||||
return GEN_AND_CHECK_status; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
|
||||
static json_gen_status append_children_array_json(json_gen gen, const JSC::ProfileNode *node);
|
||||
static json_gen_status append_node_json(json_gen gen, const JSC::ProfileNode *node);
|
||||
|
||||
static json_gen_status append_root_json(json_gen gen, const JSC::Profile *profile) {
|
||||
GEN_AND_CHECK(json_gen_map_open(gen, json_entry_key));
|
||||
GEN_AND_CHECK(json_gen_key_cstring(gen, "rootNodes"));
|
||||
#if IOS8
|
||||
GEN_AND_CHECK(append_children_array_json(gen, profile->head()));
|
||||
#else
|
||||
GEN_AND_CHECK(append_children_array_json(gen, profile->rootNode()));
|
||||
#endif
|
||||
GEN_AND_CHECK(json_gen_map_close(gen));
|
||||
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status append_children_array_json(json_gen gen, const JSC::ProfileNode *node) {
|
||||
GEN_AND_CHECK(json_gen_array_open(gen, json_entry_value));
|
||||
for (RefPtr<JSC::ProfileNode> child : node->children()) {
|
||||
GEN_AND_CHECK(append_node_json(gen, child.get()));
|
||||
}
|
||||
GEN_AND_CHECK(json_gen_array_close(gen));
|
||||
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static json_gen_status append_node_json(json_gen gen, const JSC::ProfileNode *node) {
|
||||
GEN_AND_CHECK(json_gen_map_open(gen, json_entry_key));
|
||||
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "id", node->id()));
|
||||
|
||||
if (!node->functionName().isEmpty()) {
|
||||
GEN_AND_CHECK(json_gen_keyvalue_cstring(gen, "functionName", node->functionName().utf8().data()));
|
||||
}
|
||||
|
||||
if (!node->url().isEmpty()) {
|
||||
GEN_AND_CHECK(json_gen_keyvalue_cstring(gen, "url", node->url().utf8().data()));
|
||||
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "lineNumber", node->lineNumber()));
|
||||
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "columnNumber", node->columnNumber()));
|
||||
}
|
||||
|
||||
GEN_AND_CHECK(json_gen_key_cstring(gen, "calls"));
|
||||
GEN_AND_CHECK(json_gen_array_open(gen, json_entry_value));
|
||||
for (const JSC::ProfileNode::Call &call : node->calls()) {
|
||||
GEN_AND_CHECK(json_gen_map_open(gen, json_entry_key));
|
||||
GEN_AND_CHECK(json_gen_keyvalue_double(gen, "startTime", call.startTime()));
|
||||
#if IOS8
|
||||
GEN_AND_CHECK(json_gen_keyvalue_double(gen, "totalTime", call.totalTime()));
|
||||
#else
|
||||
GEN_AND_CHECK(json_gen_keyvalue_double(gen, "totalTime", call.elapsedTime()));
|
||||
#endif
|
||||
GEN_AND_CHECK(json_gen_map_close(gen));
|
||||
}
|
||||
GEN_AND_CHECK(json_gen_array_close(gen));
|
||||
|
||||
if (!node->children().isEmpty()) {
|
||||
GEN_AND_CHECK(json_gen_key_cstring(gen, "children"));
|
||||
GEN_AND_CHECK(append_children_array_json(gen, node));
|
||||
}
|
||||
|
||||
GEN_AND_CHECK(json_gen_map_close(gen));
|
||||
|
||||
return json_gen_status_ok;
|
||||
}
|
||||
|
||||
static void convert_to_json(const JSC::Profile *profile, const char *filename) {
|
||||
json_gen_status status;
|
||||
json_gen gen = json_gen_alloc(filename);
|
||||
|
||||
status = append_root_json(gen, profile);
|
||||
if (status != json_gen_status_ok) {
|
||||
FILE *fileOut = fopen(filename, "wb");
|
||||
if (fileOut != nullptr) {
|
||||
fprintf(fileOut, "{\"error\": %d}", (int)status);
|
||||
fclose(fileOut);
|
||||
}
|
||||
}
|
||||
json_gen_free(gen);
|
||||
}
|
||||
|
||||
// Based on JSEndProfiling, with a little extra code to return the profile as JSON.
|
||||
static void JSEndProfilingAndRender(JSContextRef ctx, const char *title, const char *filename)
|
||||
{
|
||||
JSC::ExecState *exec = toJS(ctx);
|
||||
JSC::LegacyProfiler *profiler = JSC::LegacyProfiler::profiler();
|
||||
RefPtr<JSC::Profile> rawProfile = profiler->stopProfiling(exec, WTF::String(title));
|
||||
convert_to_json(rawProfile.get(), filename);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void nativeProfilerEnableBytecode(void)
|
||||
{
|
||||
JSC::Options::setOption("forceProfilerBytecodeGeneration=true");
|
||||
}
|
||||
|
||||
void nativeProfilerStart(JSContextRef ctx, const char *title) {
|
||||
JSStartProfiling(ctx, JSStringCreateWithUTF8CString(title));
|
||||
}
|
||||
|
||||
void nativeProfilerEnd(JSContextRef ctx, const char *title, const char *filename) {
|
||||
JSEndProfilingAndRender(ctx, title, filename);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
ios9:
|
||||
IOS_VERSION=9 \
|
||||
JSC_VERSION=7601.1.46.3 \
|
||||
WEB_CORE_VERSION=7601.1.46.10 \
|
||||
WTF_VERSION=7601.1.46.3 \
|
||||
make -f Makefile.base
|
||||
|
||||
ios8:
|
||||
IOS_VERSION=8 \
|
||||
JSC_VERSION=7600.1.17 \
|
||||
WEB_CORE_VERSION=7600.1.25 \
|
||||
WTF_VERSION=7600.1.24 \
|
||||
make -f Makefile.base
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -rf $(wildcard *.dylib)
|
||||
-rm -rf download
|
||||
@@ -1,80 +0,0 @@
|
||||
HEADER_PATHS := `find download/JavaScriptCore/JavaScriptCore-$(JSC_VERSION) -name '*.h' | xargs -I{} dirname {} | uniq | xargs -I{} echo "-I {}"`
|
||||
|
||||
XCODE_PATH ?= $(shell xcode-select -p)
|
||||
SDK_PATH = $(XCODE_PATH)/Platforms/$1.platform/Developer/SDKs/$1.sdk
|
||||
SDK_VERSION = $(shell plutil -convert json -o - $(call SDK_PATH,iPhoneOS)/SDKSettings.plist | awk -f parseSDKVersion.awk)
|
||||
|
||||
CERT ?= iPhone Developer
|
||||
|
||||
ARCHS = x86_64 arm64 armv7 i386
|
||||
|
||||
PLATFORM = \
|
||||
if [[ "$*" = "x86_64" || "$*" = "i386" ]]; then \
|
||||
PLATFORM=iPhoneSimulator; \
|
||||
else \
|
||||
PLATFORM=iPhoneOS; \
|
||||
fi;
|
||||
|
||||
SYSROOT = -isysroot $(call SDK_PATH,$${PLATFORM})
|
||||
|
||||
IOS_LIBS = \
|
||||
download/JavaScriptCore/JavaScriptCore-$(JSC_VERSION) \
|
||||
download/WebCore/WebCore-$(WEB_CORE_VERSION) \
|
||||
download/WTF/WTF-$(WTF_VERSION) \
|
||||
download/JavaScriptCore/JavaScriptCore-$(JSC_VERSION)/Bytecodes.h
|
||||
|
||||
IOS_EXT=ios$(IOS_VERSION)
|
||||
|
||||
ifneq ($(SDK_VERSION), $(IOS_VERSION))
|
||||
|
||||
all:
|
||||
$(error "Expected to be compiled with iOS SDK version 8, found $(SDK_VERSION)")
|
||||
|
||||
else
|
||||
|
||||
all: RCTJSCProfiler.$(IOS_EXT).dylib /tmp/RCTJSCProfiler
|
||||
cp $^
|
||||
|
||||
endif
|
||||
|
||||
/tmp/RCTJSCProfiler:
|
||||
mkdir -p $@
|
||||
|
||||
RCTJSCProfiler.$(IOS_EXT).dylib: RCTJSCProfiler_unsigned.$(IOS_EXT).dylib
|
||||
cp $< $@
|
||||
codesign -f -s "${CERT}" $@
|
||||
|
||||
.PRECIOUS: RCTJSCProfiler_unsigned.$(IOS_EXT).dylib
|
||||
RCTJSCProfiler_unsigned.$(IOS_EXT).dylib: $(patsubst %,RCTJSCProfiler_%.$(IOS_EXT).dylib,$(ARCHS))
|
||||
lipo -create -output $@ $^
|
||||
|
||||
.PRECIOUS: RCTJSCProfiler_%.$(IOS_EXT).dylib
|
||||
RCTJSCProfiler_%.$(IOS_EXT).dylib: $(IOS_LIBS)
|
||||
$(PLATFORM) \
|
||||
clang -w -dynamiclib -o RCTJSCProfiler_$*.$(IOS_EXT).dylib -std=c++11 \
|
||||
-arch $* \
|
||||
-install_name RCTJSCProfiler.$(IOS_EXT).dylib \
|
||||
-include ./download/JavaScriptCore/JavaScriptCore-$(JSC_VERSION)/config.h \
|
||||
-I download \
|
||||
-I download/WebCore/WebCore-$(WEB_CORE_VERSION)/icu \
|
||||
-I download/WTF/WTF-$(WTF_VERSION) \
|
||||
-DNDEBUG=1 \
|
||||
-DIOS$(IOS_VERSION)=1 \
|
||||
-miphoneos-version-min=8.0 \
|
||||
$(SYSROOT) \
|
||||
$(HEADER_PATHS) \
|
||||
-undefined dynamic_lookup \
|
||||
JSCLegacyProfiler.mm
|
||||
|
||||
.PRECIOUS: %/Bytecodes.h
|
||||
%/Bytecodes.h:
|
||||
python $*/generate-bytecode-files --bytecodes_h $@ $*/bytecode/BytecodeList.json
|
||||
|
||||
.PRECIOUS: download/%
|
||||
download/%: download/%.tar.gz
|
||||
tar -zxvf $< -C `dirname $@` > /dev/null
|
||||
|
||||
.PRECIOUS: %.tar.gz
|
||||
%.tar.gz:
|
||||
mkdir -p `dirname $@`
|
||||
curl -o $@ http://www.opensource.apple.com/tarballs/$(patsubst download/%,%,$@)
|
||||
@@ -1,254 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import smap
|
||||
import trace_data
|
||||
import urllib
|
||||
|
||||
SECONDS_TO_NANOSECONDS = (1000*1000)
|
||||
SAMPLE_DELTA_IN_SECONDS = 0.0001
|
||||
|
||||
class Marker(object):
|
||||
def __init__(self, _name, _timestamp, _depth, _is_end, _ident, url, line, col):
|
||||
self.name = _name
|
||||
self.timestamp = _timestamp
|
||||
self.depth = _depth
|
||||
self.is_end = _is_end
|
||||
self.ident = _ident
|
||||
self.url = url
|
||||
self.line = line
|
||||
self.col = col
|
||||
|
||||
# sort markers making sure they are ordered by timestamp then depth of function call
|
||||
# and finally that markers of the same ident are sorted in the order begin then end
|
||||
def __cmp__(self, other):
|
||||
if self.timestamp < other.timestamp:
|
||||
return -1
|
||||
if self.timestamp > other.timestamp:
|
||||
return 1
|
||||
if self.depth < other.depth:
|
||||
return -1
|
||||
if self.depth > other.depth:
|
||||
return 1
|
||||
if self.ident == other.ident:
|
||||
if self.is_end:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
# calculate marker name based on combination of function name and location
|
||||
def _calcname(entry):
|
||||
funcname = ""
|
||||
if "functionName" in entry:
|
||||
funcname = funcname + entry["functionName"]
|
||||
return funcname
|
||||
|
||||
def _calcurl(mapcache, entry, map_file):
|
||||
if entry.url not in mapcache:
|
||||
map_url = entry.url.replace('.bundle', '.map')
|
||||
|
||||
if map_url != entry.url:
|
||||
if map_file:
|
||||
print('Loading sourcemap from:' + map_file)
|
||||
map_url = map_file
|
||||
|
||||
try:
|
||||
url_file = urllib.urlopen(map_url)
|
||||
if url_file != None:
|
||||
entries = smap.parse(url_file)
|
||||
mapcache[entry.url] = entries
|
||||
except Exception, e:
|
||||
mapcache[entry.url] = []
|
||||
|
||||
if entry.url in mapcache:
|
||||
source_entry = smap.find(mapcache[entry.url], entry.line, entry.col)
|
||||
if source_entry:
|
||||
entry.url = 'file://' + source_entry.src
|
||||
entry.line = source_entry.src_line
|
||||
entry.col = source_entry.src_col
|
||||
|
||||
def _compute_markers(markers, call_point, depth):
|
||||
name = _calcname(call_point)
|
||||
ident = len(markers)
|
||||
url = ""
|
||||
lineNumber = -1
|
||||
columnNumber = -1
|
||||
if "url" in call_point:
|
||||
url = call_point["url"]
|
||||
if "lineNumber" in call_point:
|
||||
lineNumber = call_point["lineNumber"]
|
||||
if "columnNumber" in call_point:
|
||||
columnNumber = call_point["columnNumber"]
|
||||
|
||||
for call in call_point["calls"]:
|
||||
markers.append(Marker(name, call["startTime"], depth, 0, ident, url, lineNumber, columnNumber))
|
||||
markers.append(Marker(name, call["startTime"] + call["totalTime"], depth, 1, ident, url, lineNumber, columnNumber))
|
||||
ident = ident + 2
|
||||
if "children" in call_point:
|
||||
for child in call_point["children"]:
|
||||
_compute_markers(markers, child, depth+1);
|
||||
|
||||
def _find_child(children, name):
|
||||
for child in children:
|
||||
if child['functionName'] == name:
|
||||
return child
|
||||
return None
|
||||
|
||||
def _add_entry_cpuprofiler_program(newtime, cpuprofiler):
|
||||
curnode = _find_child(cpuprofiler['head']['children'], '(program)')
|
||||
if cpuprofiler['lastTime'] != None:
|
||||
lastTime = cpuprofiler['lastTime']
|
||||
while lastTime < newtime:
|
||||
curnode['hitCount'] += 1
|
||||
cpuprofiler['samples'].append(curnode['callUID'])
|
||||
cpuprofiler['timestamps'].append(int(lastTime*SECONDS_TO_NANOSECONDS))
|
||||
lastTime += SAMPLE_DELTA_IN_SECONDS
|
||||
cpuprofiler['lastTime'] = lastTime
|
||||
else:
|
||||
cpuprofiler['lastTime'] = newtime
|
||||
|
||||
|
||||
def _add_entry_cpuprofiler(stack, newtime, cpuprofiler):
|
||||
index = len(stack) - 1
|
||||
marker = stack[index]
|
||||
|
||||
if marker.name not in cpuprofiler['markers']:
|
||||
cpuprofiler['markers'][marker.name] = cpuprofiler['id']
|
||||
cpuprofiler['callUID'] += 1
|
||||
callUID = cpuprofiler['markers'][marker.name]
|
||||
|
||||
curnode = cpuprofiler['head']
|
||||
index = 0
|
||||
while index < len(stack):
|
||||
newnode = _find_child(curnode['children'], stack[index].name)
|
||||
if newnode == None:
|
||||
newnode = {}
|
||||
newnode['callUID'] = callUID
|
||||
newnode['url'] = marker.url
|
||||
newnode['functionName'] = stack[index].name
|
||||
newnode['hitCount'] = 0
|
||||
newnode['lineNumber'] = marker.line
|
||||
newnode['columnNumber'] = marker.col
|
||||
newnode['scriptId'] = callUID
|
||||
newnode['positionTicks'] = []
|
||||
newnode['id'] = cpuprofiler['id']
|
||||
cpuprofiler['id'] += 1
|
||||
newnode['children'] = []
|
||||
curnode['children'].append(newnode)
|
||||
curnode['deoptReason'] = ''
|
||||
curnode = newnode
|
||||
index += 1
|
||||
|
||||
if cpuprofiler['lastTime'] == None:
|
||||
cpuprofiler['lastTime'] = newtime
|
||||
|
||||
if cpuprofiler['lastTime'] != None:
|
||||
lastTime = cpuprofiler['lastTime']
|
||||
while lastTime < newtime:
|
||||
curnode['hitCount'] += 1
|
||||
if len(curnode['positionTicks']) == 0:
|
||||
ticks = {}
|
||||
ticks['line'] = curnode['callUID']
|
||||
ticks['ticks'] = 0
|
||||
curnode['positionTicks'].append(ticks)
|
||||
curnode['positionTicks'][0]['ticks'] += 1
|
||||
cpuprofiler['samples'].append(curnode['callUID'])
|
||||
cpuprofiler['timestamps'].append(int(lastTime*1000*1000))
|
||||
lastTime += 0.0001
|
||||
cpuprofiler['lastTime'] = lastTime
|
||||
|
||||
def _create_default_cpuprofiler_node(name, _id, _uid):
|
||||
return {'functionName': name,
|
||||
'scriptId':'0',
|
||||
'url':'',
|
||||
'lineNumber':0,
|
||||
'columnNumber':0,
|
||||
'positionTicks':[],
|
||||
'id':_id,
|
||||
'callUID':_uid,
|
||||
'children': [],
|
||||
'hitCount': 0,
|
||||
'deoptReason':''}
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Converts JSON profile format to fbsystrace text output")
|
||||
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
dest = "output_file",
|
||||
default = None,
|
||||
help = "Output file for trace data")
|
||||
parser.add_argument(
|
||||
"-cpuprofiler",
|
||||
dest = "output_cpuprofiler",
|
||||
default = None,
|
||||
help = "Output file for cpuprofiler data")
|
||||
parser.add_argument(
|
||||
"-map",
|
||||
dest = "map_file",
|
||||
default = None,
|
||||
help = "Map file for symbolicating")
|
||||
parser.add_argument( "file", help = "JSON trace input_file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
markers = []
|
||||
with open(args.file, "r") as trace_file:
|
||||
trace = json.load(trace_file)
|
||||
for root_entry in trace["rootNodes"]:
|
||||
_compute_markers(markers, root_entry, 0)
|
||||
|
||||
mapcache = {}
|
||||
for m in markers:
|
||||
_calcurl(mapcache, m, args.map_file)
|
||||
|
||||
sorted_markers = list(sorted(markers));
|
||||
|
||||
if args.output_cpuprofiler != None:
|
||||
cpuprofiler = {}
|
||||
cpuprofiler['startTime'] = None
|
||||
cpuprofiler['endTime'] = None
|
||||
cpuprofiler['lastTime'] = None
|
||||
cpuprofiler['id'] = 4
|
||||
cpuprofiler['callUID'] = 4
|
||||
cpuprofiler['samples'] = []
|
||||
cpuprofiler['timestamps'] = []
|
||||
cpuprofiler['markers'] = {}
|
||||
cpuprofiler['head'] = _create_default_cpuprofiler_node('(root)', 1, 1)
|
||||
cpuprofiler['head']['children'].append(_create_default_cpuprofiler_node('(root)', 2, 2))
|
||||
cpuprofiler['head']['children'].append(_create_default_cpuprofiler_node('(program)', 3, 3))
|
||||
marker_stack = []
|
||||
with open(args.output_cpuprofiler, 'w') as file_out:
|
||||
for marker in sorted_markers:
|
||||
if len(marker_stack):
|
||||
_add_entry_cpuprofiler(marker_stack, marker.timestamp, cpuprofiler)
|
||||
else:
|
||||
_add_entry_cpuprofiler_program(marker.timestamp, cpuprofiler)
|
||||
if marker.is_end:
|
||||
marker_stack.pop()
|
||||
else:
|
||||
marker_stack.append(marker)
|
||||
cpuprofiler['startTime'] = cpuprofiler['timestamps'][0] / 1000000.0
|
||||
cpuprofiler['endTime'] = cpuprofiler['timestamps'][len(cpuprofiler['timestamps']) - 1] / 1000000.0
|
||||
json.dump(cpuprofiler, file_out, sort_keys=False, indent=4, separators=(',', ': '))
|
||||
|
||||
|
||||
if args.output_file != None:
|
||||
with open(args.output_file,"w") as trace_file:
|
||||
for marker in sorted_markers:
|
||||
start_or_end = None
|
||||
if marker.is_end:
|
||||
start_or_end = "E"
|
||||
else:
|
||||
start_or_end = "B"
|
||||
#output with timestamp at high level of precision
|
||||
trace_file.write("json-0 [000] .... {0:.12f}: tracing_mark_write: {1}|0|{2}\n".format(
|
||||
marker.timestamp,
|
||||
start_or_end,
|
||||
marker.name))
|
||||
|
||||
main()
|
||||
@@ -1,10 +0,0 @@
|
||||
BEGIN {
|
||||
FS = ":"
|
||||
RS = ","
|
||||
}
|
||||
|
||||
/"Version"/ {
|
||||
version = substr($2, 2, length($2) - 2)
|
||||
print int(version)
|
||||
exit 0
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
"""
|
||||
adapted from https://github.com/martine/python-sourcemap into a reuasable module
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2010
|
||||
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.
|
||||
"""
|
||||
|
||||
"""A module for parsing source maps, as output by the Closure and
|
||||
CoffeeScript compilers and consumed by browsers. See
|
||||
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/
|
||||
"""
|
||||
|
||||
import collections
|
||||
import json
|
||||
import sys
|
||||
import bisect
|
||||
|
||||
class entry(object):
|
||||
def __init__(self, dst_line, dst_col, src, src_line, src_col):
|
||||
self.dst_line = dst_line
|
||||
self.dst_col = dst_col
|
||||
self.src = src
|
||||
self.src_line = src_line
|
||||
self.src_col = src_col
|
||||
|
||||
def __cmp__(self, other):
|
||||
#print(self)
|
||||
#print(other)
|
||||
if self.dst_line < other.dst_line:
|
||||
return -1
|
||||
if self.dst_line > other.dst_line:
|
||||
return 1
|
||||
if self.dst_col < other.dst_col:
|
||||
return -1
|
||||
if self.dst_col > other.dst_col:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
SmapState = collections.namedtuple(
|
||||
'SmapState', ['dst_line', 'dst_col',
|
||||
'src', 'src_line', 'src_col',
|
||||
'name'])
|
||||
|
||||
# Mapping of base64 letter -> integer value.
|
||||
B64 = dict((c, i) for i, c in
|
||||
enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
'0123456789+/'))
|
||||
|
||||
|
||||
def _parse_vlq(segment):
|
||||
"""Parse a string of VLQ-encoded data.
|
||||
|
||||
Returns:
|
||||
a list of integers.
|
||||
"""
|
||||
|
||||
values = []
|
||||
|
||||
cur, shift = 0, 0
|
||||
for c in segment:
|
||||
val = B64[c]
|
||||
# Each character is 6 bits:
|
||||
# 5 of value and the high bit is the continuation.
|
||||
val, cont = val & 0b11111, val >> 5
|
||||
cur += val << shift
|
||||
shift += 5
|
||||
|
||||
if not cont:
|
||||
# The low bit of the unpacked value is the sign.
|
||||
cur, sign = cur >> 1, cur & 1
|
||||
if sign:
|
||||
cur = -cur
|
||||
values.append(cur)
|
||||
cur, shift = 0, 0
|
||||
|
||||
if cur or shift:
|
||||
raise Exception('leftover cur/shift in vlq decode')
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def _parse_smap(file):
|
||||
"""Given a file-like object, yield SmapState()s as they are read from it."""
|
||||
|
||||
smap = json.load(file)
|
||||
sources = smap['sources']
|
||||
names = smap['names']
|
||||
mappings = smap['mappings']
|
||||
lines = mappings.split(';')
|
||||
|
||||
dst_col, src_id, src_line, src_col, name_id = 0, 0, 0, 0, 0
|
||||
for dst_line, line in enumerate(lines):
|
||||
segments = line.split(',')
|
||||
dst_col = 0
|
||||
for segment in segments:
|
||||
if not segment:
|
||||
continue
|
||||
parsed = _parse_vlq(segment)
|
||||
dst_col += parsed[0]
|
||||
|
||||
src = None
|
||||
name = None
|
||||
if len(parsed) > 1:
|
||||
src_id += parsed[1]
|
||||
src = sources[src_id]
|
||||
src_line += parsed[2]
|
||||
src_col += parsed[3]
|
||||
|
||||
if len(parsed) > 4:
|
||||
name_id += parsed[4]
|
||||
name = names[name_id]
|
||||
|
||||
assert dst_line >= 0
|
||||
assert dst_col >= 0
|
||||
assert src_line >= 0
|
||||
assert src_col >= 0
|
||||
|
||||
yield SmapState(dst_line, dst_col, src, src_line, src_col, name)
|
||||
|
||||
def find(entries, line, col):
|
||||
test = entry(line, col, '', 0, 0)
|
||||
index = bisect.bisect_right(entries, test)
|
||||
if index == 0:
|
||||
return None
|
||||
return entries[index - 1]
|
||||
|
||||
def parse(file):
|
||||
# Simple demo that shows files that most contribute to total size.
|
||||
lookup = []
|
||||
for state in _parse_smap(file):
|
||||
lookup.append(entry(state.dst_line, state.dst_col, state.src, state.src_line, state.src_col))
|
||||
|
||||
sorted_lookup = list(sorted(lookup))
|
||||
return sorted_lookup
|
||||
@@ -1,244 +0,0 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import unittest
|
||||
|
||||
"""
|
||||
# _-----=> irqs-off
|
||||
# / _----=> need-resched
|
||||
# | / _---=> hardirq/softirq
|
||||
# || / _--=> preempt-depth
|
||||
# ||| / delay
|
||||
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
|
||||
# | | | |||| | |
|
||||
<idle>-0 [001] ...2 3269.291072: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmcqd/0 next_pid=120 next_prio=120
|
||||
"""
|
||||
TRACE_LINE_PATTERN = re.compile(
|
||||
r'^\s*(?P<task>.+)-(?P<pid>\d+)\s+(?:\((?P<tgid>.+)\)\s+)?\[(?P<cpu>\d+)\]\s+(?:(?P<flags>\S{4})\s+)?(?P<timestamp>[0-9.]+):\s+(?P<function>.+)$')
|
||||
|
||||
"""
|
||||
Example lines from custom app traces:
|
||||
0: B|27295|providerRemove
|
||||
0: E
|
||||
tracing_mark_write: S|27311|NNFColdStart<D-7744962>|1112249168
|
||||
"""
|
||||
APP_TRACE_LINE_PATTERN = re.compile(
|
||||
r'^(?P<type>.+?): (?P<args>.+)$')
|
||||
|
||||
"""
|
||||
Example section names:
|
||||
NNFColdStart
|
||||
NNFColdStart<0><T7744962>
|
||||
NNFColdStart<X>
|
||||
NNFColdStart<T7744962>
|
||||
"""
|
||||
DECORATED_SECTION_NAME_PATTERN = re.compile(r'^(?P<section_name>.*?)(?:<0>)?(?:<(?P<command>.)(?P<argument>.*?)>)?$')
|
||||
|
||||
SYSTRACE_LINE_TYPES = set(['0', 'tracing_mark_write'])
|
||||
|
||||
class TraceLine(object):
|
||||
def __init__(self, task, pid, tgid, cpu, flags, timestamp, function):
|
||||
self.task = task
|
||||
self.pid = pid
|
||||
self.tgid = tgid
|
||||
self.cpu = cpu
|
||||
self.flags = flags
|
||||
self.timestamp = timestamp
|
||||
self.function = function
|
||||
self.canceled = False
|
||||
|
||||
@property
|
||||
def is_app_trace_line(self):
|
||||
return isinstance(self.function, AppTraceFunction)
|
||||
|
||||
def cancel(self):
|
||||
self.canceled = True
|
||||
|
||||
def __str__(self):
|
||||
if self.canceled:
|
||||
return ""
|
||||
elif self.tgid:
|
||||
return "{task:>16s}-{pid:<5d} ({tgid:5s}) [{cpu:03d}] {flags:4s} {timestamp:12f}: {function}\n".format(**vars(self))
|
||||
elif self.flags:
|
||||
return "{task:>16s}-{pid:<5d} [{cpu:03d}] {flags:4s} {timestamp:12f}: {function}\n".format(**vars(self))
|
||||
else:
|
||||
return "{task:>16s}-{pid:<5d} [{cpu:03d}] {timestamp:12.6f}: {function}\n".format(**vars(self))
|
||||
|
||||
|
||||
class AppTraceFunction(object):
|
||||
def __init__(self, type, args):
|
||||
self.type = type
|
||||
self.args = args
|
||||
self.operation = args[0]
|
||||
|
||||
if len(args) >= 2 and args[1]:
|
||||
self.pid = int(args[1])
|
||||
if len(args) >= 3:
|
||||
self._section_name, self.command, self.argument = _parse_section_name(args[2])
|
||||
args[2] = self._section_name
|
||||
else:
|
||||
self._section_name = None
|
||||
self.command = None
|
||||
self.argument = None
|
||||
self.cookie = None
|
||||
|
||||
@property
|
||||
def section_name(self):
|
||||
return self._section_name
|
||||
|
||||
@section_name.setter
|
||||
def section_name(self, value):
|
||||
self._section_name = value
|
||||
self.args[2] = value
|
||||
|
||||
def __str__(self):
|
||||
return "{type}: {args}".format(type=self.type, args='|'.join(self.args))
|
||||
|
||||
|
||||
class AsyncTraceFunction(AppTraceFunction):
|
||||
def __init__(self, type, args):
|
||||
super(AsyncTraceFunction, self).__init__(type, args)
|
||||
|
||||
self.cookie = int(args[3])
|
||||
|
||||
|
||||
TRACE_TYPE_MAP = {
|
||||
'S': AsyncTraceFunction,
|
||||
'T': AsyncTraceFunction,
|
||||
'F': AsyncTraceFunction,
|
||||
}
|
||||
|
||||
def parse_line(line):
|
||||
match = TRACE_LINE_PATTERN.match(line.strip())
|
||||
if not match:
|
||||
return None
|
||||
|
||||
task = match.group("task")
|
||||
pid = int(match.group("pid"))
|
||||
tgid = match.group("tgid")
|
||||
cpu = int(match.group("cpu"))
|
||||
flags = match.group("flags")
|
||||
timestamp = float(match.group("timestamp"))
|
||||
function = match.group("function")
|
||||
|
||||
app_trace = _parse_function(function)
|
||||
if app_trace:
|
||||
function = app_trace
|
||||
|
||||
return TraceLine(task, pid, tgid, cpu, flags, timestamp, function)
|
||||
|
||||
def parse_dextr_line(line):
|
||||
task = line["name"]
|
||||
pid = line["pid"]
|
||||
tgid = line["tid"]
|
||||
cpu = None
|
||||
flags = None
|
||||
timestamp = line["ts"]
|
||||
function = AppTraceFunction("DextrTrace", [line["ph"], line["pid"], line["name"]])
|
||||
|
||||
return TraceLine(task, pid, tgid, cpu, flags, timestamp, function)
|
||||
|
||||
|
||||
def _parse_function(function):
|
||||
line_match = APP_TRACE_LINE_PATTERN.match(function)
|
||||
if not line_match:
|
||||
return None
|
||||
|
||||
type = line_match.group("type")
|
||||
if not type in SYSTRACE_LINE_TYPES:
|
||||
return None
|
||||
|
||||
args = line_match.group("args").split('|')
|
||||
if len(args) == 1 and len(args[0]) == 0:
|
||||
args = None
|
||||
|
||||
constructor = TRACE_TYPE_MAP.get(args[0], AppTraceFunction)
|
||||
return constructor(type, args)
|
||||
|
||||
|
||||
def _parse_section_name(section_name):
|
||||
if section_name is None:
|
||||
return section_name, None, None
|
||||
|
||||
section_name_match = DECORATED_SECTION_NAME_PATTERN.match(section_name)
|
||||
section_name = section_name_match.group("section_name")
|
||||
command = section_name_match.group("command")
|
||||
argument = section_name_match.group("argument")
|
||||
return section_name, command, argument
|
||||
|
||||
|
||||
def _format_section_name(section_name, command, argument):
|
||||
if not command:
|
||||
return section_name
|
||||
|
||||
return "{section_name}<{command}{argument}>".format(**vars())
|
||||
|
||||
|
||||
class RoundTripFormattingTests(unittest.TestCase):
|
||||
def testPlainSectionName(self):
|
||||
section_name = "SectionName12345-5562342fas"
|
||||
|
||||
self.assertEqual(section_name, _format_section_name(*_parse_section_name(section_name)))
|
||||
|
||||
def testDecoratedSectionName(self):
|
||||
section_name = "SectionName12345-5562342fas<D-123456>"
|
||||
|
||||
self.assertEqual(section_name, _format_section_name(*_parse_section_name(section_name)))
|
||||
|
||||
def testSimpleFunction(self):
|
||||
function = "0: E"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testFunctionWithoutCookie(self):
|
||||
function = "0: B|27295|providerRemove"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testFunctionWithCookie(self):
|
||||
function = "0: S|27311|NNFColdStart|1112249168"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testFunctionWithCookieAndArgs(self):
|
||||
function = "0: T|27311|NNFColdStart|1122|Start"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testFunctionWithArgsButNoPid(self):
|
||||
function = "0: E|||foo=bar"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testKitKatFunction(self):
|
||||
function = "tracing_mark_write: B|14127|Looper.dispatchMessage|arg=>>>>> Dispatching to Handler (android.os.Handler) {422ae980} null: 0|Java"
|
||||
|
||||
self.assertEqual(function, str(_parse_function(function)))
|
||||
|
||||
def testNonSysTraceFunctionIgnored(self):
|
||||
function = "sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmcqd/0 next_pid=120 next_prio=120"
|
||||
|
||||
self.assertEqual(None, _parse_function(function))
|
||||
|
||||
def testLineWithFlagsAndTGID(self):
|
||||
line = " <idle>-0 ( 550) [000] d..2 7953.258473: cpu_idle: state=1 cpu_id=0\n"
|
||||
|
||||
self.assertEqual(line, str(parse_line(line)))
|
||||
|
||||
def testLineWithFlagsAndNoTGID(self):
|
||||
line = " <idle>-0 (-----) [000] d..2 7953.258473: cpu_idle: state=1 cpu_id=0\n"
|
||||
|
||||
self.assertEqual(line, str(parse_line(line)))
|
||||
|
||||
def testLineWithFlags(self):
|
||||
line = " <idle>-0 [001] ...2 3269.291072: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmcqd/0 next_pid=120 next_prio=120\n"
|
||||
|
||||
self.assertEqual(line, str(parse_line(line)))
|
||||
|
||||
def testLineWithoutFlags(self):
|
||||
line = " <idle>-0 [001] 3269.291072: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmcqd/0 next_pid=120 next_prio=120\n"
|
||||
|
||||
self.assertEqual(line, str(parse_line(line)))
|
||||
@@ -152,7 +152,10 @@
|
||||
0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */,
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AdSupportIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AdSupport = require('NativeModules').AdSupport;
|
||||
|
||||
/**
|
||||
* `AdSupport` provides access to the "advertising identifier". If you link this library
|
||||
* in your project, you may need to justify your use for this identifier when submitting
|
||||
* your application to the App Store.
|
||||
*
|
||||
* In order to use `AdSupport` in your project, you must link the `RCTAdSupport` library.
|
||||
* In Xcode, you can manually add the `RCTAdSupport.m` and `RCTAdSupport.h` files from
|
||||
* `node_modules/react-native/Libraries/AdSupport/` to the `Libraries/React/Base/` folder
|
||||
* of your current project.
|
||||
*
|
||||
* You can refer to [Linking](docs/linking-libraries-ios.html) for help.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
getAdvertisingId: function(onSuccess: Function, onFailure: Function) {
|
||||
AdSupport.getAdvertisingId(onSuccess, onFailure);
|
||||
},
|
||||
|
||||
getAdvertisingTrackingEnabled: function(onSuccess: Function, onFailure: Function) {
|
||||
AdSupport.getAdvertisingTrackingEnabled(onSuccess, onFailure);
|
||||
},
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTAdSupport.h"
|
||||
|
||||
#import <AdSupport/ASIdentifierManager.h>
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
@implementation RCTAdSupport
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_METHOD(getAdvertisingId:(RCTResponseSenderBlock)callback
|
||||
withErrorCallback:(RCTResponseErrorBlock)errorCallback)
|
||||
{
|
||||
NSUUID *advertisingIdentifier = [ASIdentifierManager sharedManager].advertisingIdentifier;
|
||||
if (advertisingIdentifier) {
|
||||
callback(@[advertisingIdentifier.UUIDString]);
|
||||
} else {
|
||||
errorCallback(RCTErrorWithMessage(@"Advertising identifier is unavailable."));
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getAdvertisingTrackingEnabled:(RCTResponseSenderBlock)callback
|
||||
withErrorCallback:(__unused RCTResponseSenderBlock)errorCallback)
|
||||
{
|
||||
callback(@[@([ASIdentifierManager sharedManager].advertisingTrackingEnabled)]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,257 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
832C819C1AAF6E1A007FA2F7 /* RCTAdSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
832C817E1AAF6DEF007FA2F7 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTAdSupport.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
832C819A1AAF6E1A007FA2F7 /* RCTAdSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTAdSupport.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAdSupport.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
832C817D1AAF6DEF007FA2F7 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
832C81771AAF6DEF007FA2F7 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
832C819A1AAF6E1A007FA2F7 /* RCTAdSupport.h */,
|
||||
832C819B1AAF6E1A007FA2F7 /* RCTAdSupport.m */,
|
||||
832C81811AAF6DEF007FA2F7 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
};
|
||||
832C81811AAF6DEF007FA2F7 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
832C817F1AAF6DEF007FA2F7 /* RCTAdSupport */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTAdSupport" */;
|
||||
buildPhases = (
|
||||
832C817C1AAF6DEF007FA2F7 /* Sources */,
|
||||
832C817D1AAF6DEF007FA2F7 /* Frameworks */,
|
||||
832C817E1AAF6DEF007FA2F7 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RCTAdSupport;
|
||||
productName = RCTAdSupport;
|
||||
productReference = 832C81801AAF6DEF007FA2F7 /* libRCTAdSupport.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
832C81781AAF6DEF007FA2F7 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0620;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
832C817F1AAF6DEF007FA2F7 = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTAdSupport" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 832C81771AAF6DEF007FA2F7;
|
||||
productRefGroup = 832C81811AAF6DEF007FA2F7 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
832C817F1AAF6DEF007FA2F7 /* RCTAdSupport */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
832C817C1AAF6DEF007FA2F7 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
832C819C1AAF6E1A007FA2F7 /* RCTAdSupport.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
832C81921AAF6DF0007FA2F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
WARNING_CFLAGS = (
|
||||
"-Werror",
|
||||
"-Wall",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
832C81931AAF6DF0007FA2F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WARNING_CFLAGS = (
|
||||
"-Werror",
|
||||
"-Wall",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
832C81951AAF6DF0007FA2F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
832C81961AAF6DF0007FA2F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTAdSupport" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
832C81921AAF6DF0007FA2F7 /* Debug */,
|
||||
832C81931AAF6DF0007FA2F7 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTAdSupport" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
832C81951AAF6DF0007FA2F7 /* Debug */,
|
||||
832C81961AAF6DF0007FA2F7 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 832C81781AAF6DEF007FA2F7 /* Project object */;
|
||||
}
|
||||
@@ -459,6 +459,7 @@ type SpringAnimationConfig = AnimationConfig & {
|
||||
speed?: number,
|
||||
tension?: number,
|
||||
friction?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
type SpringAnimationConfigSingle = AnimationConfig & {
|
||||
@@ -471,6 +472,7 @@ type SpringAnimationConfigSingle = AnimationConfig & {
|
||||
speed?: number,
|
||||
tension?: number,
|
||||
friction?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
function withDefault<T>(value: ?T, defaultValue: T): T {
|
||||
@@ -492,6 +494,8 @@ class SpringAnimation extends Animation {
|
||||
_toValue: any;
|
||||
_tension: number;
|
||||
_friction: number;
|
||||
_delay: number;
|
||||
_timeout: any;
|
||||
_lastTime: number;
|
||||
_onUpdate: (value: number) => void;
|
||||
_animationFrame: any;
|
||||
@@ -508,6 +512,7 @@ class SpringAnimation extends Animation {
|
||||
this._initialVelocity = config.velocity;
|
||||
this._lastVelocity = withDefault(config.velocity, 0);
|
||||
this._toValue = config.toValue;
|
||||
this._delay = withDefault(config.delay, 0);
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
||||
@@ -571,10 +576,20 @@ class SpringAnimation extends Animation {
|
||||
this._initialVelocity !== null) {
|
||||
this._lastVelocity = this._initialVelocity;
|
||||
}
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
|
||||
var start = () => {
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
} else {
|
||||
this.onUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
// If this._delay is more than 0, we start after the timeout.
|
||||
if (this._delay) {
|
||||
this._timeout = setTimeout(start, this._delay);
|
||||
} else {
|
||||
this.onUpdate();
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,6 +700,7 @@ class SpringAnimation extends Animation {
|
||||
stop(): void {
|
||||
super.stop();
|
||||
this.__active = false;
|
||||
clearTimeout(this._timeout);
|
||||
global.cancelAnimationFrame(this._animationFrame);
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
@@ -1108,6 +1124,11 @@ class AnimatedInterpolation extends AnimatedWithChildren {
|
||||
this._interpolation = Interpolation.create(config);
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._parent.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number | string {
|
||||
var parentValue: number = this._parent.__getValue();
|
||||
invariant(
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
*
|
||||
* @providesModule BatchedBridge
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const MessageQueue = require('MessageQueue');
|
||||
|
||||
const BatchedBridge = new MessageQueue();
|
||||
|
||||
// Wire up the batched bridge on the global object so that we can call into it.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule MessageQueue
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*eslint no-bitwise: 0*/
|
||||
@@ -24,9 +25,13 @@ const stringifySafe = require('stringifySafe');
|
||||
export type SpyData = {
|
||||
type: number,
|
||||
module: ?string,
|
||||
method: string|number,
|
||||
args: any
|
||||
}
|
||||
method: string | number,
|
||||
isSync: boolean,
|
||||
successCbId: number,
|
||||
failCbId: number,
|
||||
args: Array<any>,
|
||||
returnValue?: any,
|
||||
};
|
||||
|
||||
const TO_JS = 0;
|
||||
const TO_NATIVE = 1;
|
||||
@@ -38,13 +43,13 @@ const MIN_TIME_BETWEEN_FLUSHES_MS = 5;
|
||||
|
||||
const TRACE_TAG_REACT_APPS = 1 << 17;
|
||||
|
||||
const DEBUG_INFO_LIMIT = 32;
|
||||
const DEBUG_INFO_LIMIT = 64;
|
||||
|
||||
// Work around an initialization order issue
|
||||
let JSTimers = null;
|
||||
|
||||
class MessageQueue {
|
||||
_lazyCallableModules: {[key: string]: void => Object};
|
||||
_lazyCallableModules: {[key: string]: (void) => Object};
|
||||
_queue: [Array<number>, Array<number>, Array<any>, number];
|
||||
_successCallbacks: Array<?Function>;
|
||||
_failureCallbacks: Array<?Function>;
|
||||
@@ -73,23 +78,19 @@ class MessageQueue {
|
||||
this._remoteModuleTable = {};
|
||||
this._remoteMethodTable = {};
|
||||
}
|
||||
|
||||
(this:any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind(this);
|
||||
(this:any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind(this);
|
||||
(this:any).flushedQueue = this.flushedQueue.bind(this);
|
||||
(this:any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public APIs
|
||||
*/
|
||||
|
||||
static spy(spyOrToggle: boolean|(data: SpyData) => void){
|
||||
if (spyOrToggle === true){
|
||||
static spy(spyOrToggle: boolean | ((data: SpyData) => void)) {
|
||||
if (spyOrToggle === true) {
|
||||
MessageQueue.prototype.__spy = info => {
|
||||
console.log(`${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
|
||||
`${info.module ? (info.module + '.') : ''}${info.method}` +
|
||||
`(${JSON.stringify(info.args)})`);
|
||||
console.log(
|
||||
`${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
|
||||
`${info.module ? info.module + '.' : ''}${info.method}` +
|
||||
`(${JSON.stringify(info.args)})`,
|
||||
);
|
||||
};
|
||||
} else if (spyOrToggle === false) {
|
||||
MessageQueue.prototype.__spy = null;
|
||||
@@ -98,32 +99,40 @@ class MessageQueue {
|
||||
}
|
||||
}
|
||||
|
||||
callFunctionReturnFlushedQueue(module: string, method: string, args: Array<any>) {
|
||||
callFunctionReturnFlushedQueue = (
|
||||
module: string,
|
||||
method: string,
|
||||
args: Array<any>,
|
||||
) => {
|
||||
this.__guard(() => {
|
||||
this.__callFunction(module, method, args);
|
||||
});
|
||||
|
||||
return this.flushedQueue();
|
||||
}
|
||||
};
|
||||
|
||||
callFunctionReturnResultAndFlushedQueue(module: string, method: string, args: Array<any>) {
|
||||
callFunctionReturnResultAndFlushedQueue = (
|
||||
module: string,
|
||||
method: string,
|
||||
args: Array<any>,
|
||||
) => {
|
||||
let result;
|
||||
this.__guard(() => {
|
||||
result = this.__callFunction(module, method, args);
|
||||
});
|
||||
|
||||
return [result, this.flushedQueue()];
|
||||
}
|
||||
};
|
||||
|
||||
invokeCallbackAndReturnFlushedQueue(cbID: number, args: Array<any>) {
|
||||
invokeCallbackAndReturnFlushedQueue = (cbID: number, args: Array<any>) => {
|
||||
this.__guard(() => {
|
||||
this.__invokeCallback(cbID, args);
|
||||
});
|
||||
|
||||
return this.flushedQueue();
|
||||
}
|
||||
};
|
||||
|
||||
flushedQueue() {
|
||||
flushedQueue = () => {
|
||||
this.__guard(() => {
|
||||
this.__callImmediates();
|
||||
});
|
||||
@@ -131,7 +140,7 @@ class MessageQueue {
|
||||
const queue = this._queue;
|
||||
this._queue = [[], [], [], this._callID];
|
||||
return queue[0].length ? queue : null;
|
||||
}
|
||||
};
|
||||
|
||||
getEventLoopRunningTime() {
|
||||
return new Date().getTime() - this._eventLoopStartTime;
|
||||
@@ -143,7 +152,7 @@ class MessageQueue {
|
||||
|
||||
registerLazyCallableModule(name: string, factory: void => Object) {
|
||||
let module: Object;
|
||||
let getValue: ?(void => Object) = factory;
|
||||
let getValue: ?(void) => Object = factory;
|
||||
this._lazyCallableModules[name] = () => {
|
||||
if (getValue) {
|
||||
module = getValue();
|
||||
@@ -158,7 +167,13 @@ class MessageQueue {
|
||||
return getValue ? getValue() : null;
|
||||
}
|
||||
|
||||
enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
|
||||
enqueueNativeCall(
|
||||
moduleID: number,
|
||||
methodID: number,
|
||||
params: Array<any>,
|
||||
onFail: ?Function,
|
||||
onSucc: ?Function,
|
||||
) {
|
||||
if (onFail || onSucc) {
|
||||
if (__DEV__) {
|
||||
this._debugInfo[this._callID] = [moduleID, methodID];
|
||||
@@ -176,7 +191,11 @@ class MessageQueue {
|
||||
|
||||
if (__DEV__) {
|
||||
global.nativeTraceBeginAsyncFlow &&
|
||||
global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID);
|
||||
global.nativeTraceBeginAsyncFlow(
|
||||
TRACE_TAG_REACT_APPS,
|
||||
'native',
|
||||
this._callID,
|
||||
);
|
||||
}
|
||||
this._callID++;
|
||||
|
||||
@@ -188,32 +207,51 @@ class MessageQueue {
|
||||
JSON.stringify(params);
|
||||
|
||||
// The params object should not be mutated after being queued
|
||||
deepFreezeAndThrowOnMutationInDev((params:any));
|
||||
deepFreezeAndThrowOnMutationInDev((params: any));
|
||||
}
|
||||
this._queue[PARAMS].push(params);
|
||||
|
||||
const now = new Date().getTime();
|
||||
if (global.nativeFlushQueueImmediate &&
|
||||
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
|
||||
this._inCall === 0)) {
|
||||
if (
|
||||
global.nativeFlushQueueImmediate &&
|
||||
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
|
||||
this._inCall === 0)
|
||||
) {
|
||||
var queue = this._queue;
|
||||
this._queue = [[], [], [], this._callID];
|
||||
this._lastFlush = now;
|
||||
global.nativeFlushQueueImmediate(queue);
|
||||
}
|
||||
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
|
||||
if (__DEV__ && this.__spy && isFinite(moduleID)) {
|
||||
this.__spy(
|
||||
{ type: TO_NATIVE,
|
||||
module: this._remoteModuleTable[moduleID],
|
||||
method: this._remoteMethodTable[moduleID][methodID],
|
||||
args: params }
|
||||
);
|
||||
} else if (this.__spy) {
|
||||
this.__spy({type: TO_NATIVE, module: moduleID + '', method: methodID, args: params});
|
||||
|
||||
if (this.__spy) {
|
||||
this.__spyNativeCall(moduleID, methodID, params, {
|
||||
failCbId: onFail ? params[params.length - 2] : -1,
|
||||
successCbId: onSucc ? params[params.length - 1] : -1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
callSyncHook(moduleID: number, methodID: number, args: Array<any>) {
|
||||
if (__DEV__) {
|
||||
invariant(
|
||||
global.nativeCallSyncHook,
|
||||
'Calling synchronous methods on native ' +
|
||||
'modules is not supported in Chrome.\n\n Consider providing alternative ' +
|
||||
'methods to expose this method in debug mode, e.g. by exposing constants ' +
|
||||
'ahead-of-time.',
|
||||
);
|
||||
}
|
||||
const returnValue = global.nativeCallSyncHook(moduleID, methodID, args);
|
||||
if (this.__spy) {
|
||||
this.__spyNativeCall(moduleID, methodID, args, {
|
||||
isSync: true,
|
||||
returnValue,
|
||||
});
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
createDebugLookup(moduleID: number, name: string, methods: Array<string>) {
|
||||
if (__DEV__) {
|
||||
this._remoteModuleTable[moduleID] = name;
|
||||
@@ -250,18 +288,20 @@ class MessageQueue {
|
||||
this._eventLoopStartTime = this._lastFlush;
|
||||
Systrace.beginEvent(`${module}.${method}()`);
|
||||
if (this.__spy) {
|
||||
this.__spy({ type: TO_JS, module, method, args});
|
||||
this.__spyJSCall(module, method, args);
|
||||
}
|
||||
const moduleMethods = this.getCallableModule(module);
|
||||
invariant(
|
||||
!!moduleMethods,
|
||||
'Module %s is not a registered callable module (calling %s)',
|
||||
module, method
|
||||
module,
|
||||
method,
|
||||
);
|
||||
invariant(
|
||||
!!moduleMethods[method],
|
||||
'Method %s does not exist on module %s',
|
||||
method, module
|
||||
method,
|
||||
module,
|
||||
);
|
||||
const result = moduleMethods[method].apply(moduleMethods, args);
|
||||
Systrace.endEvent();
|
||||
@@ -274,7 +314,10 @@ class MessageQueue {
|
||||
|
||||
// The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left.
|
||||
const callID = cbID >>> 1;
|
||||
const callback = (cbID & 1) ? this._successCallbacks[callID] : this._failureCallbacks[callID];
|
||||
const isSuccess = cbID & 1;
|
||||
const callback = isSuccess
|
||||
? this._successCallbacks[callID]
|
||||
: this._failureCallbacks[callID];
|
||||
|
||||
if (__DEV__) {
|
||||
const debug = this._debugInfo[callID];
|
||||
@@ -283,20 +326,24 @@ class MessageQueue {
|
||||
if (!callback) {
|
||||
let errorMessage = `Callback with id ${cbID}: ${module}.${method}() not found`;
|
||||
if (method) {
|
||||
errorMessage = `The callback ${method}() exists in module ${module}, `
|
||||
+ 'but only one callback may be registered to a function in a native module.';
|
||||
errorMessage =
|
||||
`The callback ${method}() exists in module ${module}, ` +
|
||||
'but only one callback may be registered to a function in a native module.';
|
||||
}
|
||||
invariant(
|
||||
callback,
|
||||
errorMessage
|
||||
);
|
||||
invariant(callback, errorMessage);
|
||||
}
|
||||
const profileName = debug ? '<callback for ' + module + '.' + method + '>' : cbID;
|
||||
if (callback && this.__spy) {
|
||||
this.__spy({ type: TO_JS, module:null, method:profileName, args });
|
||||
const profileName = debug
|
||||
? '<callback for ' + module + '.' + method + '>'
|
||||
: cbID + '';
|
||||
if (this.__spy) {
|
||||
this.__spyJSCall(null, profileName, args, {
|
||||
failCbId: isSuccess ? -1 : cbID,
|
||||
successCbId: isSuccess ? cbID : -1,
|
||||
});
|
||||
}
|
||||
Systrace.beginEvent(
|
||||
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`);
|
||||
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
@@ -310,6 +357,57 @@ class MessageQueue {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
}
|
||||
|
||||
__spyJSCall(
|
||||
module: ?string,
|
||||
method: string,
|
||||
methodArgs: Array<any>,
|
||||
params: any,
|
||||
) {
|
||||
if (!this.__spy) {
|
||||
return;
|
||||
}
|
||||
this.__spy({
|
||||
type: TO_JS,
|
||||
isSync: false,
|
||||
module,
|
||||
method,
|
||||
failCbId: -1,
|
||||
successCbId: -1,
|
||||
args: methodArgs,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
__spyNativeCall(
|
||||
moduleID: number,
|
||||
methodID: number,
|
||||
methodArgs: Array<any>,
|
||||
params: any,
|
||||
) {
|
||||
const spy = this.__spy;
|
||||
if (!spy) {
|
||||
return;
|
||||
}
|
||||
|
||||
let moduleName = moduleID + '';
|
||||
let methodName = methodID;
|
||||
if (__DEV__ && isFinite(moduleID)) {
|
||||
moduleName = this._remoteModuleTable[moduleID];
|
||||
methodName = this._remoteMethodTable[moduleID][methodID];
|
||||
}
|
||||
|
||||
spy({
|
||||
type: TO_NATIVE,
|
||||
isSync: false,
|
||||
module: moduleName,
|
||||
method: methodName,
|
||||
failCbId: -1,
|
||||
successCbId: -1,
|
||||
args: methodArgs,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageQueue;
|
||||
|
||||
@@ -82,13 +82,7 @@ function genMethod(moduleID: number, methodID: number, type: MethodType) {
|
||||
};
|
||||
} else if (type === 'sync') {
|
||||
fn = function(...args: Array<any>) {
|
||||
if (__DEV__) {
|
||||
invariant(global.nativeCallSyncHook, 'Calling synchronous methods on native ' +
|
||||
'modules is not supported in Chrome.\n\n Consider providing alternative ' +
|
||||
'methods to expose this method in debug mode, e.g. by exposing constants ' +
|
||||
'ahead-of-time.');
|
||||
}
|
||||
return global.nativeCallSyncHook(moduleID, methodID, args);
|
||||
return BatchedBridge.callSyncHook(moduleID, methodID, args);
|
||||
};
|
||||
} else {
|
||||
fn = function(...args: Array<any>) {
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule Blob
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const uuid = require('uuid');
|
||||
|
||||
const { BlobModule } = require('NativeModules');
|
||||
|
||||
import type { BlobProps } from 'BlobTypes';
|
||||
|
||||
/**
|
||||
* Opaque JS representation of some binary data in native.
|
||||
*
|
||||
* The API is modeled after the W3C Blob API, with one caveat
|
||||
* regarding explicit deallocation. Refer to the `close()`
|
||||
* method for further details.
|
||||
*
|
||||
* Example usage in a React component:
|
||||
*
|
||||
* class WebSocketImage extends React.Component {
|
||||
* state = {blob: null};
|
||||
* componentDidMount() {
|
||||
* let ws = this.ws = new WebSocket(...);
|
||||
* ws.binaryType = 'blob';
|
||||
* ws.onmessage = (event) => {
|
||||
* if (this.state.blob) {
|
||||
* this.state.blob.close();
|
||||
* }
|
||||
* this.setState({blob: event.data});
|
||||
* };
|
||||
* }
|
||||
* componentUnmount() {
|
||||
* if (this.state.blob) {
|
||||
* this.state.blob.close();
|
||||
* }
|
||||
* this.ws.close();
|
||||
* }
|
||||
* render() {
|
||||
* if (!this.state.blob) {
|
||||
* return <View />;
|
||||
* }
|
||||
* return <Image source={{uri: URL.createObjectURL(this.state.blob)}} />;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob
|
||||
*/
|
||||
class Blob {
|
||||
/**
|
||||
* Size of the data contained in the Blob object, in bytes.
|
||||
*/
|
||||
size: number;
|
||||
/*
|
||||
* String indicating the MIME type of the data contained in the Blob.
|
||||
* If the type is unknown, this string is empty.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/*
|
||||
* Unique id to identify the blob on native side (non-standard)
|
||||
*/
|
||||
blobId: string;
|
||||
/*
|
||||
* Offset to indicate part of blob, used when sliced (non-standard)
|
||||
*/
|
||||
offset: number;
|
||||
|
||||
/**
|
||||
* Construct blob instance from blob data from native.
|
||||
* Used internally by modules like XHR, WebSocket, etc.
|
||||
*/
|
||||
static create(props: BlobProps): Blob {
|
||||
return Object.assign(Object.create(Blob.prototype), props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for JS consumers.
|
||||
* Currently we only support creating Blobs from other Blobs.
|
||||
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
|
||||
*/
|
||||
constructor(parts: Array<Blob>, options: any) {
|
||||
const blobId = uuid();
|
||||
let size = 0;
|
||||
parts.forEach((part) => {
|
||||
invariant(part instanceof Blob, 'Can currently only create a Blob from other Blobs');
|
||||
size += part.size;
|
||||
});
|
||||
BlobModule.createFromParts(parts, blobId);
|
||||
return Blob.create({
|
||||
blobId,
|
||||
offset: 0,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is used to create a new Blob object containing
|
||||
* the data in the specified range of bytes of the source Blob.
|
||||
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice
|
||||
*/
|
||||
slice(start?: number, end?: number): Blob {
|
||||
let offset = this.offset;
|
||||
let size = this.size;
|
||||
if (typeof start === 'number') {
|
||||
if (start > size) {
|
||||
start = size;
|
||||
}
|
||||
offset += start;
|
||||
size -= start;
|
||||
|
||||
if (typeof end === 'number') {
|
||||
if (end < 0) {
|
||||
end = this.size + end;
|
||||
}
|
||||
size = end - start;
|
||||
}
|
||||
}
|
||||
return Blob.create({
|
||||
blobId: this.blobId,
|
||||
offset,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is in the standard, but not actually implemented by
|
||||
* any browsers at this point. It's important for how Blobs work in
|
||||
* React Native, however, since we cannot de-allocate resources automatically,
|
||||
* so consumers need to explicitly de-allocate them.
|
||||
*
|
||||
* Note that the semantics around Blobs created via `blob.slice()`
|
||||
* and `new Blob([blob])` are different. `blob.slice()` creates a
|
||||
* new *view* onto the same binary data, so calling `close()` on any
|
||||
* of those views is enough to deallocate the data, whereas
|
||||
* `new Blob([blob, ...])` actually copies the data in memory.
|
||||
*/
|
||||
close() {
|
||||
BlobModule.release(this.blobId);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Blob;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule BlobTypes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type BlobProps = {
|
||||
blobId: string,
|
||||
offset: number,
|
||||
size: number,
|
||||
type?: string,
|
||||
};
|
||||
|
||||
export type FileProps = BlobProps & {
|
||||
name: string,
|
||||
lastModified: number,
|
||||
};
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
|
||||
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
|
||||
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
|
||||
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
|
||||
AD9A43C31DFC7126008DC588 /* RCTBlobManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */; };
|
||||
ADD01A711E09404A00F6D226 /* RCTBlobManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
358F4ED51D1E81A9004DF814 /* Copy Headers */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = include/RCTBlob;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Headers";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
AD0871121E215B16007D136D /* Copy Headers */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = include/RCTBlob;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Headers";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBlobManager.h; sourceTree = "<group>"; };
|
||||
AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBlobManager.m; sourceTree = "<group>"; };
|
||||
ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTBlob-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
358F4ECE1D1E81A9004DF814 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */,
|
||||
AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */,
|
||||
358F4ED81D1E81A9004DF814 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
358F4ED81D1E81A9004DF814 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */,
|
||||
ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
AD0871151E215EB7007D136D /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
AD0871171E215ECC007D136D /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
358F4ED61D1E81A9004DF814 /* RCTBlob */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 358F4EE01D1E81A9004DF814 /* Build configuration list for PBXNativeTarget "RCTBlob" */;
|
||||
buildPhases = (
|
||||
AD0871151E215EB7007D136D /* Headers */,
|
||||
358F4ED51D1E81A9004DF814 /* Copy Headers */,
|
||||
358F4ED31D1E81A9004DF814 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RCTBlob;
|
||||
productName = SLKBlobs;
|
||||
productReference = 358F4ED71D1E81A9004DF814 /* libRCTBlob.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
ADD01A671E09402E00F6D226 /* RCTBlob-tvOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = ADD01A6E1E09402E00F6D226 /* Build configuration list for PBXNativeTarget "RCTBlob-tvOS" */;
|
||||
buildPhases = (
|
||||
AD0871171E215ECC007D136D /* Headers */,
|
||||
AD0871121E215B16007D136D /* Copy Headers */,
|
||||
ADD01A641E09402E00F6D226 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "RCTBlob-tvOS";
|
||||
productName = "RCTBlob-tvOS";
|
||||
productReference = ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
358F4ECF1D1E81A9004DF814 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0730;
|
||||
ORGANIZATIONNAME = "Silk Labs";
|
||||
TargetAttributes = {
|
||||
358F4ED61D1E81A9004DF814 = {
|
||||
CreatedOnToolsVersion = 7.3;
|
||||
};
|
||||
ADD01A671E09402E00F6D226 = {
|
||||
CreatedOnToolsVersion = 8.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 358F4ED21D1E81A9004DF814 /* Build configuration list for PBXProject "RCTBlob" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 358F4ECE1D1E81A9004DF814;
|
||||
productRefGroup = 358F4ED81D1E81A9004DF814 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
358F4ED61D1E81A9004DF814 /* RCTBlob */,
|
||||
ADD01A671E09402E00F6D226 /* RCTBlob-tvOS */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
358F4ED31D1E81A9004DF814 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AD9A43C31DFC7126008DC588 /* RCTBlobManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
ADD01A641E09402E00F6D226 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
ADD01A711E09404A00F6D226 /* RCTBlobManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
358F4EDE1D1E81A9004DF814 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
358F4EDF1D1E81A9004DF814 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
358F4EE11D1E81A9004DF814 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
358F4EE21D1E81A9004DF814 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
ADD01A6F1E09402E00F6D226 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
TVOS_DEPLOYMENT_TARGET = 10.1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
ADD01A701E09402E00F6D226 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
TVOS_DEPLOYMENT_TARGET = 10.1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
358F4ED21D1E81A9004DF814 /* Build configuration list for PBXProject "RCTBlob" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
358F4EDE1D1E81A9004DF814 /* Debug */,
|
||||
358F4EDF1D1E81A9004DF814 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
358F4EE01D1E81A9004DF814 /* Build configuration list for PBXNativeTarget "RCTBlob" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
358F4EE11D1E81A9004DF814 /* Debug */,
|
||||
358F4EE21D1E81A9004DF814 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
ADD01A6E1E09402E00F6D226 /* Build configuration list for PBXNativeTarget "RCTBlob-tvOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
ADD01A6F1E09402E00F6D226 /* Debug */,
|
||||
ADD01A701E09402E00F6D226 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 358F4ECF1D1E81A9004DF814 /* Project object */;
|
||||
}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTURLRequestHandler.h>
|
||||
|
||||
@interface RCTBlobManager : NSObject <RCTBridgeModule, RCTURLRequestHandler>
|
||||
|
||||
@end
|
||||
Executable
+213
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTBlobManager.h"
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTWebSocketModule.h>
|
||||
|
||||
static NSString *const kBlobUriScheme = @"blob";
|
||||
|
||||
@interface _RCTBlobContentHandler : NSObject <RCTWebSocketContentHandler>
|
||||
|
||||
- (instancetype)initWithBlobManager:(RCTBlobManager *)blobManager;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation RCTBlobManager
|
||||
{
|
||||
NSMutableDictionary<NSString *, NSData *> *_blobs;
|
||||
_RCTBlobContentHandler *_contentHandler;
|
||||
NSOperationQueue *_queue;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE(BlobModule)
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (NSDictionary<NSString *, id> *)constantsToExport
|
||||
{
|
||||
return @{
|
||||
@"BLOB_URI_SCHEME": kBlobUriScheme,
|
||||
@"BLOB_URI_HOST": [NSNull null],
|
||||
};
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return [[_bridge webSocketModule] methodQueue];
|
||||
}
|
||||
|
||||
- (NSString *)store:(NSData *)data
|
||||
{
|
||||
NSString *blobId = [NSUUID UUID].UUIDString;
|
||||
[self store:data withId:blobId];
|
||||
return blobId;
|
||||
}
|
||||
|
||||
- (void)store:(NSData *)data withId:(NSString *)blobId
|
||||
{
|
||||
if (!_blobs) {
|
||||
_blobs = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
_blobs[blobId] = data;
|
||||
}
|
||||
|
||||
- (NSData *)resolve:(NSDictionary<NSString *, id> *)blob
|
||||
{
|
||||
NSString *blobId = [RCTConvert NSString:blob[@"blobId"]];
|
||||
NSNumber *offset = [RCTConvert NSNumber:blob[@"offset"]];
|
||||
NSNumber *size = [RCTConvert NSNumber:blob[@"size"]];
|
||||
|
||||
return [self resolve:blobId
|
||||
offset:offset ? [offset integerValue] : 0
|
||||
size:size ? [size integerValue] : -1];
|
||||
}
|
||||
|
||||
- (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size
|
||||
{
|
||||
NSData *data = _blobs[blobId];
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
if (offset != 0 || (size != -1 && size != data.length)) {
|
||||
data = [data subdataWithRange:NSMakeRange(offset, size)];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(enableBlobSupport:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
if (!_contentHandler) {
|
||||
_contentHandler = [[_RCTBlobContentHandler alloc] initWithBlobManager:self];
|
||||
}
|
||||
[[_bridge webSocketModule] setContentHandler:_contentHandler forSocketID:socketID];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(disableBlobSupport:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
[[_bridge webSocketModule] setContentHandler:nil forSocketID:socketID];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(sendBlob:(NSDictionary *)blob socketID:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
[[_bridge webSocketModule] sendData:[self resolve:blob] forSocketID:socketID];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:(NSString *)blobId)
|
||||
{
|
||||
NSMutableData *data = [NSMutableData new];
|
||||
for (NSDictionary<NSString *, id> *part in parts) {
|
||||
NSData *partData = [self resolve:part];
|
||||
[data appendData:partData];
|
||||
}
|
||||
[self store:data withId:blobId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(release:(NSString *)blobId)
|
||||
{
|
||||
[_blobs removeObjectForKey:blobId];
|
||||
}
|
||||
|
||||
#pragma mark - RCTURLRequestHandler methods
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [request.URL.scheme caseInsensitiveCompare:kBlobUriScheme] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
// Lazy setup
|
||||
if (!_queue) {
|
||||
_queue = [NSOperationQueue new];
|
||||
_queue.maxConcurrentOperationCount = 2;
|
||||
}
|
||||
|
||||
__weak __block NSBlockOperation *weakOp;
|
||||
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:nil
|
||||
expectedContentLength:-1
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:weakOp didReceiveResponse:response];
|
||||
|
||||
NSURLComponents *components = [[NSURLComponents alloc] initWithURL:request.URL resolvingAgainstBaseURL:NO];
|
||||
|
||||
NSString *blobId = components.path;
|
||||
NSInteger offset = 0;
|
||||
NSInteger size = -1;
|
||||
|
||||
if (components.queryItems) {
|
||||
for (NSURLQueryItem *queryItem in components.queryItems) {
|
||||
if ([queryItem.name isEqualToString:@"offset"]) {
|
||||
offset = [queryItem.value integerValue];
|
||||
}
|
||||
if ([queryItem.name isEqualToString:@"size"]) {
|
||||
size = [queryItem.value integerValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSData *data;
|
||||
if (blobId) {
|
||||
data = [self resolve:blobId offset:offset size:size];
|
||||
}
|
||||
NSError *error;
|
||||
if (data) {
|
||||
[delegate URLRequest:weakOp didReceiveData:data];
|
||||
} else {
|
||||
error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
|
||||
}
|
||||
[delegate URLRequest:weakOp didCompleteWithError:error];
|
||||
}];
|
||||
|
||||
weakOp = op;
|
||||
[_queue addOperation:op];
|
||||
return op;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(NSOperation *)op
|
||||
{
|
||||
[op cancel];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _RCTBlobContentHandler {
|
||||
__weak RCTBlobManager *_blobManager;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBlobManager:(RCTBlobManager *)blobManager
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_blobManager = blobManager;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)processMessage:(id)message forSocketID:(NSNumber *)socketID withType:(NSString *__autoreleasing _Nonnull *)type
|
||||
{
|
||||
if (![message isKindOfClass:[NSData class]]) {
|
||||
*type = @"text";
|
||||
return message;
|
||||
}
|
||||
|
||||
*type = @"blob";
|
||||
return @{
|
||||
@"blobId": [_blobManager store:message],
|
||||
@"offset": @0,
|
||||
@"size": @(((NSData *)message).length),
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule URL
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Blob = require('Blob');
|
||||
|
||||
const { BlobModule } = require('NativeModules');
|
||||
|
||||
let BLOB_URL_PREFIX = null;
|
||||
|
||||
if (typeof BlobModule.BLOB_URI_SCHEME === 'string') {
|
||||
BLOB_URL_PREFIX = BlobModule.BLOB_URI_SCHEME + ':';
|
||||
if (typeof BlobModule.BLOB_URI_HOST === 'string') {
|
||||
BLOB_URL_PREFIX += `//${BlobModule.BLOB_URI_HOST}/`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To allow Blobs be accessed via `content://` URIs,
|
||||
* you need to register `BlobProvider` as a ContentProvider in your app's `AndroidManifest.xml`:
|
||||
*
|
||||
* ```xml
|
||||
* <manifest>
|
||||
* <application>
|
||||
* <provider
|
||||
* android:name="com.facebook.react.modules.blob.BlobProvider"
|
||||
* android:authorities="@string/blob_provider_authority"
|
||||
* android:exported="false"
|
||||
* />
|
||||
* </application>
|
||||
* </manifest>
|
||||
* ```
|
||||
* And then define the `blob_provider_authority` string in `res/values/strings.xml`.
|
||||
* Use a dotted name that's entirely unique to your app:
|
||||
*
|
||||
* ```xml
|
||||
* <resources>
|
||||
* <string name="blob_provider_authority">your.app.package.blobs</string>
|
||||
* </resources>
|
||||
* ```
|
||||
*/
|
||||
class URL {
|
||||
constructor() {
|
||||
throw new Error('Creating BlobURL objects is not supported yet.');
|
||||
}
|
||||
|
||||
static createObjectURL(blob: Blob) {
|
||||
if (BLOB_URL_PREFIX === null) {
|
||||
throw new Error('Cannot create URL for blob!');
|
||||
}
|
||||
return `${BLOB_URL_PREFIX}${blob.blobId}?offset=${blob.offset}&size=${blob.size}`;
|
||||
}
|
||||
|
||||
static revokeObjectURL(url: string) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = URL;
|
||||
@@ -88,6 +88,9 @@ const getPhotosReturnChecker = createStrictShapeTypeChecker({
|
||||
height: PropTypes.number.isRequired,
|
||||
width: PropTypes.number.isRequired,
|
||||
isStored: PropTypes.bool,
|
||||
// TODO (nivethavadivelu) Need to add changes to Android before
|
||||
// setting it as required
|
||||
playableDuration: PropTypes.number,
|
||||
}).isRequired,
|
||||
timestamp: PropTypes.number.isRequired,
|
||||
location: createStrictShapeTypeChecker({
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#import "RCTAssetsLibraryRequestHandler.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
@@ -41,13 +41,13 @@ RCT_EXPORT_MODULE()
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
|
||||
void (^cancellationBlock)(void) = ^{
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
atomic_store(&cancelled, YES);
|
||||
};
|
||||
|
||||
[[self assetsLibrary] assetForURL:request.URL resultBlock:^(ALAsset *asset) {
|
||||
if (cancelled) {
|
||||
if (atomic_load(&cancelled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ RCT_EXPORT_MODULE()
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
}
|
||||
} failureBlock:^(NSError *loadError) {
|
||||
if (cancelled) {
|
||||
if (atomic_load(&cancelled)) {
|
||||
return;
|
||||
}
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:loadError];
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
58B5115E1A9E6B3D00147676 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -80,8 +80,8 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
NSString *const RCTErrorUnableToLoad = @"E_UNABLE_TO_LOAD";
|
||||
NSString *const RCTErrorUnableToSave = @"E_UNABLE_TO_SAVE";
|
||||
static NSString *const kErrorUnableToLoad = @"E_UNABLE_TO_LOAD";
|
||||
static NSString *const kErrorUnableToSave = @"E_UNABLE_TO_SAVE";
|
||||
|
||||
RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
type:(NSString *)type
|
||||
@@ -93,7 +93,7 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self->_bridge.assetsLibrary writeVideoAtPathToSavedPhotosAlbum:request.URL completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
reject(RCTErrorUnableToSave, nil, saveError);
|
||||
reject(kErrorUnableToSave, nil, saveError);
|
||||
} else {
|
||||
resolve(assetURL.absoluteString);
|
||||
}
|
||||
@@ -103,7 +103,7 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
[_bridge.imageLoader loadImageWithURLRequest:request
|
||||
callback:^(NSError *loadError, UIImage *loadedImage) {
|
||||
if (loadError) {
|
||||
reject(RCTErrorUnableToLoad, nil, loadError);
|
||||
reject(kErrorUnableToLoad, nil, loadError);
|
||||
return;
|
||||
}
|
||||
// It's unclear if writeImageToSavedPhotosAlbum is thread-safe
|
||||
@@ -111,7 +111,7 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
[self->_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
RCTLogWarn(@"Error saving cropped image: %@", saveError);
|
||||
reject(RCTErrorUnableToSave, nil, saveError);
|
||||
reject(kErrorUnableToSave, nil, saveError);
|
||||
} else {
|
||||
resolve(assetURL.absoluteString);
|
||||
}
|
||||
@@ -187,6 +187,11 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
||||
CLLocation *loc = [result valueForProperty:ALAssetPropertyLocation];
|
||||
NSDate *date = [result valueForProperty:ALAssetPropertyDate];
|
||||
NSString *filename = [result defaultRepresentation].filename;
|
||||
int64_t duration = 0;
|
||||
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]) {
|
||||
duration = [[result valueForProperty:ALAssetPropertyDuration] intValue];
|
||||
}
|
||||
|
||||
[assets addObject:@{
|
||||
@"node": @{
|
||||
@"type": [result valueForProperty:ALAssetPropertyType],
|
||||
@@ -197,6 +202,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
||||
@"height": @(dimensions.height),
|
||||
@"width": @(dimensions.width),
|
||||
@"isStored": @YES,
|
||||
@"playableDuration": @(duration),
|
||||
},
|
||||
@"timestamp": @(date.timeIntervalSince1970),
|
||||
@"location": loc ? @{
|
||||
@@ -224,7 +230,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
||||
if (error.code != ALAssetsLibraryAccessUserDeniedError) {
|
||||
RCTLogError(@"Failure while iterating through asset groups %@", error);
|
||||
}
|
||||
reject(RCTErrorUnableToLoad, nil, error);
|
||||
reject(kErrorUnableToLoad, nil, error);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,22 @@ var DrawerLayoutAndroid = createReactClass({
|
||||
null
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Closing and opening example
|
||||
* Note: To access the drawer you have to give it a ref. Refs do not work on stateless components
|
||||
* render () {
|
||||
* this.openDrawer = () => {
|
||||
* this.refs.DRAWER.openDrawer()
|
||||
* }
|
||||
* this.closeDrawer = () => {
|
||||
* this.refs.DRAWER.closeDrawer()
|
||||
* }
|
||||
* return (
|
||||
* <DrawerLayoutAndroid ref={'DRAWER'}>
|
||||
* </DrawerLayoutAndroid>
|
||||
* )
|
||||
* }
|
||||
*/
|
||||
_getDrawerLayoutHandle: function() {
|
||||
return ReactNative.findNodeHandle(this.refs[RK_DRAWER_REF]);
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ type KeyboardEventListener = (e: KeyboardEventData) => void;
|
||||
|
||||
// The following object exists for documentation purposes
|
||||
// Actual work happens in
|
||||
// https://github.com/facebook/react-native/blob/master/Libraries/EventEmitter/NativeEventEmitter.js
|
||||
// https://github.com/facebook/react-native/blob/master/Libraries/vendor/emitter/NativeEventEmitter.js
|
||||
|
||||
/**
|
||||
* `Keyboard` module to control keyboard events.
|
||||
@@ -142,4 +142,3 @@ Keyboard = KeyboardEventEmitter;
|
||||
Keyboard.dismiss = dismissKeyboard;
|
||||
|
||||
module.exports = Keyboard;
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
const ReactPropTypes = React.PropTypes;
|
||||
|
||||
import type { ViewProps } from 'ViewPropTypes';
|
||||
|
||||
@@ -69,7 +69,7 @@ class MaskedViewIOS extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
...ViewPropTypes,
|
||||
maskElement: ReactPropTypes.element.isRequired,
|
||||
maskElement: PropTypes.element.isRequired,
|
||||
};
|
||||
|
||||
_hasWarnedInvalidRenderMask = false;
|
||||
|
||||
@@ -130,7 +130,7 @@ type Event = Object;
|
||||
* [`UINavigationController`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationController_Class/),
|
||||
* enabling you to implement a navigation stack. It works exactly the same as it
|
||||
* would on a native app using `UINavigationController`, providing the same
|
||||
* animations and behavior from UIKIt.
|
||||
* animations and behavior from UIKit.
|
||||
*
|
||||
* As the name implies, it is only available on iOS. Take a look at
|
||||
* [`React Navigation`](https://reactnavigation.org/) for a cross-platform
|
||||
|
||||
@@ -201,16 +201,21 @@ const ScrollView = createReactClass({
|
||||
/**
|
||||
* Determines whether the keyboard gets dismissed in response to a drag.
|
||||
*
|
||||
* *Cross platform*
|
||||
*
|
||||
* - `'none'` (the default), drags do not dismiss the keyboard.
|
||||
* - `'on-drag'`, the keyboard is dismissed when a drag begins.
|
||||
*
|
||||
* *iOS Only*
|
||||
*
|
||||
* - `'interactive'`, the keyboard is dismissed interactively with the drag and moves in
|
||||
* synchrony with the touch; dragging upwards cancels the dismissal.
|
||||
* On android this is not supported and it will have the same behavior as 'none'.
|
||||
*/
|
||||
keyboardDismissMode: PropTypes.oneOf([
|
||||
'none', // default
|
||||
'interactive',
|
||||
'on-drag',
|
||||
'on-drag', // Cross-platform
|
||||
'interactive', // iOS-only
|
||||
]),
|
||||
/**
|
||||
* Determines when the keyboard should stay visible after a tap.
|
||||
@@ -235,16 +240,19 @@ const ScrollView = createReactClass({
|
||||
* @platform ios
|
||||
*/
|
||||
minimumZoomScale: PropTypes.number,
|
||||
/**
|
||||
* Called when the momentum scroll starts (scroll which occurs as the ScrollView glides to a stop).
|
||||
*/
|
||||
onMomentumScrollBegin: PropTypes.func,
|
||||
/**
|
||||
* Called when the momentum scroll ends (scroll which occurs as the ScrollView glides to a stop).
|
||||
*/
|
||||
onMomentumScrollEnd: PropTypes.func,
|
||||
/**
|
||||
* Fires at most once per frame during scrolling. The frequency of the
|
||||
* events can be controlled using the `scrollEventThrottle` prop.
|
||||
*/
|
||||
onScroll: PropTypes.func,
|
||||
/**
|
||||
* Called when a scrolling animation ends.
|
||||
* @platform ios
|
||||
*/
|
||||
onScrollAnimationEnd: PropTypes.func,
|
||||
/**
|
||||
* Called when scrollable content view of the ScrollView changes.
|
||||
*
|
||||
@@ -352,7 +360,18 @@ const ScrollView = createReactClass({
|
||||
* @platform ios
|
||||
*/
|
||||
zoomScale: PropTypes.number,
|
||||
|
||||
/**
|
||||
* This property specifies how the safe area insets are used to modify the
|
||||
* content area of the scroll view. The default value of this property is
|
||||
* "never". Available on iOS 11 and later.
|
||||
* @platform ios
|
||||
*/
|
||||
contentInsetAdjustmentBehavior: PropTypes.oneOf([
|
||||
'automatic',
|
||||
'scrollableAxes',
|
||||
'never', // default
|
||||
'always',
|
||||
]),
|
||||
/**
|
||||
* A RefreshControl component, used to provide pull-to-refresh
|
||||
* functionality for the ScrollView. Only works for vertical ScrollViews
|
||||
|
||||
@@ -63,7 +63,6 @@ var TouchableOpacity = createReactClass({
|
||||
* active. Defaults to 0.2.
|
||||
*/
|
||||
activeOpacity: PropTypes.number,
|
||||
focusedOpacity: PropTypes.number,
|
||||
/**
|
||||
* Apple TV parallax effects
|
||||
*/
|
||||
@@ -73,7 +72,6 @@ var TouchableOpacity = createReactClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
activeOpacity: 0.2,
|
||||
focusedOpacity: 0.7,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -165,10 +163,6 @@ var TouchableOpacity = createReactClass({
|
||||
);
|
||||
},
|
||||
|
||||
_opacityFocused: function() {
|
||||
this.setOpacityTo(this.props.focusedOpacity);
|
||||
},
|
||||
|
||||
_getChildStyleOpacityWithDefault: function() {
|
||||
var childStyle = flattenStyle(this.props.style) || {};
|
||||
return childStyle.opacity == undefined ? 1 : childStyle.opacity;
|
||||
|
||||
@@ -59,8 +59,15 @@ const TouchableWithoutFeedback = createReactClass({
|
||||
* that steals the responder lock).
|
||||
*/
|
||||
onPress: PropTypes.func,
|
||||
/**
|
||||
* Called as soon as the touchable element is pressed and invoked even before onPress.
|
||||
* This can be useful when making network requests.
|
||||
*/
|
||||
onPressIn: PropTypes.func,
|
||||
onPressOut: PropTypes.func,
|
||||
/**
|
||||
* Called as soon as the touch is released even before onPress.
|
||||
*/
|
||||
onPressOut: PropTypes.func,
|
||||
/**
|
||||
* Invoked on mount and layout changes with
|
||||
*
|
||||
|
||||
+4
-5
@@ -5,10 +5,9 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule PlatformViewPropTypes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCTAdSupport : NSObject <RCTBridgeModule>
|
||||
|
||||
@end
|
||||
module.export = {};
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule PlatformViewPropTypes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const Platform = require('Platform');
|
||||
|
||||
var TVViewPropTypes = {};
|
||||
if (Platform.isTVOS) {
|
||||
TVViewPropTypes = require('TVViewPropTypes');
|
||||
}
|
||||
|
||||
module.exports = TVViewPropTypes;
|
||||
@@ -12,7 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
const EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
const Platform = require('Platform');
|
||||
const PlatformViewPropTypes = require('PlatformViewPropTypes');
|
||||
const PropTypes = require('prop-types');
|
||||
const StyleSheetPropType = require('StyleSheetPropType');
|
||||
const ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
@@ -22,11 +22,6 @@ const {
|
||||
AccessibilityTraits,
|
||||
} = require('ViewAccessibility');
|
||||
|
||||
var TVViewPropTypes = {};
|
||||
if (Platform.isTVOS) {
|
||||
TVViewPropTypes = require('TVViewPropTypes');
|
||||
}
|
||||
|
||||
import type {
|
||||
AccessibilityComponentType,
|
||||
AccessibilityTrait,
|
||||
@@ -85,7 +80,7 @@ export type ViewProps = {
|
||||
} & TVViewProps;
|
||||
|
||||
module.exports = {
|
||||
...TVViewPropTypes,
|
||||
...PlatformViewPropTypes,
|
||||
|
||||
/**
|
||||
* When `true`, indicates that the view is an accessibility element. By default,
|
||||
|
||||
@@ -40,25 +40,24 @@ const defineLazyObjectProperty = require('defineLazyObjectProperty');
|
||||
|
||||
/**
|
||||
* Sets an object's property. If a property with the same name exists, this will
|
||||
* replace it but maintain its descriptor configuration. By default, the property
|
||||
* will replaced with a lazy getter.
|
||||
* replace it but maintain its descriptor configuration. The property will be
|
||||
* replaced with a lazy getter.
|
||||
*
|
||||
* The original property value will be preserved as `original[PropertyName]` so
|
||||
* that, if necessary, it can be restored. For example, if you want to route
|
||||
* In DEV mode the original property value will be preserved as `original[PropertyName]`
|
||||
* so that, if necessary, it can be restored. For example, if you want to route
|
||||
* network requests through DevTools (to trace them):
|
||||
*
|
||||
* global.XMLHttpRequest = global.originalXMLHttpRequest;
|
||||
*
|
||||
* @see https://github.com/facebook/react-native/issues/934
|
||||
*/
|
||||
function defineProperty<T>(
|
||||
function defineLazyProperty<T>(
|
||||
object: Object,
|
||||
name: string,
|
||||
getValue: () => T,
|
||||
eager?: boolean
|
||||
): void {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(object, name);
|
||||
if (descriptor) {
|
||||
if (__DEV__ && descriptor) {
|
||||
const backupName = `original${name[0].toUpperCase()}${name.substr(1)}`;
|
||||
Object.defineProperty(object, backupName, {
|
||||
...descriptor,
|
||||
@@ -72,20 +71,15 @@ function defineProperty<T>(
|
||||
return;
|
||||
}
|
||||
|
||||
if (eager === true) {
|
||||
Object.defineProperty(object, name, {
|
||||
configurable: true,
|
||||
enumerable: enumerable !== false,
|
||||
writable: writable !== false,
|
||||
value: getValue(),
|
||||
});
|
||||
} else {
|
||||
defineLazyObjectProperty(object, name, {
|
||||
get: getValue,
|
||||
enumerable: enumerable !== false,
|
||||
writable: writable !== false,
|
||||
});
|
||||
}
|
||||
defineLazyObjectProperty(object, name, {
|
||||
get: getValue,
|
||||
enumerable: enumerable !== false,
|
||||
writable: writable !== false,
|
||||
});
|
||||
}
|
||||
|
||||
function polyfillGlobal<T>(name: string, getValue: () => T): void {
|
||||
defineLazyProperty(global, name, getValue);
|
||||
}
|
||||
|
||||
// Set up process
|
||||
@@ -128,18 +122,21 @@ if (!global.__fbDisableExceptionsManager) {
|
||||
}
|
||||
|
||||
// Set up collections
|
||||
// We can't make these lazy because `Map` checks for `global.Map` (which wouldc
|
||||
// not exist if it were lazily defined).
|
||||
defineProperty(global, 'Map', () => require('Map'), true);
|
||||
defineProperty(global, 'Set', () => require('Set'), true);
|
||||
const _shouldPolyfillCollection = require('_shouldPolyfillES6Collection');
|
||||
if (_shouldPolyfillCollection('Map')) {
|
||||
polyfillGlobal('Map', () => require('Map'));
|
||||
}
|
||||
if (_shouldPolyfillCollection('Set')) {
|
||||
polyfillGlobal('Set', () => require('Set'));
|
||||
}
|
||||
|
||||
// Set up Promise
|
||||
// The native Promise implementation throws the following error:
|
||||
// ERROR: Event loop not supported.
|
||||
defineProperty(global, 'Promise', () => require('Promise'));
|
||||
polyfillGlobal('Promise', () => require('Promise'));
|
||||
|
||||
// Set up regenerator.
|
||||
defineProperty(global, 'regeneratorRuntime', () => {
|
||||
polyfillGlobal('regeneratorRuntime', () => {
|
||||
// The require just sets up the global, so make sure when we first
|
||||
// invoke it the global does not exist
|
||||
delete global.regeneratorRuntime;
|
||||
@@ -149,7 +146,7 @@ defineProperty(global, 'regeneratorRuntime', () => {
|
||||
|
||||
// Set up timers
|
||||
const defineLazyTimer = name => {
|
||||
defineProperty(global, name, () => require('JSTimers')[name]);
|
||||
polyfillGlobal(name, () => require('JSTimers')[name]);
|
||||
};
|
||||
defineLazyTimer('setTimeout');
|
||||
defineLazyTimer('setInterval');
|
||||
@@ -165,14 +162,16 @@ defineLazyTimer('cancelIdleCallback');
|
||||
// Set up XHR
|
||||
// The native XMLHttpRequest in Chrome dev tools is CORS aware and won't
|
||||
// let you fetch anything from the internet
|
||||
defineProperty(global, 'XMLHttpRequest', () => require('XMLHttpRequest'));
|
||||
defineProperty(global, 'FormData', () => require('FormData'));
|
||||
polyfillGlobal('XMLHttpRequest', () => require('XMLHttpRequest'));
|
||||
polyfillGlobal('FormData', () => require('FormData'));
|
||||
|
||||
defineProperty(global, 'fetch', () => require('fetch').fetch);
|
||||
defineProperty(global, 'Headers', () => require('fetch').Headers);
|
||||
defineProperty(global, 'Request', () => require('fetch').Request);
|
||||
defineProperty(global, 'Response', () => require('fetch').Response);
|
||||
defineProperty(global, 'WebSocket', () => require('WebSocket'));
|
||||
polyfillGlobal('fetch', () => require('fetch').fetch);
|
||||
polyfillGlobal('Headers', () => require('fetch').Headers);
|
||||
polyfillGlobal('Request', () => require('fetch').Request);
|
||||
polyfillGlobal('Response', () => require('fetch').Response);
|
||||
polyfillGlobal('WebSocket', () => require('WebSocket'));
|
||||
polyfillGlobal('Blob', () => require('Blob'));
|
||||
polyfillGlobal('URL', () => require('URL'));
|
||||
|
||||
// Set up alert
|
||||
if (!global.alert) {
|
||||
@@ -190,8 +189,8 @@ if (navigator === undefined) {
|
||||
}
|
||||
|
||||
// see https://github.com/facebook/react-native/issues/10881
|
||||
defineProperty(navigator, 'product', () => 'ReactNative', true);
|
||||
defineProperty(navigator, 'geolocation', () => require('Geolocation'));
|
||||
defineLazyProperty(navigator, 'product', () => 'ReactNative');
|
||||
defineLazyProperty(navigator, 'geolocation', () => require('Geolocation'));
|
||||
|
||||
// Just to make sure the JS gets packaged up. Wait until the JS environment has
|
||||
// been initialized before requiring them.
|
||||
|
||||
@@ -16,13 +16,18 @@ const Platform = require('Platform');
|
||||
const Systrace = require('Systrace');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const performanceNow = require('fbjs/lib/performanceNow');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
const {Timing} = require('NativeModules');
|
||||
|
||||
import type {ExtendedError} from 'parseErrorStack';
|
||||
|
||||
let _performanceNow = null;
|
||||
function performanceNow() {
|
||||
if (!_performanceNow) {
|
||||
_performanceNow = require('fbjs/lib/performanceNow');
|
||||
}
|
||||
return _performanceNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* JS implementation of timer functions. Must be completely driven by an
|
||||
* external clock signal, all that's stored here is timerID, timer type, and
|
||||
@@ -96,7 +101,7 @@ function _allocateCallback(func: Function, type: JSTimerType): number {
|
||||
* recurring (setInterval).
|
||||
*/
|
||||
function _callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) {
|
||||
warning(
|
||||
require('fbjs/lib/warning')(
|
||||
timerID <= GUID,
|
||||
'Tried to call timer with ID %s but no such timer exists.',
|
||||
timerID,
|
||||
@@ -170,6 +175,34 @@ function _callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a single pass over the enqueued immediates. Returns whether
|
||||
* more immediates are queued up (can be used as a condition a while loop).
|
||||
*/
|
||||
function _callImmediatesPass() {
|
||||
if (__DEV__) {
|
||||
Systrace.beginEvent('callImmediatesPass()');
|
||||
}
|
||||
|
||||
// The main reason to extract a single pass is so that we can track
|
||||
// in the system trace
|
||||
if (immediates.length > 0) {
|
||||
const passImmediates = immediates.slice();
|
||||
immediates = [];
|
||||
|
||||
// Use for loop rather than forEach as per @vjeux's advice
|
||||
// https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051
|
||||
for (let i = 0; i < passImmediates.length; ++i) {
|
||||
_callTimer(passImmediates[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
return immediates.length > 0;
|
||||
}
|
||||
|
||||
function _clearIndex(i: number) {
|
||||
timerIDs[i] = null;
|
||||
callbacks[i] = null;
|
||||
@@ -422,41 +455,13 @@ const JSTimers = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a single pass over the enqueued immediates. Returns whether
|
||||
* more immediates are queued up (can be used as a condition a while loop).
|
||||
*/
|
||||
callImmediatesPass() {
|
||||
if (__DEV__) {
|
||||
Systrace.beginEvent('callImmediatesPass()');
|
||||
}
|
||||
|
||||
// The main reason to extract a single pass is so that we can track
|
||||
// in the system trace
|
||||
if (immediates.length > 0) {
|
||||
const passImmediates = immediates.slice();
|
||||
immediates = [];
|
||||
|
||||
// Use for loop rather than forEach as per @vjeux's advice
|
||||
// https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051
|
||||
for (let i = 0; i < passImmediates.length; ++i) {
|
||||
_callTimer(passImmediates[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
return immediates.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is called after we execute any command we receive from native but
|
||||
* before we hand control back to native.
|
||||
*/
|
||||
callImmediates() {
|
||||
errors = null;
|
||||
while (JSTimers.callImmediatesPass()) {}
|
||||
while (_callImmediatesPass()) {}
|
||||
if (errors) {
|
||||
errors.forEach(error =>
|
||||
JSTimers.setTimeout(() => {
|
||||
@@ -478,4 +483,13 @@ const JSTimers = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = JSTimers;
|
||||
if (!Timing) {
|
||||
console.warn("Timing native module is not available, can't set timers.");
|
||||
// $FlowFixMe: we can assume timers are generally available
|
||||
module.exports = ({
|
||||
callImmediates: JSTimers.callImmediates,
|
||||
setImmediate: JSTimers.setImmediate,
|
||||
}: typeof JSTimers);
|
||||
} else {
|
||||
module.exports = JSTimers;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,20 @@ const EventSubscriptionVendor = require('EventSubscriptionVendor');
|
||||
|
||||
import type EmitterSubscription from 'EmitterSubscription';
|
||||
|
||||
function checkNativeEventModule(eventType: ?string) {
|
||||
if (eventType) {
|
||||
if (eventType.lastIndexOf('statusBar', 0) === 0) {
|
||||
throw new Error('`' + eventType + '` event should be registered via the StatusBarIOS module');
|
||||
}
|
||||
if (eventType.lastIndexOf('keyboard', 0) === 0) {
|
||||
throw new Error('`' + eventType + '` event should be registered via the Keyboard module');
|
||||
}
|
||||
if (eventType === 'appStateDidChange' || eventType === 'memoryWarning') {
|
||||
throw new Error('`' + eventType + '` event should be registered via the AppState module');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated - subclass NativeEventEmitter to create granular event modules instead of
|
||||
* adding all event listeners directly to RCTDeviceEventEmitter.
|
||||
@@ -30,34 +44,19 @@ class RCTDeviceEventEmitter extends EventEmitter {
|
||||
this.sharedSubscriber = sharedSubscriber;
|
||||
}
|
||||
|
||||
_nativeEventModule(eventType: ?string) {
|
||||
if (eventType) {
|
||||
if (eventType.lastIndexOf('statusBar', 0) === 0) {
|
||||
console.warn('`%s` event should be registered via the StatusBarIOS module', eventType);
|
||||
return require('StatusBarIOS');
|
||||
}
|
||||
if (eventType.lastIndexOf('keyboard', 0) === 0) {
|
||||
console.warn('`%s` event should be registered via the Keyboard module', eventType);
|
||||
return require('Keyboard');
|
||||
}
|
||||
if (eventType === 'appStateDidChange' || eventType === 'memoryWarning') {
|
||||
console.warn('`%s` event should be registered via the AppState module', eventType);
|
||||
return require('AppState');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
addListener(eventType: string, listener: Function, context: ?Object): EmitterSubscription {
|
||||
const eventModule = this._nativeEventModule(eventType);
|
||||
return eventModule ? eventModule.addListener(eventType, listener, context)
|
||||
: super.addListener(eventType, listener, context);
|
||||
if (__DEV__) {
|
||||
checkNativeEventModule(eventType);
|
||||
}
|
||||
return super.addListener(eventType, listener, context);
|
||||
}
|
||||
|
||||
removeAllListeners(eventType: ?string) {
|
||||
const eventModule = this._nativeEventModule(eventType);
|
||||
(eventModule && eventType) ? eventModule.removeAllListeners(eventType)
|
||||
: super.removeAllListeners(eventType);
|
||||
if (__DEV__) {
|
||||
checkNativeEventModule(eventType);
|
||||
}
|
||||
super.removeAllListeners(eventType);
|
||||
}
|
||||
|
||||
removeSubscription(subscription: EmitterSubscription) {
|
||||
|
||||
@@ -72,7 +72,6 @@ const SwipeableRow = createReactClass({
|
||||
propTypes: {
|
||||
children: PropTypes.any,
|
||||
isOpen: PropTypes.bool,
|
||||
preventSwipeLeft: PropTypes.bool,
|
||||
preventSwipeRight: PropTypes.bool,
|
||||
maxSwipeDistance: PropTypes.number.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
@@ -110,7 +109,6 @@ const SwipeableRow = createReactClass({
|
||||
getDefaultProps(): Object {
|
||||
return {
|
||||
isOpen: false,
|
||||
preventSwipeLeft: false,
|
||||
preventSwipeRight: false,
|
||||
maxSwipeDistance: 0,
|
||||
onOpen: emptyFunction,
|
||||
@@ -339,12 +337,10 @@ const SwipeableRow = createReactClass({
|
||||
|
||||
// Ignore swipes due to user's finger moving slightly when tapping
|
||||
_isValidSwipe(gestureState: Object): boolean {
|
||||
if (this.props.preventSwipeLeft && gestureState.dx < 0) {
|
||||
return false;
|
||||
}
|
||||
if (this.props.preventSwipeRight && gestureState.dx > 0) {
|
||||
if (this.props.preventSwipeRight && this._previousLeft === CLOSED_LEFT_POSITION && gestureState.dx > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
|
||||
},
|
||||
|
||||
|
||||
@@ -27,10 +27,11 @@ var subscriptions = [];
|
||||
var updatesEnabled = false;
|
||||
|
||||
type GeoOptions = {
|
||||
timeout: number,
|
||||
maximumAge: number,
|
||||
enableHighAccuracy: bool,
|
||||
timeout?: number,
|
||||
maximumAge?: number,
|
||||
enableHighAccuracy?: bool,
|
||||
distanceFilter: number,
|
||||
useSignificantChanges?: bool,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +125,7 @@ var Geolocation = {
|
||||
|
||||
/*
|
||||
* Invokes the success callback whenever the location changes. Supported
|
||||
* options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool), distanceFilter(m)
|
||||
* options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool), distanceFilter(m), useSignificantChanges (bool)
|
||||
*/
|
||||
watchPosition: function(success: Function, error?: Function, options?: GeoOptions): number {
|
||||
if (!updatesEnabled) {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ typedef struct {
|
||||
double maximumAge;
|
||||
double accuracy;
|
||||
double distanceFilter;
|
||||
BOOL useSignificantChanges;
|
||||
} RCTLocationOptions;
|
||||
|
||||
@implementation RCTConvert (RCTLocationOptions)
|
||||
@@ -47,7 +48,8 @@ typedef struct {
|
||||
.timeout = [RCTConvert NSTimeInterval:options[@"timeout"]] ?: INFINITY,
|
||||
.maximumAge = [RCTConvert NSTimeInterval:options[@"maximumAge"]] ?: INFINITY,
|
||||
.accuracy = [RCTConvert BOOL:options[@"enableHighAccuracy"]] ? kCLLocationAccuracyBest : RCT_DEFAULT_LOCATION_ACCURACY,
|
||||
.distanceFilter = distanceFilter
|
||||
.distanceFilter = distanceFilter,
|
||||
.useSignificantChanges = [RCTConvert BOOL:options[@"useSignificantChanges"]] ?: NO,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,6 +110,7 @@ static NSDictionary<NSString *, id> *RCTPositionError(RCTPositionErrorCode code,
|
||||
NSDictionary<NSString *, id> *_lastLocationEvent;
|
||||
NSMutableArray<RCTLocationRequest *> *_pendingRequests;
|
||||
BOOL _observingLocation;
|
||||
BOOL _usingSignificantChanges;
|
||||
RCTLocationOptions _observerOptions;
|
||||
}
|
||||
|
||||
@@ -117,7 +120,10 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_locationManager stopUpdatingLocation];
|
||||
_usingSignificantChanges ?
|
||||
[_locationManager stopMonitoringSignificantLocationChanges] :
|
||||
[_locationManager stopUpdatingLocation];
|
||||
|
||||
_locationManager.delegate = nil;
|
||||
}
|
||||
|
||||
@@ -133,14 +139,18 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
#pragma mark - Private API
|
||||
|
||||
- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy distanceFilter:(CLLocationDistance)distanceFilter
|
||||
- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy distanceFilter:(CLLocationDistance)distanceFilter useSignificantChanges:(BOOL)useSignificantChanges
|
||||
{
|
||||
[self requestAuthorization];
|
||||
|
||||
_locationManager.distanceFilter = distanceFilter;
|
||||
_locationManager.desiredAccuracy = desiredAccuracy;
|
||||
_usingSignificantChanges = useSignificantChanges;
|
||||
|
||||
// Start observing location
|
||||
[_locationManager startUpdatingLocation];
|
||||
_usingSignificantChanges ?
|
||||
[_locationManager startMonitoringSignificantLocationChanges] :
|
||||
[_locationManager startUpdatingLocation];
|
||||
}
|
||||
|
||||
#pragma mark - Timeout handler
|
||||
@@ -154,7 +164,9 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
// Stop updating if no pending requests
|
||||
if (_pendingRequests.count == 0 && !_observingLocation) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
_usingSignificantChanges ?
|
||||
[_locationManager stopMonitoringSignificantLocationChanges] :
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +207,9 @@ RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options)
|
||||
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
|
||||
}
|
||||
|
||||
[self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy distanceFilter:_observerOptions.distanceFilter];
|
||||
[self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy
|
||||
distanceFilter:_observerOptions.distanceFilter
|
||||
useSignificantChanges:_observerOptions.useSignificantChanges];
|
||||
_observingLocation = YES;
|
||||
}
|
||||
|
||||
@@ -206,7 +220,9 @@ RCT_EXPORT_METHOD(stopObserving)
|
||||
|
||||
// Stop updating if no pending requests
|
||||
if (_pendingRequests.count == 0) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
_usingSignificantChanges ?
|
||||
[_locationManager stopMonitoringSignificantLocationChanges] :
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +285,9 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
|
||||
if (_locationManager) {
|
||||
accuracy = MIN(_locationManager.desiredAccuracy, accuracy);
|
||||
}
|
||||
[self beginLocationUpdatesWithDesiredAccuracy:accuracy distanceFilter:options.distanceFilter];
|
||||
[self beginLocationUpdatesWithDesiredAccuracy:accuracy
|
||||
distanceFilter:options.distanceFilter
|
||||
useSignificantChanges:options.useSignificantChanges];
|
||||
}
|
||||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
@@ -306,7 +324,9 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
|
||||
|
||||
// Stop updating if not observing
|
||||
if (!_observingLocation) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
_usingSignificantChanges ?
|
||||
[_locationManager stopMonitoringSignificantLocationChanges] :
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
// Reset location accuracy if desiredAccuracy is changed.
|
||||
@@ -356,8 +376,9 @@ static void checkLocationConfig()
|
||||
{
|
||||
#if RCT_DEV
|
||||
if (!([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] ||
|
||||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"])) {
|
||||
RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription key must be present in Info.plist to use geolocation.");
|
||||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] ||
|
||||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"])) {
|
||||
RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription key must be present in Info.plist to use geolocation.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
58B5115E1A9E6B3D00147676 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <stdatomic.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <ImageIO/ImageIO.h>
|
||||
@@ -324,7 +324,8 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ?
|
||||
[loadHandler requiresScheduling] : YES;
|
||||
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
|
||||
// TODO: Protect this variable shared between threads.
|
||||
__block dispatch_block_t cancelLoad = nil;
|
||||
void (^completionHandler)(NSError *, id, NSString *) = ^(NSError *error, id imageOrData, NSString *fetchDate) {
|
||||
cancelLoad = nil;
|
||||
@@ -338,11 +339,11 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
// Most loaders do not return on the main thread, so caller is probably not
|
||||
// expecting it, and may do expensive post-processing in the callback
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (!cancelled) {
|
||||
if (!atomic_load(&cancelled)) {
|
||||
completionBlock(error, imageOrData, cacheResult, fetchDate);
|
||||
}
|
||||
});
|
||||
} else if (!cancelled) {
|
||||
} else if (!atomic_load(&cancelled)) {
|
||||
completionBlock(error, imageOrData, cacheResult, fetchDate);
|
||||
}
|
||||
};
|
||||
@@ -369,7 +370,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
__weak RCTImageLoader *weakSelf = self;
|
||||
dispatch_async(_URLRequestQueue, ^{
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
if (cancelled || !strongSelf) {
|
||||
if (atomic_load(&cancelled) || !strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -392,12 +393,15 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
});
|
||||
|
||||
return ^{
|
||||
BOOL alreadyCancelled = atomic_fetch_or(&cancelled, 1);
|
||||
if (alreadyCancelled) {
|
||||
return;
|
||||
}
|
||||
dispatch_block_t cancelLoadLocal = cancelLoad;
|
||||
cancelLoad = nil;
|
||||
if (cancelLoadLocal && !cancelled) {
|
||||
if (cancelLoadLocal) {
|
||||
cancelLoadLocal();
|
||||
}
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -524,20 +528,25 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
{
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
|
||||
// TODO: Protect this variable shared between threads.
|
||||
__block dispatch_block_t cancelLoad = nil;
|
||||
dispatch_block_t cancellationBlock = ^{
|
||||
BOOL alreadyCancelled = atomic_fetch_or(&cancelled, 1);
|
||||
if (alreadyCancelled) {
|
||||
return;
|
||||
}
|
||||
dispatch_block_t cancelLoadLocal = cancelLoad;
|
||||
if (cancelLoadLocal && !cancelled) {
|
||||
cancelLoad = nil;
|
||||
if (cancelLoadLocal) {
|
||||
cancelLoadLocal();
|
||||
}
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
|
||||
__weak RCTImageLoader *weakSelf = self;
|
||||
void (^completionHandler)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
if (cancelled || !strongSelf) {
|
||||
if (atomic_load(&cancelled) || !strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -606,17 +615,17 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
return ^{};
|
||||
}
|
||||
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
|
||||
void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) {
|
||||
if (RCTIsMainQueue()) {
|
||||
// Most loaders do not return on the main thread, so caller is probably not
|
||||
// expecting it, and may do expensive post-processing in the callback
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (!cancelled) {
|
||||
if (!atomic_load(&cancelled)) {
|
||||
completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
|
||||
}
|
||||
});
|
||||
} else if (!cancelled) {
|
||||
} else if (!atomic_load(&cancelled)) {
|
||||
completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
|
||||
}
|
||||
};
|
||||
@@ -638,7 +647,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
|
||||
// Do actual decompression on a concurrent background queue
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (!cancelled) {
|
||||
if (!atomic_load(&cancelled)) {
|
||||
|
||||
// Decompress the image data (this may be CPU and memory intensive)
|
||||
UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode);
|
||||
@@ -695,7 +704,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
});
|
||||
|
||||
return ^{
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
atomic_store(&cancelled, YES);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#import "RCTImageStoreManager.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <MobileCoreServices/UTType.h>
|
||||
@@ -76,7 +76,7 @@ RCT_EXPORT_MODULE()
|
||||
{
|
||||
RCTAssertParam(block);
|
||||
dispatch_async(_methodQueue, ^{
|
||||
NSString *imageTag = [self _storeImageData:RCTGetImageData(image.CGImage, 0.75)];
|
||||
NSString *imageTag = [self _storeImageData:RCTGetImageData(image, 0.75)];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
block(imageTag);
|
||||
});
|
||||
@@ -137,14 +137,14 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
|
||||
void (^cancellationBlock)(void) = ^{
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
atomic_store(&cancelled, YES);
|
||||
};
|
||||
|
||||
// Dispatch async to give caller time to cancel the request
|
||||
dispatch_async(_methodQueue, ^{
|
||||
if (cancelled) {
|
||||
if (atomic_load(&cancelled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
|
||||
RCTLogWarn(@"RCTImageStoreManager.storeImage() is deprecated and has poor performance. Use an alternative method instead.");
|
||||
__block NSString *imageTag;
|
||||
dispatch_sync(_methodQueue, ^{
|
||||
imageTag = [self _storeImageData:RCTGetImageData(image.CGImage, 0.75)];
|
||||
imageTag = [self _storeImageData:RCTGetImageData(image, 0.75)];
|
||||
});
|
||||
return imageTag;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ RCT_EXTERN NSDictionary<NSString *, id> *__nullable RCTGetImageMetadata(NSData *
|
||||
* conversion, with 1.0 being maximum quality. It has no effect for images
|
||||
* using PNG compression.
|
||||
*/
|
||||
RCT_EXTERN NSData *__nullable RCTGetImageData(CGImageRef image, float quality);
|
||||
RCT_EXTERN NSData *__nullable RCTGetImageData(UIImage *image, float quality);
|
||||
|
||||
/**
|
||||
* This function transforms an image. `destSize` is the size of the final image,
|
||||
|
||||
@@ -35,6 +35,22 @@ static CGSize RCTCeilSize(CGSize size, CGFloat scale)
|
||||
};
|
||||
}
|
||||
|
||||
static CGImagePropertyOrientation CGImagePropertyOrientationFromUIImageOrientation(UIImageOrientation imageOrientation)
|
||||
{
|
||||
// see https://stackoverflow.com/a/6699649/496389
|
||||
switch (imageOrientation) {
|
||||
case UIImageOrientationUp: return kCGImagePropertyOrientationUp;
|
||||
case UIImageOrientationDown: return kCGImagePropertyOrientationDown;
|
||||
case UIImageOrientationLeft: return kCGImagePropertyOrientationLeft;
|
||||
case UIImageOrientationRight: return kCGImagePropertyOrientationRight;
|
||||
case UIImageOrientationUpMirrored: return kCGImagePropertyOrientationUpMirrored;
|
||||
case UIImageOrientationDownMirrored: return kCGImagePropertyOrientationDownMirrored;
|
||||
case UIImageOrientationLeftMirrored: return kCGImagePropertyOrientationLeftMirrored;
|
||||
case UIImageOrientationRightMirrored: return kCGImagePropertyOrientationRightMirrored;
|
||||
default: return kCGImagePropertyOrientationUp;
|
||||
}
|
||||
}
|
||||
|
||||
CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
|
||||
CGFloat destScale, RCTResizeMode resizeMode)
|
||||
{
|
||||
@@ -314,20 +330,23 @@ NSDictionary<NSString *, id> *__nullable RCTGetImageMetadata(NSData *data)
|
||||
return (__bridge_transfer id)imageProperties;
|
||||
}
|
||||
|
||||
NSData *__nullable RCTGetImageData(CGImageRef image, float quality)
|
||||
NSData *__nullable RCTGetImageData(UIImage *image, float quality)
|
||||
{
|
||||
NSDictionary *properties;
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] initWithDictionary:@{
|
||||
(id)kCGImagePropertyOrientation : @(CGImagePropertyOrientationFromUIImageOrientation(image.imageOrientation))
|
||||
}];
|
||||
CGImageDestinationRef destination;
|
||||
CFMutableDataRef imageData = CFDataCreateMutable(NULL, 0);
|
||||
if (RCTImageHasAlpha(image)) {
|
||||
CGImageRef cgImage = image.CGImage;
|
||||
if (RCTImageHasAlpha(cgImage)) {
|
||||
// get png data
|
||||
destination = CGImageDestinationCreateWithData(imageData, kUTTypePNG, 1, NULL);
|
||||
} else {
|
||||
// get jpeg data
|
||||
destination = CGImageDestinationCreateWithData(imageData, kUTTypeJPEG, 1, NULL);
|
||||
properties = @{(NSString *)kCGImageDestinationLossyCompressionQuality: @(quality)};
|
||||
[properties setValue:@(quality) forKey:(id)kCGImageDestinationLossyCompressionQuality];
|
||||
}
|
||||
CGImageDestinationAddImage(destination, image, (__bridge CFDictionaryRef)properties);
|
||||
CGImageDestinationAddImage(destination, cgImage, (__bridge CFDictionaryRef)properties);
|
||||
if (!CGImageDestinationFinalize(destination))
|
||||
{
|
||||
CFRelease(imageData);
|
||||
|
||||
@@ -364,7 +364,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
}
|
||||
} else {
|
||||
if (self->_onLoad) {
|
||||
self->_onLoad(onLoadParamsForSource(source));
|
||||
RCTImageSource *sourceLoaded = [source imageSourceWithSize:image.size scale:source.scale];
|
||||
self->_onLoad(onLoadParamsForSource(sourceLoaded));
|
||||
}
|
||||
if (self->_onLoadEnd) {
|
||||
self->_onLoadEnd(nil);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#import "RCTLocalAssetImageLoader.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
@@ -44,9 +44,9 @@ RCT_EXPORT_MODULE()
|
||||
partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
|
||||
RCTExecuteOnMainQueue(^{
|
||||
if (cancelled) {
|
||||
if (atomic_load(&cancelled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ RCT_EXPORT_MODULE()
|
||||
});
|
||||
|
||||
return ^{
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
atomic_store(&cancelled, YES);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const BridgeSpyStallHandler = {
|
||||
}
|
||||
}
|
||||
return `${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
|
||||
`${info.module ? (info.module + '.') : ''}${info.method}(${args})`;
|
||||
`${info.module ? (info.module + '.') : ''}${info.method}(${JSON.stringify(args)})`;
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -79,7 +79,7 @@ const LinkingManager = Platform.OS === 'android' ?
|
||||
* openURL:(NSURL *)url
|
||||
* options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
|
||||
* {
|
||||
* return [RCTLinkingManager application:app openURL:url options:options];
|
||||
* return [RCTLinkingManager application:application openURL:url options:options];
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
|
||||
@@ -35,7 +35,10 @@
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
2D2A28471D9B043800D4039D /* libRCTLinking-tvOS.a */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
|
||||
@@ -13,15 +13,14 @@
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification";
|
||||
|
||||
static NSString *const kOpenURLNotification = @"RCTOpenURLNotification";
|
||||
|
||||
static void postNotificationWithURL(NSURL *URL, id sender)
|
||||
{
|
||||
NSDictionary<NSString *, id> *payload = @{@"url": URL.absoluteString};
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTOpenURLNotification
|
||||
object:sender
|
||||
userInfo:payload];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification
|
||||
object:sender
|
||||
userInfo:payload];
|
||||
}
|
||||
|
||||
@implementation RCTLinkingManager
|
||||
@@ -37,7 +36,7 @@ RCT_EXPORT_MODULE()
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleOpenURLNotification:)
|
||||
name:RCTOpenURLNotification
|
||||
name:kOpenURLNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
@@ -74,7 +73,7 @@ continueUserActivity:(NSUserActivity *)userActivity
|
||||
{
|
||||
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
||||
NSDictionary *payload = @{@"url": userActivity.webpageURL.absoluteString};
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTOpenURLNotification
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenURLNotification
|
||||
object:self
|
||||
userInfo:payload];
|
||||
}
|
||||
|
||||
@@ -303,6 +303,7 @@ type DefaultProps = typeof defaultProps;
|
||||
* - By default, the list looks for a `key` prop on each item and uses that for the React key.
|
||||
* Alternatively, you can provide a custom `keyExtractor` prop.
|
||||
*
|
||||
* Also inherets [ScrollView Props](docs/scrollview.html#props), unless it is nested in another FlatList of same orientation.
|
||||
*/
|
||||
class FlatList<ItemT> extends React.PureComponent<
|
||||
DefaultProps,
|
||||
@@ -319,7 +320,7 @@ class FlatList<ItemT> extends React.PureComponent<
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to the item at a the specified index such that it is positioned in the viewable area
|
||||
* Scrolls to the item at the specified index such that it is positioned in the viewable area
|
||||
* such that `viewPosition` 0 places it at the top, 1 at the bottom, and 0.5 centered in the
|
||||
* middle. `viewOffset` is a fixed number of pixels to offset the final target position.
|
||||
*
|
||||
|
||||
@@ -173,6 +173,8 @@ var ListView = createReactClass({
|
||||
* on every render pass. If they are expensive to re-render, wrap them
|
||||
* in StaticContainer or other mechanism as appropriate. Footer is always
|
||||
* at the bottom of the list, and header at the top, on every render pass.
|
||||
* In a horizontal ListView, the header is rendered on the left and the
|
||||
* footer on the right.
|
||||
*/
|
||||
renderFooter: PropTypes.func,
|
||||
renderHeader: PropTypes.func,
|
||||
|
||||
@@ -62,7 +62,7 @@ type ParamType = {
|
||||
*
|
||||
* ```
|
||||
* getInitialState: function() {
|
||||
* var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged});
|
||||
* var ds = new ListView.DataSource({rowHasChanged: this._rowHasChanged});
|
||||
* return {ds};
|
||||
* },
|
||||
* _onDataArrived(newData) {
|
||||
@@ -154,11 +154,19 @@ class ListViewDataSource {
|
||||
* you also specify what your `sectionIdentities` are. If you don't care
|
||||
* about sections you should safely be able to use `cloneWithRows`.
|
||||
*
|
||||
* `sectionIdentities` is an array of identifiers for sections.
|
||||
* ie. ['s1', 's2', ...]. If not provided, it's assumed that the
|
||||
* `sectionIdentities` is an array of identifiers for sections.
|
||||
* ie. ['s1', 's2', ...]. The identifiers should correspond to the keys or array indexes
|
||||
* of the data you wish to include. If not provided, it's assumed that the
|
||||
* keys of dataBlob are the section identities.
|
||||
*
|
||||
* Note: this returns a new object!
|
||||
*
|
||||
* ```
|
||||
* const dataSource = ds.cloneWithRowsAndSections({
|
||||
* addresses: ['row 1', 'row 2'],
|
||||
* phone_numbers: ['data 1', 'data 2'],
|
||||
* }, ['phone_numbers']);
|
||||
* ```
|
||||
*/
|
||||
cloneWithRowsAndSections(
|
||||
dataBlob: any,
|
||||
@@ -207,10 +215,20 @@ class ListViewDataSource {
|
||||
return newSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows in the data source.
|
||||
*
|
||||
* If you are specifying the rowIdentities or sectionIdentities, then `getRowCount` will return the number of rows in the filtered data source.
|
||||
*/
|
||||
getRowCount(): number {
|
||||
return this._cachedRowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows in the data source (see `getRowCount` for how this is calculated) plus the number of sections in the data.
|
||||
*
|
||||
* If you are specifying the rowIdentities or sectionIdentities, then `getRowAndSectionCount` will return the number of rows & sections in the filtered data source.
|
||||
*/
|
||||
getRowAndSectionCount(): number {
|
||||
return this._cachedRowCount + this.sectionIdentities.length;
|
||||
}
|
||||
|
||||
@@ -217,8 +217,8 @@ type DefaultProps = typeof defaultProps;
|
||||
* Simple Examples:
|
||||
*
|
||||
* <SectionList
|
||||
* renderItem={({item}) => <ListItem title={item.title} />}
|
||||
* renderSectionHeader={({section}) => <H1 title={section.title} />}
|
||||
* renderItem={({item}) => <ListItem title={item} />}
|
||||
* renderSectionHeader={({section}) => <Header title={section.title} />}
|
||||
* sections={[ // homogenous rendering between sections
|
||||
* {data: [...], title: ...},
|
||||
* {data: [...], title: ...},
|
||||
@@ -228,9 +228,9 @@ type DefaultProps = typeof defaultProps;
|
||||
*
|
||||
* <SectionList
|
||||
* sections={[ // heterogeneous rendering between sections
|
||||
* {data: [...], title: ..., renderItem: ...},
|
||||
* {data: [...], title: ..., renderItem: ...},
|
||||
* {data: [...], title: ..., renderItem: ...},
|
||||
* {data: [...], renderItem: ...},
|
||||
* {data: [...], renderItem: ...},
|
||||
* {data: [...], renderItem: ...},
|
||||
* ]}
|
||||
* />
|
||||
*
|
||||
|
||||
@@ -47,7 +47,7 @@ const RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
|
||||
* return (
|
||||
* <View style={{marginTop: 22}}>
|
||||
* <Modal
|
||||
* animationType={"slide"}
|
||||
* animationType="slide"
|
||||
* transparent={false}
|
||||
* visible={this.state.modalVisible}
|
||||
* onRequestClose={() => {alert("Modal has been closed.")}}
|
||||
|
||||
@@ -274,7 +274,10 @@
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
2D2A28201D9B03D100D4039D /* libRCTAnimation.a */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
94C129491D4069170025F25C /* Drivers */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -170,6 +170,7 @@
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
[self stopAnimationsForNode:node];
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
valueNode.value = value.floatValue;
|
||||
@@ -264,6 +265,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopAnimationsForNode:(nonnull RCTAnimatedNode *)node
|
||||
{
|
||||
NSMutableArray<id<RCTAnimationDriver>> *discarded = [NSMutableArray new];
|
||||
for (id<RCTAnimationDriver> driver in _activeAnimations) {
|
||||
if ([driver.valueNode isEqual:node]) {
|
||||
[discarded addObject:driver];
|
||||
}
|
||||
}
|
||||
for (id<RCTAnimationDriver> driver in discarded) {
|
||||
[driver stopAnimation];
|
||||
[_activeAnimations removeObject:driver];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Events
|
||||
|
||||
- (void)addAnimatedEventToView:(nonnull NSNumber *)viewTag
|
||||
@@ -328,6 +343,7 @@
|
||||
NSMutableArray<RCTEventAnimation *> *driversForKey = _eventDrivers[key];
|
||||
if (driversForKey) {
|
||||
for (RCTEventAnimation *driver in driversForKey) {
|
||||
[self stopAnimationsForNode:driver.valueNode];
|
||||
[driver updateWithEvent:event];
|
||||
}
|
||||
|
||||
|
||||
+117
-45
@@ -22,6 +22,7 @@ const NetInfoEventEmitter = new NativeEventEmitter(RCTNetInfo);
|
||||
const DEVICE_CONNECTIVITY_EVENT = 'networkStatusDidChange';
|
||||
|
||||
type ChangeEventName = $Enum<{
|
||||
connectionChange: string,
|
||||
change: string,
|
||||
}>;
|
||||
|
||||
@@ -58,77 +59,76 @@ type ConnectivityStateAndroid = $Enum<{
|
||||
|
||||
const _subscriptions = new Map();
|
||||
|
||||
let _isConnected;
|
||||
let _isConnectedDeprecated;
|
||||
if (Platform.OS === 'ios') {
|
||||
_isConnected = function(
|
||||
_isConnectedDeprecated = function(
|
||||
reachability: ReachabilityStateIOS,
|
||||
): bool {
|
||||
return reachability !== 'none' && reachability !== 'unknown';
|
||||
};
|
||||
} else if (Platform.OS === 'android') {
|
||||
_isConnected = function(
|
||||
_isConnectedDeprecated = function(
|
||||
connectionType: ConnectivityStateAndroid,
|
||||
): bool {
|
||||
return connectionType !== 'NONE' && connectionType !== 'UNKNOWN';
|
||||
};
|
||||
}
|
||||
|
||||
function _isConnected(connection) {
|
||||
return connection.type !== 'none' && connection.type !== 'unknown';
|
||||
}
|
||||
|
||||
const _isConnectedSubscriptions = new Map();
|
||||
|
||||
/**
|
||||
* NetInfo exposes info about online/offline status
|
||||
*
|
||||
* ```
|
||||
* NetInfo.fetch().then((reach) => {
|
||||
* console.log('Initial: ' + reach);
|
||||
* NetInfo.getConnectionInfo().then((connectionInfo) => {
|
||||
* console.log('Initial, type: ' + connectionInfo.type + ', effectiveType: ' + connectionInfo.effectiveType);
|
||||
* });
|
||||
* function handleFirstConnectivityChange(reach) {
|
||||
* console.log('First change: ' + reach);
|
||||
* function handleFirstConnectivityChange(connectionInfo) {
|
||||
* console.log('First change, type: ' + connectionInfo.type + ', effectiveType: ' + connectionInfo.effectiveType);
|
||||
* NetInfo.removeEventListener(
|
||||
* 'change',
|
||||
* 'connectionChange',
|
||||
* handleFirstConnectivityChange
|
||||
* );
|
||||
* }
|
||||
* NetInfo.addEventListener(
|
||||
* 'change',
|
||||
* 'connectionChange',
|
||||
* handleFirstConnectivityChange
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* ### IOS
|
||||
* ### ConnectionType enum
|
||||
*
|
||||
* Asynchronously determine if the device is online and on a cellular network.
|
||||
* `ConnectionType` describes the type of connection the device is using to communicate with the network.
|
||||
*
|
||||
* Cross platform values for `ConnectionType`:
|
||||
* - `none` - device is offline
|
||||
* - `wifi` - device is online and connected via wifi, or is the iOS simulator
|
||||
* - `cell` - device is connected via Edge, 3G, WiMax, or LTE
|
||||
* - `cellular` - device is connected via Edge, 3G, WiMax, or LTE
|
||||
* - `unknown` - error case and the network status is unknown
|
||||
*
|
||||
* Android-only values for `ConnectionType`:
|
||||
* - `bluetooth` - device is connected via Bluetooth
|
||||
* - `ethernet` - device is connected via Ethernet
|
||||
* - `wimax` - device is connected via WiMAX
|
||||
*
|
||||
* ### EffectiveConnectionType enum
|
||||
*
|
||||
* Cross platform values for `EffectiveConnectionType`:
|
||||
* - `2g`
|
||||
* - `3g`
|
||||
* - `4g`
|
||||
* - `unknown`
|
||||
*
|
||||
* ### Android
|
||||
*
|
||||
* To request network info, you need to add the following line to your
|
||||
* app's `AndroidManifest.xml`:
|
||||
*
|
||||
* `<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />`
|
||||
* Asynchronously determine if the device is connected and details about that connection.
|
||||
*
|
||||
* Android Connectivity Types.
|
||||
*
|
||||
* - `NONE` - device is offline
|
||||
* - `BLUETOOTH` - The Bluetooth data connection.
|
||||
* - `DUMMY` - Dummy data connection.
|
||||
* - `ETHERNET` - The Ethernet data connection.
|
||||
* - `MOBILE` - The Mobile data connection.
|
||||
* - `MOBILE_DUN` - A DUN-specific Mobile data connection.
|
||||
* - `MOBILE_HIPRI` - A High Priority Mobile data connection.
|
||||
* - `MOBILE_MMS` - An MMS-specific Mobile data connection.
|
||||
* - `MOBILE_SUPL` - A SUPL-specific Mobile data connection.
|
||||
* - `VPN` - A virtual network using one or more native bearers. Requires API Level 21
|
||||
* - `WIFI` - The WIFI data connection.
|
||||
* - `WIMAX` - The WiMAX data connection.
|
||||
* - `UNKNOWN` - Unknown data connection.
|
||||
*
|
||||
* The rest ConnectivityStates are hidden by the Android API, but can be used if necessary.
|
||||
*
|
||||
* ### isConnectionExpensive
|
||||
*
|
||||
@@ -167,22 +167,77 @@ const _isConnectedSubscriptions = new Map();
|
||||
* handleFirstConnectivityChange
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* ### Connectivity Types (deprecated)
|
||||
*
|
||||
* The following connectivity types are deprecated. They're used by the deprecated APIs `fetch` and the `change` event.
|
||||
*
|
||||
* iOS connectivity types (deprecated):
|
||||
* - `none` - device is offline
|
||||
* - `wifi` - device is online and connected via wifi, or is the iOS simulator
|
||||
* - `cell` - device is connected via Edge, 3G, WiMax, or LTE
|
||||
* - `unknown` - error case and the network status is unknown
|
||||
*
|
||||
* Android connectivity types (deprecated).
|
||||
* - `NONE` - device is offline
|
||||
* - `BLUETOOTH` - The Bluetooth data connection.
|
||||
* - `DUMMY` - Dummy data connection.
|
||||
* - `ETHERNET` - The Ethernet data connection.
|
||||
* - `MOBILE` - The Mobile data connection.
|
||||
* - `MOBILE_DUN` - A DUN-specific Mobile data connection.
|
||||
* - `MOBILE_HIPRI` - A High Priority Mobile data connection.
|
||||
* - `MOBILE_MMS` - An MMS-specific Mobile data connection.
|
||||
* - `MOBILE_SUPL` - A SUPL-specific Mobile data connection.
|
||||
* - `VPN` - A virtual network using one or more native bearers. Requires API Level 21
|
||||
* - `WIFI` - The WIFI data connection.
|
||||
* - `WIMAX` - The WiMAX data connection.
|
||||
* - `UNKNOWN` - Unknown data connection.
|
||||
*
|
||||
* The rest of the connectivity types are hidden by the Android API, but can be used if necessary.
|
||||
*/
|
||||
const NetInfo = {
|
||||
/**
|
||||
* Invokes the listener whenever network status changes.
|
||||
* The listener receives one of the connectivity types listed above.
|
||||
* Adds an event handler. Supported events:
|
||||
*
|
||||
* - `connectionChange`: Fires when the network status changes. The argument to the event
|
||||
* handler is an object with keys:
|
||||
* - `type`: A `ConnectionType` (listed above)
|
||||
* - `effectiveType`: An `EffectiveConnectionType` (listed above)
|
||||
* - `change`: This event is deprecated. Listen to `connectionChange` instead. Fires when
|
||||
* the network status changes. The argument to the event handler is one of the deprecated
|
||||
* connectivity types listed above.
|
||||
*/
|
||||
addEventListener(
|
||||
eventName: ChangeEventName,
|
||||
handler: Function
|
||||
): {remove: () => void} {
|
||||
const listener = NetInfoEventEmitter.addListener(
|
||||
DEVICE_CONNECTIVITY_EVENT,
|
||||
(appStateData) => {
|
||||
handler(appStateData.network_info);
|
||||
}
|
||||
);
|
||||
let listener;
|
||||
if (eventName === 'connectionChange') {
|
||||
listener = NetInfoEventEmitter.addListener(
|
||||
DEVICE_CONNECTIVITY_EVENT,
|
||||
(appStateData) => {
|
||||
handler({
|
||||
type: appStateData.connectionType,
|
||||
effectiveType: appStateData.effectiveConnectionType
|
||||
});
|
||||
}
|
||||
);
|
||||
} else if (eventName === 'change') {
|
||||
console.warn('NetInfo\'s "change" event is deprecated. Listen to the "connectionChange" event instead.');
|
||||
|
||||
listener = NetInfoEventEmitter.addListener(
|
||||
DEVICE_CONNECTIVITY_EVENT,
|
||||
(appStateData) => {
|
||||
handler(appStateData.network_info);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.warn('Trying to subscribe to unknown event: "' + eventName + '"');
|
||||
return {
|
||||
remove: () => {}
|
||||
};
|
||||
}
|
||||
|
||||
_subscriptions.set(handler, listener);
|
||||
return {
|
||||
remove: () => NetInfo.removeEventListener(eventName, handler)
|
||||
@@ -205,13 +260,28 @@ const NetInfo = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves with one of the connectivity types listed
|
||||
* above.
|
||||
* This function is deprecated. Use `getConnectionInfo` instead. Returns a promise that
|
||||
* resolves with one of the deprecated connectivity types listed above.
|
||||
*/
|
||||
fetch(): Promise<any> {
|
||||
console.warn('NetInfo.fetch() is deprecated. Use NetInfo.getConnectionInfo() instead.');
|
||||
return RCTNetInfo.getCurrentConnectivity().then(resp => resp.network_info);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to an object with `type` and `effectiveType` keys
|
||||
* whose values are a `ConnectionType` and an `EffectiveConnectionType`, (described above),
|
||||
* respectively.
|
||||
*/
|
||||
getConnectionInfo(): Promise<any> {
|
||||
return RCTNetInfo.getCurrentConnectivity().then(resp => {
|
||||
return {
|
||||
type: resp.connectionType,
|
||||
effectiveType: resp.effectiveConnectionType,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* An object with the same methods as above but the listener receives a
|
||||
* boolean which represents the internet connectivity.
|
||||
@@ -224,7 +294,11 @@ const NetInfo = {
|
||||
handler: Function
|
||||
): {remove: () => void} {
|
||||
const listener = (connection) => {
|
||||
handler(_isConnected(connection));
|
||||
if (eventName === 'change') {
|
||||
handler(_isConnectedDeprecated(connection));
|
||||
} else if (eventName === 'connectionChange') {
|
||||
handler(_isConnected(connection));
|
||||
}
|
||||
};
|
||||
_isConnectedSubscriptions.set(handler, listener);
|
||||
NetInfo.addEventListener(
|
||||
@@ -252,9 +326,7 @@ const NetInfo = {
|
||||
},
|
||||
|
||||
fetch(): Promise<any> {
|
||||
return NetInfo.fetch().then(
|
||||
(connection) => _isConnected(connection)
|
||||
);
|
||||
return NetInfo.getConnectionInfo().then(_isConnected);
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -9,10 +9,28 @@
|
||||
|
||||
#import "RCTNetInfo.h"
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
|
||||
#endif
|
||||
#import <React/RCTAssert.h>
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
|
||||
// Based on the ConnectionType enum described in the W3C Network Information API spec
|
||||
// (https://wicg.github.io/netinfo/).
|
||||
static NSString *const RCTConnectionTypeUnknown = @"unknown";
|
||||
static NSString *const RCTConnectionTypeNone = @"none";
|
||||
static NSString *const RCTConnectionTypeWifi = @"wifi";
|
||||
static NSString *const RCTConnectionTypeCellular = @"cellular";
|
||||
|
||||
// Based on the EffectiveConnectionType enum described in the W3C Network Information API spec
|
||||
// (https://wicg.github.io/netinfo/).
|
||||
static NSString *const RCTEffectiveConnectionTypeUnknown = @"unknown";
|
||||
static NSString *const RCTEffectiveConnectionType2g = @"2g";
|
||||
static NSString *const RCTEffectiveConnectionType3g = @"3g";
|
||||
static NSString *const RCTEffectiveConnectionType4g = @"4g";
|
||||
|
||||
// The RCTReachabilityState* values are deprecated.
|
||||
static NSString *const RCTReachabilityStateUnknown = @"unknown";
|
||||
static NSString *const RCTReachabilityStateNone = @"none";
|
||||
static NSString *const RCTReachabilityStateWifi = @"wifi";
|
||||
@@ -21,7 +39,9 @@ static NSString *const RCTReachabilityStateCell = @"cell";
|
||||
@implementation RCTNetInfo
|
||||
{
|
||||
SCNetworkReachabilityRef _reachability;
|
||||
NSString *_status;
|
||||
NSString *_connectionType;
|
||||
NSString *_effectiveConnectionType;
|
||||
NSString *_statusDeprecated;
|
||||
NSString *_host;
|
||||
}
|
||||
|
||||
@@ -30,27 +50,57 @@ RCT_EXPORT_MODULE()
|
||||
static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
|
||||
{
|
||||
RCTNetInfo *self = (__bridge id)info;
|
||||
NSString *connectionType = RCTConnectionTypeUnknown;
|
||||
NSString *effectiveConnectionType = RCTEffectiveConnectionTypeUnknown;
|
||||
NSString *status = RCTReachabilityStateUnknown;
|
||||
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0 ||
|
||||
(flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0) {
|
||||
connectionType = RCTConnectionTypeNone;
|
||||
status = RCTReachabilityStateNone;
|
||||
}
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
|
||||
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
|
||||
connectionType = RCTConnectionTypeCellular;
|
||||
status = RCTReachabilityStateCell;
|
||||
|
||||
CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init];
|
||||
if (netinfo) {
|
||||
if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS] ||
|
||||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] ||
|
||||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMA1x]) {
|
||||
effectiveConnectionType = RCTEffectiveConnectionType2g;
|
||||
} else if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyWCDMA] ||
|
||||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSDPA] ||
|
||||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSUPA] ||
|
||||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORev0] ||
|
||||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevA] ||
|
||||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevB] ||
|
||||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyeHRPD]) {
|
||||
effectiveConnectionType = RCTEffectiveConnectionType3g;
|
||||
} else if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) {
|
||||
effectiveConnectionType = RCTEffectiveConnectionType4g;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
else {
|
||||
connectionType = RCTConnectionTypeWifi;
|
||||
status = RCTReachabilityStateWifi;
|
||||
}
|
||||
|
||||
if (![status isEqualToString:self->_status]) {
|
||||
self->_status = status;
|
||||
[self sendEventWithName:@"networkStatusDidChange" body:@{@"network_info": status}];
|
||||
|
||||
if (![connectionType isEqualToString:self->_connectionType] ||
|
||||
![effectiveConnectionType isEqualToString:self->_effectiveConnectionType] ||
|
||||
![status isEqualToString:self->_statusDeprecated]) {
|
||||
self->_connectionType = connectionType;
|
||||
self->_effectiveConnectionType = effectiveConnectionType;
|
||||
self->_statusDeprecated = status;
|
||||
[self sendEventWithName:@"networkStatusDidChange" body:@{@"connectionType": connectionType,
|
||||
@"effectiveConnectionType": effectiveConnectionType,
|
||||
@"network_info": status}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +124,9 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC
|
||||
|
||||
- (void)startObserving
|
||||
{
|
||||
_status = RCTReachabilityStateUnknown;
|
||||
_connectionType = RCTConnectionTypeUnknown;
|
||||
_effectiveConnectionType = RCTEffectiveConnectionTypeUnknown;
|
||||
_statusDeprecated = RCTReachabilityStateUnknown;
|
||||
_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, _host.UTF8String ?: "apple.com");
|
||||
SCNetworkReachabilityContext context = { 0, ( __bridge void *)self, NULL, NULL, NULL };
|
||||
SCNetworkReachabilitySetCallback(_reachability, RCTReachabilityCallback, &context);
|
||||
@@ -94,7 +146,9 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC
|
||||
RCT_EXPORT_METHOD(getCurrentConnectivity:(RCTPromiseResolveBlock)resolve
|
||||
reject:(__unused RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
resolve(@{@"network_info": _status ?: RCTReachabilityStateUnknown});
|
||||
resolve(@{@"connectionType": _connectionType ?: RCTConnectionTypeUnknown,
|
||||
@"effectiveConnectionType": _effectiveConnectionType ?: RCTEffectiveConnectionTypeUnknown,
|
||||
@"network_info": _statusDeprecated ?: RCTReachabilityStateUnknown});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
58B511DC1A9E6C8500147676 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule CPUProfiler
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var _label;
|
||||
var _nestingLevel = 0;
|
||||
|
||||
var CPUProfiler = {
|
||||
|
||||
start(profileName: string) {
|
||||
if (_nestingLevel === 0) {
|
||||
if (global.nativeProfilerStart) {
|
||||
_label = profileName;
|
||||
global.nativeProfilerStart(profileName);
|
||||
} else if (console.profile) {
|
||||
console.profile(profileName);
|
||||
}
|
||||
}
|
||||
_nestingLevel++;
|
||||
},
|
||||
end() {
|
||||
_nestingLevel--;
|
||||
if (_nestingLevel === 0) {
|
||||
if (global.nativeProfilerEnd) {
|
||||
global.nativeProfilerEnd(_label);
|
||||
} else if (console.profileEnd) {
|
||||
console.profileEnd();
|
||||
}
|
||||
_label = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = CPUProfiler;
|
||||
@@ -11,10 +11,10 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var SamplingProfiler = {
|
||||
const SamplingProfiler = {
|
||||
poke: function (token: number): void {
|
||||
var error = null;
|
||||
var result = null;
|
||||
let error = null;
|
||||
let result = null;
|
||||
try {
|
||||
result = global.pokeSamplingProfiler();
|
||||
if (result === null) {
|
||||
@@ -27,8 +27,9 @@ var SamplingProfiler = {
|
||||
'Error occured when restarting Sampling Profiler: ' + e.toString());
|
||||
error = e.toString();
|
||||
}
|
||||
require('NativeModules').JSCSamplingProfiler.operationComplete(
|
||||
token, result, error);
|
||||
|
||||
const {JSCSamplingProfiler} = require('NativeModules');
|
||||
JSCSamplingProfiler.operationComplete(token, result, error);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
NSString *const RCTLocalNotificationReceived = @"LocalNotificationReceived";
|
||||
NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived";
|
||||
NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered";
|
||||
NSString *const RCTRegisterUserNotificationSettings = @"RegisterUserNotificationSettings";
|
||||
|
||||
NSString *const RCTErrorUnableToRequestPermissions = @"E_UNABLE_TO_REQUEST_PERMISSIONS";
|
||||
NSString *const RCTErrorRemoteNotificationRegistrationFailed = @"E_FAILED_TO_REGISTER_FOR_REMOTE_NOTIFICATIONS";
|
||||
static NSString *const kLocalNotificationReceived = @"LocalNotificationReceived";
|
||||
static NSString *const kRemoteNotificationsRegistered = @"RemoteNotificationsRegistered";
|
||||
static NSString *const kRegisterUserNotificationSettings = @"RegisterUserNotificationSettings";
|
||||
static NSString *const kRemoteNotificationRegistrationFailed = @"RemoteNotificationRegistrationFailed";
|
||||
|
||||
static NSString *const kErrorUnableToRequestPermissions = @"E_UNABLE_TO_REQUEST_PERMISSIONS";
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
@implementation RCTConvert (NSCalendarUnit)
|
||||
@@ -139,7 +140,7 @@ RCT_EXPORT_MODULE()
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleLocalNotificationReceived:)
|
||||
name:RCTLocalNotificationReceived
|
||||
name:kLocalNotificationReceived
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleRemoteNotificationReceived:)
|
||||
@@ -147,15 +148,15 @@ RCT_EXPORT_MODULE()
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleRegisterUserNotificationSettings:)
|
||||
name:RCTRegisterUserNotificationSettings
|
||||
name:kRegisterUserNotificationSettings
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleRemoteNotificationsRegistered:)
|
||||
name:RCTRemoteNotificationsRegistered
|
||||
name:kRemoteNotificationsRegistered
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleRemoteNotificationRegistrationError:)
|
||||
name:RCTErrorRemoteNotificationRegistrationFailed
|
||||
name:kRemoteNotificationRegistrationFailed
|
||||
object:nil];
|
||||
}
|
||||
|
||||
@@ -176,7 +177,7 @@ RCT_EXPORT_MODULE()
|
||||
{
|
||||
if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)]) {
|
||||
[RCTSharedApplication() registerForRemoteNotifications];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRegisterUserNotificationSettings
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kRegisterUserNotificationSettings
|
||||
object:self
|
||||
userInfo:@{@"notificationSettings": notificationSettings}];
|
||||
}
|
||||
@@ -190,14 +191,14 @@ RCT_EXPORT_MODULE()
|
||||
for (NSUInteger i = 0; i < deviceTokenLength; i++) {
|
||||
[hexString appendFormat:@"%02x", bytes[i]];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationsRegistered
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kRemoteNotificationsRegistered
|
||||
object:self
|
||||
userInfo:@{@"deviceToken" : [hexString copy]}];
|
||||
}
|
||||
|
||||
+ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTErrorRemoteNotificationRegistrationFailed
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kRemoteNotificationRegistrationFailed
|
||||
object:self
|
||||
userInfo:@{@"error": error}];
|
||||
}
|
||||
@@ -221,7 +222,7 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
+ (void)didReceiveLocalNotification:(UILocalNotification *)notification
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTLocalNotificationReceived
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kLocalNotificationReceived
|
||||
object:self
|
||||
userInfo:RCTFormatLocalNotification(notification)];
|
||||
}
|
||||
@@ -315,7 +316,7 @@ RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
reject(RCTErrorUnableToRequestPermissions, nil, RCTErrorWithMessage(@"Requesting push notifications is currently unavailable in an app extension"));
|
||||
reject(kErrorUnableToRequestPermissions, nil, RCTErrorWithMessage(@"Requesting push notifications is currently unavailable in an app extension"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
580C37701AB104AF0015E709 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -15,16 +15,17 @@
|
||||
#define FB_REFERENCE_IMAGE_DIR ""
|
||||
#endif
|
||||
|
||||
#define RCT_RUN_RUNLOOP_WHILE(CONDITION) \
|
||||
{ \
|
||||
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
|
||||
while ((CONDITION)) { \
|
||||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
|
||||
if ([timeout timeIntervalSinceNow] <= 0) { \
|
||||
XCTFail(@"Runloop timed out before condition was met"); \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
#define RCT_RUN_RUNLOOP_WHILE(CONDITION) \
|
||||
{ \
|
||||
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
|
||||
NSRunLoop *runloop = [NSRunLoop mainRunLoop]; \
|
||||
while ((CONDITION)) { \
|
||||
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; \
|
||||
if ([timeout timeIntervalSinceNow] <= 0) { \
|
||||
XCTFail(@"Runloop timed out before condition was met"); \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,7 +54,10 @@
|
||||
13BE3DED1AC21097009241FE /* Sample.m */,
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
|
||||
@@ -35,7 +35,10 @@
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
2D2A28611D9B046600D4039D /* libRCTSettings-tvOS.a */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
|
||||
@@ -426,7 +426,7 @@ var LayoutPropTypes = {
|
||||
'space-around'
|
||||
]),
|
||||
|
||||
/** `overflow` controls how a children are measured and displayed.
|
||||
/** `overflow` controls how children are measured and displayed.
|
||||
* `overflow: hidden` causes views to be clipped while `overflow: scroll`
|
||||
* causes views to be measured independently of their parents main axis.
|
||||
* It works like `overflow` in CSS (default: visible).
|
||||
|
||||
@@ -42,6 +42,22 @@ var DecomposedMatrixPropType = function(
|
||||
};
|
||||
|
||||
var TransformPropTypes = {
|
||||
/**
|
||||
* `transform` accepts an array of transformation objects. Each object specifies
|
||||
* the property that will be transformed as the key, and the value to use in the
|
||||
* transformation. Objects should not be combined. Use a single key/value pair
|
||||
* per object.
|
||||
*
|
||||
* The rotate transformations require a string so that the transform may be
|
||||
* expressed in degrees (deg) or radians (rad). For example:
|
||||
*
|
||||
* `transform([{ rotateX: '45deg' }, { rotateZ: '0.785398rad' }])`
|
||||
*
|
||||
* The skew transformations require a string so that the transform may be
|
||||
* expressed in degrees (deg). For example:
|
||||
*
|
||||
* `transform([{ skewX: '45deg' }])`
|
||||
*/
|
||||
transform: ReactPropTypes.arrayOf(
|
||||
ReactPropTypes.oneOfType([
|
||||
ReactPropTypes.shape({perspective: ReactPropTypes.number}),
|
||||
@@ -59,8 +75,13 @@ var TransformPropTypes = {
|
||||
])
|
||||
),
|
||||
|
||||
/* Deprecated */
|
||||
/**
|
||||
* Deprecated. Use the transform prop instead.
|
||||
*/
|
||||
transformMatrix: TransformMatrixPropType,
|
||||
/**
|
||||
* Deprecated. Use the transform prop instead.
|
||||
*/
|
||||
decomposedMatrix: DecomposedMatrixPropType,
|
||||
|
||||
/* Deprecated transform props used on Android only */
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol RCTBackedTextInputViewProtocol;
|
||||
|
||||
@protocol RCTBackedTextInputDelegate <NSObject>
|
||||
|
||||
- (BOOL)textInputShouldBeginEditing; // Return `NO` to disallow editing.
|
||||
- (void)textInputDidBeginEditing;
|
||||
|
||||
- (BOOL)textInputShouldEndEditing; // Return `YES` to allow editing to stop and to resign first responder status. `NO` to disallow the editing session to end.
|
||||
- (void)textInputDidEndEditing; // May be called if forced even if `textInputShouldEndEditing` returns `NO` (e.g. view removed from window) or `[textInput endEditing:YES]` called.
|
||||
|
||||
- (BOOL)textInputShouldReturn; // May be called right before `textInputShouldEndEditing` if "Return" button was pressed.
|
||||
- (void)textInputDidReturn;
|
||||
|
||||
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string; // Return NO to not change text.
|
||||
- (void)textInputDidChange;
|
||||
|
||||
- (void)textInputDidChangeSelection;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBackedTextInputViewProtocol.h"
|
||||
#import "RCTBackedTextInputDelegate.h"
|
||||
|
||||
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
|
||||
|
||||
@interface RCTBackedTextFieldDelegateAdapter : NSObject
|
||||
|
||||
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInput;
|
||||
|
||||
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
|
||||
- (void)selectedTextRangeWasSet;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView)
|
||||
|
||||
@interface RCTBackedTextViewDelegateAdapter : NSObject
|
||||
|
||||
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInput;
|
||||
|
||||
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTBackedTextInputDelegateAdapter.h"
|
||||
|
||||
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
|
||||
|
||||
static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingContext;
|
||||
|
||||
@interface RCTBackedTextFieldDelegateAdapter () <UITextFieldDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTBackedTextFieldDelegateAdapter {
|
||||
__weak UITextField<RCTBackedTextInputViewProtocol> *_backedTextInput;
|
||||
BOOL _textDidChangeIsComing;
|
||||
UITextRange *_previousSelectedTextRange;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInput
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_backedTextInput = backedTextInput;
|
||||
backedTextInput.delegate = self;
|
||||
|
||||
[_backedTextInput addTarget:self action:@selector(textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
|
||||
[_backedTextInput addTarget:self action:@selector(textFieldDidEndEditingOnExit) forControlEvents:UIControlEventEditingDidEndOnExit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_backedTextInput removeTarget:self action:nil forControlEvents:UIControlEventEditingChanged];
|
||||
[_backedTextInput removeTarget:self action:nil forControlEvents:UIControlEventEditingDidEndOnExit];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textFieldShouldBeginEditing:(__unused UITextField *)textField
|
||||
{
|
||||
return [_backedTextInput.textInputDelegate textInputShouldBeginEditing];
|
||||
}
|
||||
|
||||
- (void)textFieldDidBeginEditing:(__unused UITextField *)textField
|
||||
{
|
||||
[_backedTextInput.textInputDelegate textInputDidBeginEditing];
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldEndEditing:(__unused UITextField *)textField
|
||||
{
|
||||
return [_backedTextInput.textInputDelegate textInputShouldEndEditing];
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(__unused UITextField *)textField
|
||||
{
|
||||
if (_textDidChangeIsComing) {
|
||||
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
|
||||
// which was triggered by losing focus. So, we call it manually.
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInput.textInputDelegate textInputDidChange];
|
||||
}
|
||||
|
||||
[_backedTextInput.textInputDelegate textInputDidEndEditing];
|
||||
}
|
||||
|
||||
- (BOOL)textField:(__unused UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
|
||||
{
|
||||
BOOL result = [_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:range replacementText:string];
|
||||
if (result) {
|
||||
_textDidChangeIsComing = YES;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(__unused UITextField *)textField
|
||||
{
|
||||
return [_backedTextInput.textInputDelegate textInputShouldReturn];
|
||||
}
|
||||
|
||||
#pragma mark - UIControlEventEditing* Family Events
|
||||
|
||||
- (void)textFieldDidChange
|
||||
{
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInput.textInputDelegate textInputDidChange];
|
||||
|
||||
// `selectedTextRangeWasSet` isn't triggered during typing.
|
||||
[self textFieldProbablyDidChangeSelection];
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditingOnExit
|
||||
{
|
||||
[_backedTextInput.textInputDelegate textInputDidReturn];
|
||||
}
|
||||
|
||||
#pragma mark - UIKeyboardInput (private UIKit protocol)
|
||||
|
||||
// This method allows us to detect a [Backspace] `keyPress`
|
||||
// even when there is no more text in the `UITextField`.
|
||||
- (BOOL)keyboardInputShouldDelete:(__unused UITextField *)textField
|
||||
{
|
||||
[_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:NSMakeRange(0, 0) replacementText:@""];
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Public Interface
|
||||
|
||||
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange
|
||||
{
|
||||
_previousSelectedTextRange = textRange;
|
||||
}
|
||||
|
||||
- (void)selectedTextRangeWasSet
|
||||
{
|
||||
[self textFieldProbablyDidChangeSelection];
|
||||
}
|
||||
|
||||
#pragma mark - Generalization
|
||||
|
||||
- (void)textFieldProbablyDidChangeSelection
|
||||
{
|
||||
if ([_backedTextInput.selectedTextRange isEqual:_previousSelectedTextRange]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_previousSelectedTextRange = _backedTextInput.selectedTextRange;
|
||||
[_backedTextInput.textInputDelegate textInputDidChangeSelection];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView)
|
||||
|
||||
@interface RCTBackedTextViewDelegateAdapter () <UITextViewDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTBackedTextViewDelegateAdapter {
|
||||
__weak UITextView<RCTBackedTextInputViewProtocol> *_backedTextInput;
|
||||
BOOL _textDidChangeIsComing;
|
||||
UITextRange *_previousSelectedTextRange;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInput
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_backedTextInput = backedTextInput;
|
||||
backedTextInput.delegate = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - UITextViewDelegate
|
||||
|
||||
- (BOOL)textViewShouldBeginEditing:(__unused UITextView *)textView
|
||||
{
|
||||
return [_backedTextInput.textInputDelegate textInputShouldBeginEditing];
|
||||
}
|
||||
|
||||
- (void)textViewDidBeginEditing:(__unused UITextView *)textView
|
||||
{
|
||||
[_backedTextInput.textInputDelegate textInputDidBeginEditing];
|
||||
}
|
||||
|
||||
- (BOOL)textViewShouldEndEditing:(__unused UITextView *)textView
|
||||
{
|
||||
return [_backedTextInput.textInputDelegate textInputShouldEndEditing];
|
||||
}
|
||||
|
||||
- (void)textViewDidEndEditing:(__unused UITextView *)textView
|
||||
{
|
||||
if (_textDidChangeIsComing) {
|
||||
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
|
||||
// which was triggered by losing focus. So, we call it manually.
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInput.textInputDelegate textInputDidChange];
|
||||
}
|
||||
|
||||
[_backedTextInput.textInputDelegate textInputDidEndEditing];
|
||||
}
|
||||
|
||||
- (BOOL)textView:(__unused UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
||||
{
|
||||
// Custom implementation of `textInputShouldReturn` and `textInputDidReturn` pair for `UITextView`.
|
||||
if (!_backedTextInput.textWasPasted && [text isEqualToString:@"\n"]) {
|
||||
if ([_backedTextInput.textInputDelegate textInputShouldReturn]) {
|
||||
[_backedTextInput.textInputDelegate textInputDidReturn];
|
||||
[_backedTextInput endEditing:NO];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL result = [_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:range replacementText:text];
|
||||
if (result) {
|
||||
_textDidChangeIsComing = YES;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(__unused UITextView *)textView
|
||||
{
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInput.textInputDelegate textInputDidChange];
|
||||
}
|
||||
|
||||
- (void)textViewDidChangeSelection:(__unused UITextView *)textView
|
||||
{
|
||||
[self textViewProbablyDidChangeSelection];
|
||||
}
|
||||
|
||||
#pragma mark - Public Interface
|
||||
|
||||
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange
|
||||
{
|
||||
_previousSelectedTextRange = textRange;
|
||||
}
|
||||
|
||||
#pragma mark - Generalization
|
||||
|
||||
- (void)textViewProbablyDidChangeSelection
|
||||
{
|
||||
if ([_backedTextInput.selectedTextRange isEqual:_previousSelectedTextRange]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_previousSelectedTextRange = _backedTextInput.selectedTextRange;
|
||||
[_backedTextInput.textInputDelegate textInputDidChangeSelection];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol RCTBackedTextInputDelegate;
|
||||
|
||||
@protocol RCTBackedTextInputViewProtocol <UITextInput>
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *text;
|
||||
@@ -19,5 +21,15 @@
|
||||
@property (nonatomic, strong, nullable) UIFont *font;
|
||||
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
|
||||
@property (nonatomic, strong, nullable) UIView *inputAccessoryView;
|
||||
@property (nonatomic, weak, nullable) id<RCTBackedTextInputDelegate> textInputDelegate;
|
||||
@property (nonatomic, readonly) CGSize contentSize;
|
||||
|
||||
// This protocol disallows direct access to `selectedTextRange` property because
|
||||
// unwise usage of it can break the `delegate` behavior. So, we always have to
|
||||
// explicitly specify should `delegate` be notified about the change or not.
|
||||
// If the change was initiated programmatically, we must NOT notify the delegate.
|
||||
// If the change was a result of user actions (like typing or touches), we MUST notify the delegate.
|
||||
- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange NS_UNAVAILABLE;
|
||||
- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate;
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
#import "RCTText.h"
|
||||
#import "RCTTextView.h"
|
||||
|
||||
NSString *const RCTShadowViewAttributeName = @"RCTShadowViewAttributeName";
|
||||
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
|
||||
NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
|
||||
|
||||
CGFloat const RCTTextAutoSizeDefaultMinimumFontScale = 0.5f;
|
||||
CGFloat const RCTTextAutoSizeWidthErrorMargin = 0.05f;
|
||||
CGFloat const RCTTextAutoSizeHeightErrorMargin = 0.025f;
|
||||
CGFloat const RCTTextAutoSizeGranularity = 0.001f;
|
||||
static NSString *const kShadowViewAttributeName = @"RCTShadowViewAttributeName";
|
||||
|
||||
static CGFloat const kAutoSizeWidthErrorMargin = 0.05f;
|
||||
static CGFloat const kAutoSizeHeightErrorMargin = 0.025f;
|
||||
static CGFloat const kAutoSizeGranularity = 0.001f;
|
||||
|
||||
@implementation RCTShadowText
|
||||
{
|
||||
@@ -165,7 +165,7 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
||||
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
|
||||
[layoutManager.textStorage enumerateAttribute:RCTShadowViewAttributeName inRange:characterRange options:0 usingBlock:^(RCTShadowView *child, NSRange range, BOOL *_) {
|
||||
[layoutManager.textStorage enumerateAttribute:kShadowViewAttributeName inRange:characterRange options:0 usingBlock:^(RCTShadowView *child, NSRange range, BOOL *_) {
|
||||
if (child) {
|
||||
YGNodeRef childNode = child.yogaNode;
|
||||
float width = YGNodeStyleGetWidth(childNode).value;
|
||||
@@ -334,7 +334,7 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
|
||||
attachment.bounds = (CGRect){CGPointZero, {width, height}};
|
||||
NSMutableAttributedString *attachmentString = [NSMutableAttributedString new];
|
||||
[attachmentString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
|
||||
[attachmentString addAttribute:RCTShadowViewAttributeName value:child range:(NSRange){0, attachmentString.length}];
|
||||
[attachmentString addAttribute:kShadowViewAttributeName value:child range:(NSRange){0, attachmentString.length}];
|
||||
[attributedString appendAttributedString:attachmentString];
|
||||
if (height > heightOfTallestSubview) {
|
||||
heightOfTallestSubview = height;
|
||||
@@ -516,7 +516,7 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
|
||||
prevMid:(CGFloat)prevMid
|
||||
{
|
||||
CGFloat midScale = (minScale + maxScale) / 2.0f;
|
||||
if (round((prevMid / RCTTextAutoSizeGranularity)) == round((midScale / RCTTextAutoSizeGranularity))) {
|
||||
if (round((prevMid / kAutoSizeGranularity)) == round((midScale / kAutoSizeGranularity))) {
|
||||
//Bail because we can't meet error margin.
|
||||
return [self calculateSize:textStorage];
|
||||
} else {
|
||||
@@ -529,12 +529,12 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
|
||||
return [self calculateOptimumScaleInFrame:frame
|
||||
forStorage:textStorage
|
||||
minScale:minScale
|
||||
maxScale:midScale - RCTTextAutoSizeGranularity
|
||||
maxScale:midScale - kAutoSizeGranularity
|
||||
prevMid:midScale];
|
||||
} else {
|
||||
return [self calculateOptimumScaleInFrame:frame
|
||||
forStorage:textStorage
|
||||
minScale:midScale + RCTTextAutoSizeGranularity
|
||||
minScale:midScale + kAutoSizeGranularity
|
||||
maxScale:maxScale
|
||||
prevMid:midScale];
|
||||
}
|
||||
@@ -577,8 +577,8 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
|
||||
textContainer.maximumNumberOfLines == 0;
|
||||
|
||||
if (fitLines && fitSize) {
|
||||
if ((requiredSize.width + (CGRectGetWidth(frame) * RCTTextAutoSizeWidthErrorMargin)) > CGRectGetWidth(frame) &&
|
||||
(requiredSize.height + (CGRectGetHeight(frame) * RCTTextAutoSizeHeightErrorMargin)) > CGRectGetHeight(frame))
|
||||
if ((requiredSize.width + (CGRectGetWidth(frame) * kAutoSizeWidthErrorMargin)) > CGRectGetWidth(frame) &&
|
||||
(requiredSize.height + (CGRectGetHeight(frame) * kAutoSizeHeightErrorMargin)) > CGRectGetHeight(frame))
|
||||
{
|
||||
return RCTSizeWithinRange;
|
||||
} else {
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CB1A9E6C5C00147676 /* RCTShadowText.m */; };
|
||||
58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */; };
|
||||
58B512161A9E6EFF00147676 /* RCTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512141A9E6EFF00147676 /* RCTText.m */; };
|
||||
598F41261F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 598F41251F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m */; };
|
||||
598F41271F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 598F41251F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m */; };
|
||||
599DF25F1F0306660079B53E /* RCTBackedTextInputViewProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */; };
|
||||
599DF2611F0306C30079B53E /* RCTBackedTextInputViewProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */; };
|
||||
599DF2641F03076D0079B53E /* RCTTextInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 599DF2631F03076D0079B53E /* RCTTextInput.m */; };
|
||||
@@ -95,6 +97,9 @@
|
||||
58B511CD1A9E6C5C00147676 /* RCTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextManager.m; sourceTree = "<group>"; };
|
||||
58B512141A9E6EFF00147676 /* RCTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTText.m; sourceTree = "<group>"; };
|
||||
58B512151A9E6EFF00147676 /* RCTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTText.h; sourceTree = "<group>"; };
|
||||
598F41231F145D4900B8495B /* RCTBackedTextInputDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegate.h; sourceTree = "<group>"; };
|
||||
598F41241F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegateAdapter.h; sourceTree = "<group>"; };
|
||||
598F41251F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBackedTextInputDelegateAdapter.m; sourceTree = "<group>"; };
|
||||
599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputViewProtocol.h; sourceTree = "<group>"; };
|
||||
599DF2621F03076D0079B53E /* RCTTextInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextInput.h; sourceTree = "<group>"; };
|
||||
599DF2631F03076D0079B53E /* RCTTextInput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextInput.m; sourceTree = "<group>"; };
|
||||
@@ -115,6 +120,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
58B5119C1A9E6C1200147676 /* Products */,
|
||||
598F41231F145D4900B8495B /* RCTBackedTextInputDelegate.h */,
|
||||
598F41241F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.h */,
|
||||
598F41251F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m */,
|
||||
599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */,
|
||||
AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */,
|
||||
AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */,
|
||||
@@ -152,6 +160,7 @@
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
58B5119C1A9E6C1200147676 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
@@ -244,6 +253,7 @@
|
||||
2D3B5F361D9B106F00451313 /* RCTShadowText.m in Sources */,
|
||||
2D3B5F3B1D9B106F00451313 /* RCTTextView.m in Sources */,
|
||||
59AF89AB1EDCBCC700F004B1 /* RCTUITextField.m in Sources */,
|
||||
598F41271F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */,
|
||||
2D3B5F3A1D9B106F00451313 /* RCTTextFieldManager.m in Sources */,
|
||||
599DF2651F03076D0079B53E /* RCTTextInput.m in Sources */,
|
||||
2D3B5F341D9B103100451313 /* RCTRawTextManager.m in Sources */,
|
||||
@@ -267,6 +277,7 @@
|
||||
19FC5C851D41A4120090108F /* RCTTextSelection.m in Sources */,
|
||||
1362F1001B4D51F400E06D8C /* RCTTextField.m in Sources */,
|
||||
59AF89AA1EDCBCC700F004B1 /* RCTUITextField.m in Sources */,
|
||||
598F41261F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */,
|
||||
58B512161A9E6EFF00147676 /* RCTText.m in Sources */,
|
||||
599DF2641F03076D0079B53E /* RCTTextInput.m in Sources */,
|
||||
1362F1011B4D51F400E06D8C /* RCTTextFieldManager.m in Sources */,
|
||||
|
||||
@@ -19,13 +19,6 @@
|
||||
@interface RCTTextField : RCTTextInput
|
||||
|
||||
@property (nonatomic, assign) BOOL caretHidden;
|
||||
@property (nonatomic, assign) BOOL selectTextOnFocus;
|
||||
@property (nonatomic, assign) BOOL blurOnSubmit;
|
||||
@property (nonatomic, assign) NSInteger mostRecentEventCount;
|
||||
@property (nonatomic, strong) NSNumber *maxLength;
|
||||
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;
|
||||
|
||||
@property (nonatomic, strong) RCTUITextField *textField;
|
||||
|
||||
@end
|
||||
|
||||
+31
-179
@@ -16,48 +16,32 @@
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
#import "RCTBackedTextInputDelegate.h"
|
||||
#import "RCTTextSelection.h"
|
||||
#import "RCTUITextField.h"
|
||||
|
||||
@interface RCTTextField () <UITextFieldDelegate>
|
||||
@interface RCTTextField () <RCTBackedTextInputDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTTextField
|
||||
{
|
||||
NSInteger _nativeEventCount;
|
||||
RCTUITextField *_backedTextInput;
|
||||
BOOL _submitted;
|
||||
UITextRange *_previousSelectionRange;
|
||||
NSString *_finalText;
|
||||
CGSize _previousContentSize;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super initWithBridge:bridge]) {
|
||||
RCTAssertParam(bridge);
|
||||
|
||||
// `blurOnSubmit` defaults to `true` for <TextInput multiline={false}> by design.
|
||||
_blurOnSubmit = YES;
|
||||
|
||||
_textField = [[RCTUITextField alloc] initWithFrame:self.bounds];
|
||||
_textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_backedTextInput = [[RCTUITextField alloc] initWithFrame:self.bounds];
|
||||
_backedTextInput.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_backedTextInput.textInputDelegate = self;
|
||||
|
||||
// Note: `UITextField` fires same events to channels in this order: delegate method, notification center, target action.
|
||||
// Usually (presumably) all events with equivalent semantic fires consistently in specified order...
|
||||
// but in practice, it is not always true, unfortunately.
|
||||
// Surprisingly, seems subscribing via Notification Center is the most reliable way to get these events.
|
||||
|
||||
_textField.delegate = self;
|
||||
|
||||
[_textField addTarget:self action:@selector(textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
|
||||
[_textField addTarget:self action:@selector(textFieldBeginEditing) forControlEvents:UIControlEventEditingDidBegin];
|
||||
[_textField addTarget:self action:@selector(textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd];
|
||||
[_textField addTarget:self action:@selector(textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
|
||||
|
||||
[_textField addObserver:self forKeyPath:@"selectedTextRange" options:0 context:nil];
|
||||
|
||||
[self addSubview:_textField];
|
||||
[self addSubview:_backedTextInput];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -66,14 +50,9 @@
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_textField removeObserver:self forKeyPath:@"selectedTextRange"];
|
||||
}
|
||||
|
||||
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
|
||||
{
|
||||
return _textField;
|
||||
return _backedTextInput;
|
||||
}
|
||||
|
||||
- (void)sendKeyValueForString:(NSString *)string
|
||||
@@ -87,167 +66,59 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setSelection:(RCTTextSelection *)selection
|
||||
{
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
UITextRange *currentSelection = _textField.selectedTextRange;
|
||||
UITextPosition *start = [_textField positionFromPosition:_textField.beginningOfDocument offset:selection.start];
|
||||
UITextPosition *end = [_textField positionFromPosition:_textField.beginningOfDocument offset:selection.end];
|
||||
UITextRange *selectedTextRange = [_textField textRangeFromPosition:start toPosition:end];
|
||||
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
if (eventLag == 0 && ![currentSelection isEqual:selectedTextRange]) {
|
||||
_previousSelectionRange = selectedTextRange;
|
||||
_textField.selectedTextRange = selectedTextRange;
|
||||
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
||||
RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", self.text, eventLag);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)text
|
||||
{
|
||||
return _textField.text;
|
||||
return _backedTextInput.text;
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
if (eventLag == 0 && ![text isEqualToString:self.text]) {
|
||||
UITextRange *selection = _textField.selectedTextRange;
|
||||
NSInteger oldTextLength = _textField.text.length;
|
||||
UITextRange *selection = _backedTextInput.selectedTextRange;
|
||||
NSInteger oldTextLength = _backedTextInput.text.length;
|
||||
|
||||
_textField.text = text;
|
||||
_backedTextInput.text = text;
|
||||
|
||||
if (selection.empty) {
|
||||
// maintain cursor position relative to the end of the old text
|
||||
NSInteger offsetStart = [_textField offsetFromPosition:_textField.beginningOfDocument toPosition:selection.start];
|
||||
NSInteger offsetStart = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument toPosition:selection.start];
|
||||
NSInteger offsetFromEnd = oldTextLength - offsetStart;
|
||||
NSInteger newOffset = text.length - offsetFromEnd;
|
||||
UITextPosition *position = [_textField positionFromPosition:_textField.beginningOfDocument offset:newOffset];
|
||||
_textField.selectedTextRange = [_textField textRangeFromPosition:position toPosition:position];
|
||||
UITextPosition *position = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument offset:newOffset];
|
||||
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:position toPosition:position]
|
||||
notifyDelegate:YES];
|
||||
}
|
||||
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
||||
RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", _textField.text, eventLag);
|
||||
RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", _backedTextInput.text, eventLag);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Events
|
||||
#pragma mark - RCTBackedTextInputDelegate
|
||||
|
||||
- (void)textFieldDidChange
|
||||
{
|
||||
_nativeEventCount++;
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
||||
reactTag:self.reactTag
|
||||
text:_textField.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
|
||||
// selectedTextRange observer isn't triggered when you type even though the
|
||||
// cursor position moves, so we send event again here.
|
||||
[self sendSelectionEvent];
|
||||
}
|
||||
|
||||
- (void)textFieldEndEditing
|
||||
{
|
||||
if (![_finalText isEqualToString:_textField.text]) {
|
||||
_finalText = nil;
|
||||
// iOS does't send event `UIControlEventEditingChanged` if the change was happened because of autocorrection
|
||||
// which was triggered by loosing focus. We assume that if `text` was changed in the middle of loosing focus process,
|
||||
// we did not receive that event. So, we call `textFieldDidChange` manually.
|
||||
[self textFieldDidChange];
|
||||
}
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
||||
reactTag:self.reactTag
|
||||
text:_textField.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
- (void)textFieldSubmitEditing
|
||||
{
|
||||
_submitted = YES;
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
|
||||
reactTag:self.reactTag
|
||||
text:_textField.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
- (void)textFieldBeginEditing
|
||||
{
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||
reactTag:self.reactTag
|
||||
text:_textField.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self->_selectTextOnFocus) {
|
||||
[self->_textField selectAll:nil];
|
||||
}
|
||||
|
||||
[self sendSelectionEvent];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(__unused UITextField *)textField
|
||||
change:(__unused NSDictionary *)change
|
||||
context:(__unused void *)context
|
||||
{
|
||||
if ([keyPath isEqualToString:@"selectedTextRange"]) {
|
||||
[self sendSelectionEvent];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendSelectionEvent
|
||||
{
|
||||
if (_onSelectionChange &&
|
||||
_textField.selectedTextRange != _previousSelectionRange &&
|
||||
![_textField.selectedTextRange isEqual:_previousSelectionRange]) {
|
||||
|
||||
_previousSelectionRange = _textField.selectedTextRange;
|
||||
|
||||
UITextRange *selection = _textField.selectedTextRange;
|
||||
NSInteger start = [_textField offsetFromPosition:[_textField beginningOfDocument] toPosition:selection.start];
|
||||
NSInteger end = [_textField offsetFromPosition:[_textField beginningOfDocument] toPosition:selection.end];
|
||||
_onSelectionChange(@{
|
||||
@"selection": @{
|
||||
@"start": @(start),
|
||||
@"end": @(end),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textField:(RCTTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
|
||||
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string
|
||||
{
|
||||
// Only allow single keypresses for `onKeyPress`, pasted text will not be sent.
|
||||
if (!_textField.textWasPasted) {
|
||||
if (!_backedTextInput.textWasPasted) {
|
||||
[self sendKeyValueForString:string];
|
||||
}
|
||||
|
||||
if (_maxLength != nil && ![string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return.
|
||||
NSUInteger allowedLength = _maxLength.integerValue - MIN(_maxLength.integerValue, _textField.text.length) + range.length;
|
||||
NSUInteger allowedLength = _maxLength.integerValue - MIN(_maxLength.integerValue, _backedTextInput.text.length) + range.length;
|
||||
if (string.length > allowedLength) {
|
||||
if (string.length > 1) {
|
||||
// Truncate the input string so the result is exactly `maxLength`.
|
||||
NSString *limitedString = [string substringToIndex:allowedLength];
|
||||
NSMutableString *newString = _textField.text.mutableCopy;
|
||||
NSMutableString *newString = _backedTextInput.text.mutableCopy;
|
||||
[newString replaceCharactersInRange:range withString:limitedString];
|
||||
_textField.text = newString;
|
||||
_backedTextInput.text = newString;
|
||||
|
||||
// Collapse selection at end of insert to match normal paste behavior.
|
||||
UITextPosition *insertEnd = [_textField positionFromPosition:_textField.beginningOfDocument
|
||||
UITextPosition *insertEnd = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument
|
||||
offset:(range.location + allowedLength)];
|
||||
_textField.selectedTextRange = [_textField textRangeFromPosition:insertEnd toPosition:insertEnd];
|
||||
[self textFieldDidChange];
|
||||
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:insertEnd toPosition:insertEnd]
|
||||
notifyDelegate:YES];
|
||||
[self textInputDidChange];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@@ -256,31 +127,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
return YES;
|
||||
}
|
||||
|
||||
// This method allows us to detect a `Backspace` keyPress
|
||||
// even when there is no more text in the TextField.
|
||||
- (BOOL)keyboardInputShouldDelete:(RCTTextField *)textField
|
||||
- (void)textInputDidChange
|
||||
{
|
||||
[self textField:_textField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField
|
||||
{
|
||||
_finalText = _textField.text;
|
||||
|
||||
if (_submitted) {
|
||||
_submitted = NO;
|
||||
return _blurOnSubmit;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField
|
||||
{
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
|
||||
_nativeEventCount++;
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
||||
reactTag:self.reactTag
|
||||
text:self.text
|
||||
text:_backedTextInput.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
@@ -49,33 +49,33 @@ RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BO
|
||||
RCT_REMAP_VIEW_PROPERTY(selectionColor, backedTextInputView.tintColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(spellCheck, backedTextInputView.spellCheckingType, UITextSpellCheckingType)
|
||||
RCT_REMAP_VIEW_PROPERTY(textAlign, backedTextInputView.textAlignment, NSTextAlignment)
|
||||
RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
|
||||
#pragma mark - Singleline <TextInput> (aka TextField) specific properties
|
||||
|
||||
RCT_REMAP_VIEW_PROPERTY(caretHidden, textField.caretHidden, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
|
||||
RCT_REMAP_VIEW_PROPERTY(clearButtonMode, textField.clearButtonMode, UITextFieldViewMode)
|
||||
RCT_REMAP_VIEW_PROPERTY(clearTextOnFocus, textField.clearsOnBeginEditing, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontSize, NSNumber, RCTTextField)
|
||||
{
|
||||
view.textField.font = [RCTFont updateFont:view.textField.font withSize:json ?: @(defaultView.textField.font.pointSize)];
|
||||
view.backedTextInputView.font = [RCTFont updateFont:view.backedTextInputView.font withSize:json ?: @(defaultView.backedTextInputView.font.pointSize)];
|
||||
}
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, __unused RCTTextField)
|
||||
{
|
||||
view.textField.font = [RCTFont updateFont:view.textField.font withWeight:json]; // defaults to normal
|
||||
view.backedTextInputView.font = [RCTFont updateFont:view.backedTextInputView.font withWeight:json]; // defaults to normal
|
||||
}
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, __unused RCTTextField)
|
||||
{
|
||||
view.textField.font = [RCTFont updateFont:view.textField.font withStyle:json]; // defaults to normal
|
||||
view.backedTextInputView.font = [RCTFont updateFont:view.backedTextInputView.font withStyle:json]; // defaults to normal
|
||||
}
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextField)
|
||||
{
|
||||
view.textField.font = [RCTFont updateFont:view.textField.font withFamily:json ?: defaultView.textField.font.familyName];
|
||||
view.backedTextInputView.font = [RCTFont updateFont:view.backedTextInputView.font withFamily:json ?: defaultView.backedTextInputView.font.familyName];
|
||||
}
|
||||
RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
|
||||
|
||||
|
||||
@@ -15,11 +15,15 @@
|
||||
|
||||
@class RCTBridge;
|
||||
@class RCTEventDispatcher;
|
||||
@class RCTTextSelection;
|
||||
|
||||
@interface RCTTextInput : RCTView {
|
||||
@protected
|
||||
RCTBridge *_bridge;
|
||||
RCTEventDispatcher *_eventDispatcher;
|
||||
NSInteger _nativeEventCount;
|
||||
NSInteger _mostRecentEventCount;
|
||||
BOOL _blurOnSubmit;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
@@ -35,7 +39,24 @@
|
||||
@property (nonatomic, assign, readonly) CGSize contentSize;
|
||||
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;
|
||||
|
||||
@property (nonatomic, assign) NSInteger mostRecentEventCount;
|
||||
@property (nonatomic, assign) BOOL blurOnSubmit;
|
||||
@property (nonatomic, assign) BOOL selectTextOnFocus;
|
||||
@property (nonatomic, assign) BOOL clearTextOnFocus;
|
||||
@property (nonatomic, copy) RCTTextSelection *selection;
|
||||
|
||||
- (void)invalidateContentSize;
|
||||
|
||||
// Temporary exposure of particial `RCTBackedTextInputDelegate` support.
|
||||
// In the future all methods of the protocol should move to this class.
|
||||
- (BOOL)textInputShouldBeginEditing;
|
||||
- (void)textInputDidBeginEditing;
|
||||
- (BOOL)textInputShouldReturn;
|
||||
- (void)textInputDidReturn;
|
||||
- (void)textInputDidChangeSelection;
|
||||
- (BOOL)textInputShouldEndEditing;
|
||||
- (void)textInputDidEndEditing;
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
#import "RCTTextSelection.h"
|
||||
|
||||
@implementation RCTTextInput {
|
||||
CGSize _previousContentSize;
|
||||
}
|
||||
@@ -60,14 +62,116 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (RCTTextSelection *)selection
|
||||
{
|
||||
id<RCTBackedTextInputViewProtocol> backedTextInput = self.backedTextInputView;
|
||||
UITextRange *selectedTextRange = backedTextInput.selectedTextRange;
|
||||
return [[RCTTextSelection new] initWithStart:[backedTextInput offsetFromPosition:backedTextInput.beginningOfDocument toPosition:selectedTextRange.start]
|
||||
end:[backedTextInput offsetFromPosition:backedTextInput.beginningOfDocument toPosition:selectedTextRange.end]];
|
||||
}
|
||||
|
||||
- (void)setSelection:(RCTTextSelection *)selection
|
||||
{
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
id<RCTBackedTextInputViewProtocol> backedTextInput = self.backedTextInputView;
|
||||
|
||||
UITextRange *previousSelectedTextRange = backedTextInput.selectedTextRange;
|
||||
UITextPosition *start = [backedTextInput positionFromPosition:backedTextInput.beginningOfDocument offset:selection.start];
|
||||
UITextPosition *end = [backedTextInput positionFromPosition:backedTextInput.beginningOfDocument offset:selection.end];
|
||||
UITextRange *selectedTextRange = [backedTextInput textRangeFromPosition:start toPosition:end];
|
||||
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
if (eventLag == 0 && ![previousSelectedTextRange isEqual:selectedTextRange]) {
|
||||
[backedTextInput setSelectedTextRange:selectedTextRange notifyDelegate:NO];
|
||||
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
||||
RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", backedTextInput.text, eventLag);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RCTBackedTextInputDelegate
|
||||
|
||||
- (BOOL)textInputShouldBeginEditing
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textInputDidBeginEditing
|
||||
{
|
||||
if (_clearTextOnFocus) {
|
||||
self.backedTextInputView.text = @"";
|
||||
}
|
||||
|
||||
if (_selectTextOnFocus) {
|
||||
[self.backedTextInputView selectAll:nil];
|
||||
}
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||
reactTag:self.reactTag
|
||||
text:self.backedTextInputView.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
- (BOOL)textInputShouldReturn
|
||||
{
|
||||
return _blurOnSubmit;
|
||||
}
|
||||
|
||||
- (void)textInputDidReturn
|
||||
{
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
|
||||
reactTag:self.reactTag
|
||||
text:self.backedTextInputView.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
- (void)textInputDidChangeSelection
|
||||
{
|
||||
if (!_onSelectionChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCTTextSelection *selection = self.selection;
|
||||
_onSelectionChange(@{
|
||||
@"selection": @{
|
||||
@"start": @(selection.start),
|
||||
@"end": @(selection.end),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)textInputShouldEndEditing
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textInputDidEndEditing
|
||||
{
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
||||
reactTag:self.reactTag
|
||||
text:self.backedTextInputView.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
|
||||
reactTag:self.reactTag
|
||||
text:self.backedTextInputView.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
#pragma mark - Content Size (in Yoga terms, without any insets)
|
||||
|
||||
- (CGSize)contentSize
|
||||
{
|
||||
CGSize contentSize = self.intrinsicContentSize;
|
||||
UIEdgeInsets compoundInsets = self.reactCompoundInsets;
|
||||
contentSize.width -= compoundInsets.left + compoundInsets.right;
|
||||
contentSize.height -= compoundInsets.top + compoundInsets.bottom;
|
||||
CGSize contentSize = self.backedTextInputView.contentSize;
|
||||
UIEdgeInsets reactPaddingInsets = self.reactPaddingInsets;
|
||||
contentSize.width -= reactPaddingInsets.left + reactPaddingInsets.right;
|
||||
contentSize.height -= reactPaddingInsets.top + reactPaddingInsets.bottom;
|
||||
// Returning value does NOT include border and padding insets.
|
||||
return contentSize;
|
||||
}
|
||||
@@ -133,7 +237,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
- (UIView *)reactAccessibleView
|
||||
- (UIView *)reactAccessibilityElement
|
||||
{
|
||||
return self.backedTextInputView;
|
||||
}
|
||||
|
||||
@@ -16,23 +16,18 @@
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
@interface RCTTextView : RCTTextInput <UITextViewDelegate>
|
||||
@interface RCTTextView : RCTTextInput
|
||||
|
||||
@property (nonatomic, assign) UITextAutocorrectionType autocorrectionType;
|
||||
@property (nonatomic, assign) UITextSpellCheckingType spellCheckingType;
|
||||
@property (nonatomic, assign) BOOL blurOnSubmit;
|
||||
@property (nonatomic, assign) BOOL clearTextOnFocus;
|
||||
@property (nonatomic, assign) BOOL selectTextOnFocus;
|
||||
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
||||
@property (nonatomic, copy) NSString *text;
|
||||
@property (nonatomic, strong) UIColor *placeholderTextColor;
|
||||
@property (nonatomic, copy) NSString *placeholder;
|
||||
@property (nonatomic, strong) UIFont *font;
|
||||
@property (nonatomic, assign) NSInteger mostRecentEventCount;
|
||||
@property (nonatomic, strong) NSNumber *maxLength;
|
||||
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onChange;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onTextInput;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onScroll;
|
||||
|
||||
|
||||
+59
-157
@@ -20,18 +20,20 @@
|
||||
#import "RCTTextSelection.h"
|
||||
#import "RCTUITextView.h"
|
||||
|
||||
@interface RCTTextView () <RCTBackedTextInputDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTTextView
|
||||
{
|
||||
RCTUITextView *_textView;
|
||||
RCTUITextView *_backedTextInput;
|
||||
RCTText *_richTextView;
|
||||
NSAttributedString *_pendingAttributedText;
|
||||
|
||||
UITextRange *_previousSelectionRange;
|
||||
NSString *_predictedText;
|
||||
|
||||
BOOL _blockTextShouldChange;
|
||||
BOOL _nativeUpdatesInFlight;
|
||||
NSInteger _nativeEventCount;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
@@ -39,21 +41,23 @@
|
||||
RCTAssertParam(bridge);
|
||||
|
||||
if (self = [super initWithBridge:bridge]) {
|
||||
// `blurOnSubmit` defaults to `false` for <TextInput multiline={true}> by design.
|
||||
_blurOnSubmit = NO;
|
||||
|
||||
_textView = [[RCTUITextView alloc] initWithFrame:self.bounds];
|
||||
_textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.textColor = [UIColor blackColor];
|
||||
_backedTextInput = [[RCTUITextView alloc] initWithFrame:self.bounds];
|
||||
_backedTextInput.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_backedTextInput.backgroundColor = [UIColor clearColor];
|
||||
_backedTextInput.textColor = [UIColor blackColor];
|
||||
// This line actually removes 5pt (default value) left and right padding in UITextView.
|
||||
_textView.textContainer.lineFragmentPadding = 0;
|
||||
_backedTextInput.textContainer.lineFragmentPadding = 0;
|
||||
#if !TARGET_OS_TV
|
||||
_textView.scrollsToTop = NO;
|
||||
_backedTextInput.scrollsToTop = NO;
|
||||
#endif
|
||||
_textView.scrollEnabled = YES;
|
||||
_textView.delegate = self;
|
||||
_backedTextInput.scrollEnabled = YES;
|
||||
|
||||
[self addSubview:_textView];
|
||||
_backedTextInput.textInputDelegate = self;
|
||||
|
||||
[self addSubview:_backedTextInput];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -63,7 +67,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
|
||||
{
|
||||
return _textView;
|
||||
return _backedTextInput;
|
||||
}
|
||||
|
||||
#pragma mark - RCTComponent
|
||||
@@ -85,9 +89,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
//
|
||||
// TODO: This should be removed when the related hack in -performPendingTextUpdate is removed.
|
||||
if (subview.backgroundColor) {
|
||||
NSMutableDictionary<NSString *, id> *attrs = [_textView.typingAttributes mutableCopy];
|
||||
NSMutableDictionary<NSString *, id> *attrs = [_backedTextInput.typingAttributes mutableCopy];
|
||||
attrs[NSBackgroundColorAttributeName] = subview.backgroundColor;
|
||||
_textView.typingAttributes = attrs;
|
||||
_backedTextInput.typingAttributes = attrs;
|
||||
}
|
||||
|
||||
[self performTextUpdate];
|
||||
@@ -128,7 +132,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
_pendingAttributedText = _richTextView.textStorage;
|
||||
[self performPendingTextUpdate];
|
||||
} else if (!self.text) {
|
||||
_textView.attributedText = nil;
|
||||
_backedTextInput.attributedText = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +161,7 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
|
||||
// TODO: Kill this after we finish passing all style/attribute info into JS.
|
||||
_pendingAttributedText = removeReactTagFromString(_pendingAttributedText);
|
||||
|
||||
if ([_textView.attributedText isEqualToAttributedString:_pendingAttributedText]) {
|
||||
if ([_backedTextInput.attributedText isEqualToAttributedString:_pendingAttributedText]) {
|
||||
_pendingAttributedText = nil; // Don't try again.
|
||||
return;
|
||||
}
|
||||
@@ -167,23 +171,24 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
|
||||
// we temporarily block all textShouldChange events so they are not applied.
|
||||
_blockTextShouldChange = YES;
|
||||
|
||||
UITextRange *selection = _textView.selectedTextRange;
|
||||
NSInteger oldTextLength = _textView.attributedText.length;
|
||||
UITextRange *selection = _backedTextInput.selectedTextRange;
|
||||
NSInteger oldTextLength = _backedTextInput.attributedText.length;
|
||||
|
||||
_textView.attributedText = _pendingAttributedText;
|
||||
_backedTextInput.attributedText = _pendingAttributedText;
|
||||
_predictedText = _pendingAttributedText.string;
|
||||
_pendingAttributedText = nil;
|
||||
|
||||
if (selection.empty) {
|
||||
// maintain cursor position relative to the end of the old text
|
||||
NSInteger start = [_textView offsetFromPosition:_textView.beginningOfDocument toPosition:selection.start];
|
||||
NSInteger start = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument toPosition:selection.start];
|
||||
NSInteger offsetFromEnd = oldTextLength - start;
|
||||
NSInteger newOffset = _textView.attributedText.length - offsetFromEnd;
|
||||
UITextPosition *position = [_textView positionFromPosition:_textView.beginningOfDocument offset:newOffset];
|
||||
_textView.selectedTextRange = [_textView textRangeFromPosition:position toPosition:position];
|
||||
NSInteger newOffset = _backedTextInput.attributedText.length - offsetFromEnd;
|
||||
UITextPosition *position = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument offset:newOffset];
|
||||
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:position toPosition:position]
|
||||
notifyDelegate:YES];
|
||||
}
|
||||
|
||||
[_textView layoutIfNeeded];
|
||||
[_backedTextInput layoutIfNeeded];
|
||||
|
||||
[self invalidateContentSize];
|
||||
|
||||
@@ -194,57 +199,38 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
|
||||
|
||||
- (UIFont *)font
|
||||
{
|
||||
return _textView.font;
|
||||
return _backedTextInput.font;
|
||||
}
|
||||
|
||||
- (void)setFont:(UIFont *)font
|
||||
{
|
||||
_textView.font = font;
|
||||
_backedTextInput.font = font;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setSelection:(RCTTextSelection *)selection
|
||||
{
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
UITextRange *currentSelection = _textView.selectedTextRange;
|
||||
UITextPosition *start = [_textView positionFromPosition:_textView.beginningOfDocument offset:selection.start];
|
||||
UITextPosition *end = [_textView positionFromPosition:_textView.beginningOfDocument offset:selection.end];
|
||||
UITextRange *selectedTextRange = [_textView textRangeFromPosition:start toPosition:end];
|
||||
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
if (eventLag == 0 && ![currentSelection isEqual:selectedTextRange]) {
|
||||
_previousSelectionRange = selectedTextRange;
|
||||
_textView.selectedTextRange = selectedTextRange;
|
||||
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
||||
RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", self.text, eventLag);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)text
|
||||
{
|
||||
return _textView.text;
|
||||
return _backedTextInput.text;
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
if (eventLag == 0 && ![text isEqualToString:_textView.text]) {
|
||||
UITextRange *selection = _textView.selectedTextRange;
|
||||
NSInteger oldTextLength = _textView.text.length;
|
||||
if (eventLag == 0 && ![text isEqualToString:_backedTextInput.text]) {
|
||||
UITextRange *selection = _backedTextInput.selectedTextRange;
|
||||
NSInteger oldTextLength = _backedTextInput.text.length;
|
||||
|
||||
_predictedText = text;
|
||||
_textView.text = text;
|
||||
_backedTextInput.text = text;
|
||||
|
||||
if (selection.empty) {
|
||||
// maintain cursor position relative to the end of the old text
|
||||
NSInteger start = [_textView offsetFromPosition:_textView.beginningOfDocument toPosition:selection.start];
|
||||
NSInteger start = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument toPosition:selection.start];
|
||||
NSInteger offsetFromEnd = oldTextLength - start;
|
||||
NSInteger newOffset = text.length - offsetFromEnd;
|
||||
UITextPosition *position = [_textView positionFromPosition:_textView.beginningOfDocument offset:newOffset];
|
||||
_textView.selectedTextRange = [_textView textRangeFromPosition:position toPosition:position];
|
||||
UITextPosition *position = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument offset:newOffset];
|
||||
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:position toPosition:position]
|
||||
notifyDelegate:YES];
|
||||
}
|
||||
|
||||
[self invalidateContentSize];
|
||||
@@ -253,37 +239,16 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UITextViewDelegate
|
||||
#pragma mark - RCTBackedTextInputDelegate
|
||||
|
||||
- (BOOL)textView:(RCTUITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
||||
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
||||
{
|
||||
if (!textView.textWasPasted) {
|
||||
if (!_backedTextInput.textWasPasted) {
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
|
||||
reactTag:self.reactTag
|
||||
text:nil
|
||||
key:text
|
||||
eventCount:_nativeEventCount];
|
||||
|
||||
if (_blurOnSubmit && [text isEqualToString:@"\n"]) {
|
||||
// TODO: the purpose of blurOnSubmit on RCTextField is to decide if the
|
||||
// field should lose focus when return is pressed or not. We're cheating a
|
||||
// bit here by using it on RCTextView to decide if return character should
|
||||
// submit the form, or be entered into the field.
|
||||
//
|
||||
// The reason this is cheating is because there's no way to specify that
|
||||
// you want the return key to be swallowed *and* have the field retain
|
||||
// focus (which was what blurOnSubmit was originally for). For the case
|
||||
// where _blurOnSubmit = YES, this is still the correct and expected
|
||||
// behavior though, so we'll leave the don't-blur-or-add-newline problem
|
||||
// to be solved another day.
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
|
||||
reactTag:self.reactTag
|
||||
text:self.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
[_textView resignFirstResponder];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// So we need to track that there is a native update in flight just in case JS manages to come back around and update
|
||||
@@ -294,23 +259,24 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
|
||||
}
|
||||
|
||||
if (_maxLength) {
|
||||
NSUInteger allowedLength = _maxLength.integerValue - textView.text.length + range.length;
|
||||
NSUInteger allowedLength = _maxLength.integerValue - _backedTextInput.text.length + range.length;
|
||||
if (text.length > allowedLength) {
|
||||
// If we typed/pasted more than one character, limit the text inputted
|
||||
if (text.length > 1) {
|
||||
// Truncate the input string so the result is exactly maxLength
|
||||
NSString *limitedString = [text substringToIndex:allowedLength];
|
||||
NSMutableString *newString = textView.text.mutableCopy;
|
||||
NSMutableString *newString = _backedTextInput.text.mutableCopy;
|
||||
[newString replaceCharactersInRange:range withString:limitedString];
|
||||
textView.text = newString;
|
||||
_backedTextInput.text = newString;
|
||||
_predictedText = newString;
|
||||
|
||||
// Collapse selection at end of insert to match normal paste behavior
|
||||
UITextPosition *insertEnd = [textView positionFromPosition:textView.beginningOfDocument
|
||||
UITextPosition *insertEnd = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument
|
||||
offset:(range.location + allowedLength)];
|
||||
textView.selectedTextRange = [textView textRangeFromPosition:insertEnd toPosition:insertEnd];
|
||||
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:insertEnd toPosition:insertEnd]
|
||||
notifyDelegate:YES];
|
||||
|
||||
[self textViewDidChange:textView];
|
||||
[self textInputDidChange];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@@ -321,7 +287,7 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
|
||||
if (range.location + range.length > _predictedText.length) {
|
||||
// _predictedText got out of sync in a bad way, so let's just force sync it. Haven't been able to repro this, but
|
||||
// it's causing a real crash here: #6523822
|
||||
_predictedText = textView.text;
|
||||
_predictedText = _backedTextInput.text;
|
||||
}
|
||||
|
||||
NSString *previousText = [_predictedText substringWithRange:range];
|
||||
@@ -346,49 +312,6 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textViewDidChangeSelection:(RCTUITextView *)textView
|
||||
{
|
||||
if (_onSelectionChange &&
|
||||
textView.selectedTextRange != _previousSelectionRange &&
|
||||
![textView.selectedTextRange isEqual:_previousSelectionRange]) {
|
||||
|
||||
_previousSelectionRange = textView.selectedTextRange;
|
||||
|
||||
UITextRange *selection = textView.selectedTextRange;
|
||||
NSInteger start = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selection.start];
|
||||
NSInteger end = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selection.end];
|
||||
_onSelectionChange(@{
|
||||
@"selection": @{
|
||||
@"start": @(start),
|
||||
@"end": @(end),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
|
||||
{
|
||||
if (_selectTextOnFocus) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[textView selectAll:nil];
|
||||
});
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textViewDidBeginEditing:(UITextView *)textView
|
||||
{
|
||||
if (_clearTextOnFocus) {
|
||||
_textView.text = @"";
|
||||
}
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||
reactTag:self.reactTag
|
||||
text:nil
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, NSRange *secondRange)
|
||||
{
|
||||
NSInteger firstMismatch = -1;
|
||||
@@ -418,22 +341,22 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange,
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)textView
|
||||
- (void)textInputDidChange
|
||||
{
|
||||
[self invalidateContentSize];
|
||||
|
||||
// Detect when textView updates happend that didn't invoke `shouldChangeTextInRange`
|
||||
// Detect when _backedTextInput updates happend that didn't invoke `shouldChangeTextInRange`
|
||||
// (e.g. typing simplified chinese in pinyin will insert and remove spaces without
|
||||
// calling shouldChangeTextInRange). This will cause JS to get out of sync so we
|
||||
// update the mismatched range.
|
||||
NSRange currentRange;
|
||||
NSRange predictionRange;
|
||||
if (findMismatch(textView.text, _predictedText, ¤tRange, &predictionRange)) {
|
||||
NSString *replacement = [textView.text substringWithRange:currentRange];
|
||||
[self textView:textView shouldChangeTextInRange:predictionRange replacementText:replacement];
|
||||
if (findMismatch(_backedTextInput.text, _predictedText, ¤tRange, &predictionRange)) {
|
||||
NSString *replacement = [_backedTextInput.text substringWithRange:currentRange];
|
||||
[self textInputShouldChangeTextInRange:predictionRange replacementText:replacement];
|
||||
// JS will assume the selection changed based on the location of our shouldChangeTextInRange, so reset it.
|
||||
[self textViewDidChangeSelection:textView];
|
||||
_predictedText = textView.text;
|
||||
[self textInputDidChangeSelection];
|
||||
_predictedText = _backedTextInput.text;
|
||||
}
|
||||
|
||||
_nativeUpdatesInFlight = NO;
|
||||
@@ -450,27 +373,6 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange,
|
||||
});
|
||||
}
|
||||
|
||||
- (void)textViewDidEndEditing:(UITextView *)textView
|
||||
{
|
||||
if (_nativeUpdatesInFlight) {
|
||||
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
|
||||
// which was triggered by loosing focus. So, we call it manually.
|
||||
[self textViewDidChange:textView];
|
||||
}
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
||||
reactTag:self.reactTag
|
||||
text:textView.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
|
||||
reactTag:self.reactTag
|
||||
text:nil
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
|
||||
@@ -49,20 +49,21 @@ RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BO
|
||||
RCT_REMAP_VIEW_PROPERTY(selectionColor, backedTextInputView.tintColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(spellCheck, backedTextInputView.spellCheckingType, UITextSpellCheckingType)
|
||||
RCT_REMAP_VIEW_PROPERTY(textAlign, backedTextInputView.textAlignment, NSTextAlignment)
|
||||
|
||||
#pragma mark - Multiline <TextInput> (aka TextView) specific properties
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
|
||||
#pragma mark - Multiline <TextInput> (aka TextView) specific properties
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTBubblingEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontSize, NSNumber, RCTTextView)
|
||||
{
|
||||
view.font = [RCTFont updateFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
|
||||
@@ -82,7 +83,7 @@ RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextView)
|
||||
RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, textView.dataDetectorTypes, UIDataDetectorTypes)
|
||||
RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes)
|
||||
#endif
|
||||
|
||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
|
||||
|
||||
@@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
|
||||
@property (nonatomic, weak) id<RCTBackedTextInputDelegate> textInputDelegate;
|
||||
|
||||
@property (nonatomic, assign) BOOL caretHidden;
|
||||
@property (nonatomic, assign, readonly) BOOL textWasPasted;
|
||||
@property (nonatomic, strong, nullable) UIColor *placeholderColor;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user