Skip to content

Fix overflow:'hidden' github issue #399 (attempt 2)#457

Merged
tom-un merged 3 commits intomicrosoft:masterfrom
sahrens:fixOverflowHidden
Jun 25, 2020
Merged

Fix overflow:'hidden' github issue #399 (attempt 2)#457
tom-un merged 3 commits intomicrosoft:masterfrom
sahrens:fixOverflowHidden

Conversation

@sahrens
Copy link
Copy Markdown

@sahrens sahrens commented Jun 18, 2020

Please select one of the following

  • I am removing an existing difference between facebook/react-native and microsoft/react-native-macos 👍
  • I am cherry-picking a change from Facebook's react-native into microsoft/react-native-macos 👍
  • I am making a fix / change for the macOS implementation of react-native
  • I am making a change required for Microsoft usage of react-native

Summary

Fixes overflow:'hidden' #399 by settinglayer.masksToBounds = self.clipsToBounds; in the displayLayer: method

overflow: 'hidden' doesn't work in macOS, but it does work in iOS and Android (not sure about Windows). This change brings functional parity with minimal complexity/conflict.

This also fixes borderRadius on <Image> which broke recently because the implementation of RCTImage changes from subclass to composition with the image view being a subview and thus now relies on functional clipping behavior.

Note we don't mutate self.layer.masksToBounds inside setClipsToBounds because modifying self.layer is undefined behavior on macOS and we have seen issues (e.g. with transforms) where the backing layer gets replaced by the system and mutations are not transferred over.

Changelog

[MacOS] [Fixed] - Fix overflow:'hidden' #399

Test Plan

Before After
Screen Shot 2020-05-26 at 1 15 51 PM Screen Shot 2020-05-26 at 1 15 30 PM
Screen Shot 2020-05-26 at 12 01 18 PM Screen Shot 2020-05-26 at 1 15 16 PM
Microsoft Reviewers: Open in CodeFlow
Microsoft Reviewers: Open in CodeFlow

@sahrens sahrens requested a review from tom-un as a code owner June 18, 2020 01:20
@sahrens
Copy link
Copy Markdown
Author

sahrens commented Jun 18, 2020

See #419 for more context on approach.

@sahrens
Copy link
Copy Markdown
Author

sahrens commented Jun 18, 2020

Looks like the test is still flaky:

Failing tests:
RNTester-macOSIntegrationTests:
-[RNTesterLoadAllPages ScrollViewExample]

This task has failed 8 times across 36 pipeline runs in the last 14 days.

Copy link
Copy Markdown
Collaborator

@tom-un tom-un left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

#if TARGET_OS_OSX // [TODO(macOS ISS#2323203)
// clipsToBounds is stubbed out on macOS because it's not part of NSView
self.layer.masksToBounds = self.clipsToBounds;
#endif // [TODO(macOS ISS#2323203)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: // [TODO(macOS ISS#2323203)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the nit is - can you add a code "suggestion"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant please change the comment to // ]TODO(macOS ISS#2323203 (left bracket instead of right)

@markavitale
Copy link
Copy Markdown

markavitale commented Jun 18, 2020

Isn't this relying on the exact same undefined behavior as #419 was?

macOS 10.13 is more consistent around updates to properties of views' layers (that is, layers that are the .layer of some NSView, however created). As before, if an application modifies such a layer by changing any of the following properties, the behavior of the application is undefined: bounds, position, zPosition, anchorPoint, anchorPointZ, transform, affineTransform, frame, hidden, geometryFlipped, masksToBounds, opaque, compositingFilter, filters, shadowColor, shadowOpacity, shadowOffset, shadowRadius, shadowPath, layoutManager.

https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKit/#10_13Layer-backed%20Views

It seems to me we're still modifying the masksToBounds property on a layer that is the .layer of an NSView. Note that how the layer is created doesn't matter according to Apple, so even making our own layer and assigning it to the view wouldn't be safe.

@markavitale
Copy link
Copy Markdown

The concern is that we would then be explicitly relying on undefined behavior that has been explicitly called out as undefined for years. Since we at this moment can't find a supported way to achieve the behavior we want, if Apple changes that undefined behavior as they are totally justified in doing, we have no workarounds or ideas. We would have just broken clients with no way to fix them if and when Apple changes this undefined behavior.

That's why I don't think taking this change is the right approach

@sahrens
Copy link
Copy Markdown
Author

sahrens commented Jun 18, 2020

Isn't this relying on the exact same undefined behavior as #419 was?

This approach is practically different in two major ways. First, it modifies the layer provided by displayLayer: rather than accessing self.layer or view.layer, and it does it every time, making no assumption that the state will be preserved indefinitely - this was necessary to get transforms to work (in a separate diff) because the layer does get recreated by the system and not all properties are preserved, so this technique avoids that potential class of bug that definitely happens with transforms. Second, we are already knee deep in this approach for a bunch of border rendering and other logic, so we are at least self consistent. I believe @tom-un is planning on scoping out a separate project to make all of this done "the right way" whatever that ends up being.

@markavitale
Copy link
Copy Markdown

This approach is practically different in two major ways. First, it modifies the layer provided by displayLayer: rather than accessing self.layer or view.layer, and it does it every time, making no assumption that the state will be preserved indefinitely - this was necessary to get transforms to work (in a separate diff) because the layer does get recreated by the system and not all properties are preserved, so this technique avoids that potential class of bug that definitely happens with transforms.

The code in this review does seem to explicitly call self.layer from RCTView.

Second, we are already knee deep in this approach for a bunch of border rendering and other logic, so we are at least self consistent. I believe @tom-un is planning on scoping out a separate project to make all of this done "the right way" whatever that ends up being.

I had not been aware of these other spots in the project where we're explicitly relying on undefined behavior, but those are definitely of concern. I'm concerned this debt will continue to be pushed off until the day Apple releases an OS version that changes this undefined behavior, at which point we will be unable to support existing behaviors on new OSs, breaking existing clients and apps.

@sahrens
Copy link
Copy Markdown
Author

sahrens commented Jun 18, 2020

The code in this review does seem to explicitly call self.layer from RCTView.

lol, that's a typo! Let me fix that...

@anandrajeswaran
Copy link
Copy Markdown

The code in this review does seem to explicitly call self.layer from RCTView.

lol, that's a typo! Let me fix that...

But how is that different? It's still the same layer that Apple has documented that we shouldn't mess with, whether we're grabbing it from our ivar or using it from the layer drawing callback? Or is it a different layer and I'm missing the subtlety?

@tom-un
Copy link
Copy Markdown
Collaborator

tom-un commented Jun 19, 2020

The code in this review does seem to explicitly call self.layer from RCTView.

lol, that's a typo! Let me fix that...

But how is that different? It's still the same layer that Apple has documented that we shouldn't mess with, whether we're grabbing it from our ivar or using it from the layer drawing callback? Or is it a different layer and I'm missing the subtlety?

I debugged it and the layer passed in to displayLayer: is the same as the self.layer.

As @sahrens has pointed out, there are several places in react-native-macos where we are modifying a layer backed view. The current implementation relies on several layer prop changes for several features: z-order, rotation, shadows, and clipping. All of these features are critical for react-native platform parity.

I propose that I open a new issue to track a larger refactoring of all these undefined behavior layer changes and annotate the points where we violate it, including this new contribution. I opened this issue: #466 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants