react-native-windows 0.0.0-canary.575 → 0.0.0-canary.577

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -50,6 +50,7 @@ struct INativeUIManager {
50
50
  virtual void UpdateView(ShadowNode &shadowNode, winrt::Microsoft::ReactNative::JSValueObject &props) = 0;
51
51
  virtual void onBatchComplete() = 0;
52
52
  virtual void ensureInBatch() = 0;
53
+ virtual bool isInBatch() = 0;
53
54
  virtual void measure(
54
55
  ShadowNode &shadowNode,
55
56
  ShadowNode &shadowRoot,
@@ -0,0 +1,51 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ #include "pch.h"
5
+ #include "LayoutService.h"
6
+ #include "LayoutService.g.cpp"
7
+ #include <Modules/NativeUIManager.h>
8
+ #include <Modules/PaperUIManagerModule.h>
9
+
10
+ namespace winrt::Microsoft::ReactNative::implementation {
11
+
12
+ LayoutService::LayoutService(Mso::CntPtr<Mso::React::IReactContext> &&context) noexcept : m_context(context) {}
13
+
14
+ /*static*/ winrt::Microsoft::ReactNative::LayoutService LayoutService::FromContext(IReactContext context) {
15
+ return context.Properties()
16
+ .Get(LayoutService::LayoutServiceProperty().Handle())
17
+ .try_as<winrt::Microsoft::ReactNative::LayoutService>();
18
+ }
19
+
20
+ /*static*/ ReactPropertyId<LayoutService> LayoutService::LayoutServiceProperty() noexcept {
21
+ static ReactPropertyId<LayoutService> layoutServiceProperty{L"ReactNative.UIManager", L"LayoutService"};
22
+ return layoutServiceProperty;
23
+ }
24
+
25
+ void LayoutService::ApplyLayoutForAllNodes() noexcept {
26
+ if (auto uiManager = ::Microsoft::ReactNative::GetNativeUIManager(*m_context).lock()) {
27
+ uiManager->DoLayout();
28
+ }
29
+ }
30
+
31
+ void LayoutService::ApplyLayout(int64_t reactTag, float width, float height) noexcept {
32
+ if (auto uiManager = ::Microsoft::ReactNative::GetNativeUIManager(*m_context).lock()) {
33
+ uiManager->ApplyLayout(reactTag, width, height);
34
+ }
35
+ }
36
+
37
+ bool LayoutService::IsInBatch() noexcept {
38
+ if (auto uiManager = ::Microsoft::ReactNative::GetNativeUIManager(*m_context).lock()) {
39
+ return uiManager->isInBatch();
40
+ }
41
+
42
+ return false;
43
+ }
44
+
45
+ void LayoutService::MarkDirty(int64_t reactTag) noexcept {
46
+ if (auto uiManager = ::Microsoft::ReactNative::GetNativeUIManager(*m_context).lock()) {
47
+ uiManager->DirtyYogaNode(reactTag);
48
+ }
49
+ }
50
+
51
+ } // namespace winrt::Microsoft::ReactNative::implementation
@@ -0,0 +1,32 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ #pragma once
5
+
6
+ #include "LayoutService.g.h"
7
+ #include "INativeUIManager.h"
8
+ #include "ReactHost/React.h"
9
+ #include "ReactPropertyBag.h"
10
+ #include "winrt/Microsoft.ReactNative.h"
11
+
12
+ namespace winrt::Microsoft::ReactNative::implementation {
13
+ struct LayoutService : LayoutServiceT<LayoutService> {
14
+ public:
15
+ LayoutService(Mso::CntPtr<Mso::React::IReactContext> &&context) noexcept;
16
+ static winrt::Microsoft::ReactNative::LayoutService FromContext(IReactContext context);
17
+ static ReactPropertyId<LayoutService> LayoutServiceProperty() noexcept;
18
+
19
+ void ApplyLayoutForAllNodes() noexcept;
20
+ void ApplyLayout(int64_t reactTag, float width, float height) noexcept;
21
+ bool IsInBatch() noexcept;
22
+ void MarkDirty(int64_t reactTag) noexcept;
23
+
24
+ private:
25
+ Mso::CntPtr<Mso::React::IReactContext> m_context;
26
+ };
27
+
28
+ } // namespace winrt::Microsoft::ReactNative::implementation
29
+
30
+ namespace winrt::Microsoft::ReactNative::factory_implementation {
31
+ struct LayoutService : LayoutServiceT<LayoutService, implementation::LayoutService> {};
32
+ } // namespace winrt::Microsoft::ReactNative::factory_implementation
@@ -0,0 +1,41 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ import "IReactContext.idl";
5
+
6
+ #include "NamespaceRedirect.h"
7
+ #include "DocString.h"
8
+
9
+ namespace Microsoft.ReactNative
10
+ {
11
+ [default_interface]
12
+ [webhosthidden]
13
+ DOC_STRING("Provides access to Yoga layout functionality.")
14
+ runtimeclass LayoutService {
15
+ DOC_STRING("Use this method to get access to the @LayoutService associated with the @IReactContext.")
16
+ static LayoutService FromContext(IReactContext context);
17
+
18
+ DOC_STRING(
19
+ "Recursively applies layout to all root nodes. This method will trigger "
20
+ "a Yoga layout operation on roots attached to the React instance and "
21
+ "apply the layout results to all descendant nodes.")
22
+ void ApplyLayoutForAllNodes();
23
+
24
+ DOC_STRING(
25
+ "Recursively applies layout from the given React node with the supplied "
26
+ "size constraints. This method will trigger a Yoga layout operation on the "
27
+ "given node and its descendants and apply the layout results to these nodes.")
28
+ void ApplyLayout(Int64 reactTag, Single width, Single height);
29
+
30
+ DOC_STRING(
31
+ "Determines whether the UIManager is currently processing a batch of node updates."
32
+ "This is useful for optimizing layout and ensuring that applying layout to a particular "
33
+ "node will not cause tearing in the rendered UI.")
34
+ Boolean IsInBatch {
35
+ get;
36
+ };
37
+
38
+ DOC_STRING("Mark a particular React node as dirty for Yoga layout.")
39
+ void MarkDirty(Int64 reactTag);
40
+ }
41
+ } // namespace ReactNative
@@ -380,6 +380,10 @@
380
380
  <ClInclude Include="Views\VirtualTextViewManager.h" />
381
381
  <ClInclude Include="Views\XamlFeatures.h" />
382
382
  <ClInclude Include="XamlLoadState.h" />
383
+ <ClInclude Include="LayoutService.h">
384
+ <DependentUpon>LayoutService.idl</DependentUpon>
385
+ <SubType>Code</SubType>
386
+ </ClInclude>
383
387
  <ClInclude Include="XamlUIService.h">
384
388
  <DependentUpon>XamlUIService.idl</DependentUpon>
385
389
  <SubType>Code</SubType>
@@ -598,6 +602,10 @@
598
602
  <ClCompile Include="Views\XamlFeatures.cpp" />
599
603
  <ClCompile Include="XamlLoadState.cpp" />
600
604
  <ClCompile Include="XamlView.cpp" />
605
+ <ClCompile Include="LayoutService.cpp">
606
+ <DependentUpon>LayoutService.idl</DependentUpon>
607
+ <SubType>Code</SubType>
608
+ </ClCompile>
601
609
  <ClCompile Include="XamlUIService.cpp">
602
610
  <DependentUpon>XamlUIService.idl</DependentUpon>
603
611
  <SubType>Code</SubType>
@@ -674,6 +682,9 @@
674
682
  <Midl Include="Views\cppwinrt\Effects.idl" />
675
683
  <Midl Include="Views\cppwinrt\DynamicAutomationPeer.idl" />
676
684
  <Midl Include="Views\cppwinrt\ViewPanel.idl" />
685
+ <Midl Include="LayoutService.idl">
686
+ <SubType>Designer</SubType>
687
+ </Midl>
677
688
  <Midl Include="XamlUIService.idl">
678
689
  <SubType>Designer</SubType>
679
690
  </Midl>
@@ -781,4 +792,4 @@
781
792
  </ClCompile>
782
793
  </ItemGroup>
783
794
  </Target>
784
- </Project>
795
+ </Project>
@@ -752,6 +752,7 @@
752
752
  <Midl Include="RedBoxHandler.idl" />
753
753
  <Midl Include="QuirkSettings.idl" />
754
754
  <Midl Include="XamlHelper.idl" />
755
+ <Midl Include="LayoutService.idl" />
755
756
  <Midl Include="XamlUIService.idl" />
756
757
  <Midl Include="Views\cppwinrt\AccessibilityAction.idl">
757
758
  <Filter>Views\cppwinrt</Filter>
@@ -827,4 +828,4 @@
827
828
  <Page Include="DevMenuControl.xaml" />
828
829
  <Page Include="CoreAppPage.xaml" />
829
830
  </ItemGroup>
830
- </Project>
831
+ </Project>
@@ -260,6 +260,10 @@ void NativeUIManager::ensureInBatch() {
260
260
  m_inBatch = true;
261
261
  }
262
262
 
263
+ bool NativeUIManager::isInBatch() {
264
+ return m_inBatch;
265
+ }
266
+
263
267
  static float NumberOrDefault(const winrt::Microsoft::ReactNative::JSValue &value, float defaultValue) {
264
268
  float result = defaultValue;
265
269
 
@@ -884,28 +888,28 @@ void NativeUIManager::DoLayout() {
884
888
  auto &rootTags = m_host->GetAllRootTags();
885
889
  for (int64_t rootTag : rootTags) {
886
890
  ShadowNodeBase &rootShadowNode = static_cast<ShadowNodeBase &>(m_host->GetShadowNodeForTag(rootTag));
887
- if (YGNodeRef rootNode = GetYogaNode(rootTag)) {
888
- auto rootElement = rootShadowNode.GetView().as<xaml::FrameworkElement>();
889
-
890
- float actualWidth = static_cast<float>(rootElement.ActualWidth());
891
- float actualHeight = static_cast<float>(rootElement.ActualHeight());
892
-
893
- {
894
- SystraceSection s("NativeUIManager::DoLayout::YGNodeCalculateLayout");
895
- // We must always run layout in LTR mode, which might seem unintuitive.
896
- // We will flip the root of the tree into RTL by forcing the root XAML node's FlowDirection to RightToLeft
897
- // which will inherit down the XAML tree, allowing all native controls to pick it up.
898
- YGNodeCalculateLayout(rootNode, actualWidth, actualHeight, YGDirectionLTR);
899
- }
900
- } else {
901
- assert(false);
902
- return;
903
- }
891
+ const auto rootElement = rootShadowNode.GetView().as<xaml::FrameworkElement>();
892
+ float actualWidth = static_cast<float>(rootElement.ActualWidth());
893
+ float actualHeight = static_cast<float>(rootElement.ActualHeight());
894
+ ApplyLayout(rootTag, actualWidth, actualHeight);
895
+ }
896
+ }
904
897
 
905
- {
906
- SystraceSection s("NativeUIManager::DoLayout::SetLayoutProps");
907
- SetLayoutPropsRecursive(rootTag);
908
- }
898
+ void NativeUIManager::ApplyLayout(int64_t tag, float width, float height) {
899
+ if (YGNodeRef rootNode = GetYogaNode(tag)) {
900
+ SystraceSection s("NativeUIManager::DoLayout::YGNodeCalculateLayout");
901
+ // We must always run layout in LTR mode, which might seem unintuitive.
902
+ // We will flip the root of the tree into RTL by forcing the root XAML node's FlowDirection to RightToLeft
903
+ // which will inherit down the XAML tree, allowing all native controls to pick it up.
904
+ YGNodeCalculateLayout(rootNode, width, height, YGDirectionLTR);
905
+ } else {
906
+ assert(false);
907
+ return;
908
+ }
909
+
910
+ {
911
+ SystraceSection s("NativeUIManager::DoLayout::SetLayoutProps");
912
+ SetLayoutPropsRecursive(tag);
909
913
  }
910
914
  }
911
915
 
@@ -1132,9 +1136,9 @@ void NativeUIManager::focus(int64_t reactTag) {
1132
1136
  // Note: It's a known issue that blur on flyout/popup would dismiss them.
1133
1137
  void NativeUIManager::blur(int64_t reactTag) {
1134
1138
  if (auto shadowNode = static_cast<ShadowNodeBase *>(m_host->FindShadowNodeForTag(reactTag))) {
1135
- auto view = shadowNode->GetView();
1136
1139
  // Only blur if current UI is focused to avoid problem described in PR #2687
1137
- if (view == xaml::Input::FocusManager::GetFocusedElement().try_as<xaml::DependencyObject>()) {
1140
+ const auto xamlRoot = tryGetXamlRoot(shadowNode->m_rootTag);
1141
+ if (shadowNode->GetView() == xaml::Input::FocusManager::GetFocusedElement(xamlRoot)) {
1138
1142
  if (auto reactControl = GetParentXamlReactControl(reactTag).get()) {
1139
1143
  reactControl.as<winrt::Microsoft::ReactNative::implementation::ReactRootView>()->blur(shadowNode->GetView());
1140
1144
  } else {
@@ -56,6 +56,7 @@ class NativeUIManager final : public INativeUIManager {
56
56
  void UpdateView(ShadowNode &shadowNode, winrt::Microsoft::ReactNative::JSValueObject &props) override;
57
57
  void onBatchComplete() override;
58
58
  void ensureInBatch() override;
59
+ bool isInBatch() override;
59
60
  void measure(
60
61
  ShadowNode &shadowNode,
61
62
  ShadowNode &shadowRoot,
@@ -100,8 +101,10 @@ class NativeUIManager final : public INativeUIManager {
100
101
 
101
102
  int64_t AddMeasuredRootView(facebook::react::IReactRootView *rootView);
102
103
 
103
- private:
104
104
  void DoLayout();
105
+ void ApplyLayout(int64_t tag, float width = YGUndefined, float height = YGUndefined);
106
+
107
+ private:
105
108
  void SetLayoutPropsRecursive(int64_t tag);
106
109
  YGNodeRef GetYogaNode(int64_t tag) const;
107
110
 
@@ -12,6 +12,7 @@
12
12
  #include <appModel.h>
13
13
  #include <comUtil/qiCast.h>
14
14
  #ifndef CORE_ABI
15
+ #include <LayoutService.h>
15
16
  #include <XamlUIService.h>
16
17
  #endif
17
18
  #include "ReactErrorProvider.h"
@@ -762,6 +763,10 @@ void ReactInstanceWin::InitUIManager() noexcept {
762
763
  m_reactContext->Properties().Set(
763
764
  implementation::XamlUIService::XamlUIServiceProperty().Handle(),
764
765
  winrt::make<implementation::XamlUIService>(m_reactContext));
766
+
767
+ m_reactContext->Properties().Set(
768
+ implementation::LayoutService::LayoutServiceProperty().Handle(),
769
+ winrt::make<implementation::LayoutService>(m_reactContext));
765
770
  }
766
771
  #endif
767
772
 
@@ -90,8 +90,7 @@ void ScrollViewShadowNode::dispatchCommand(
90
90
  scrollViewer.ChangeView(x, y, nullptr, !animated /*disableAnimation*/);
91
91
  } else if (commandId == ScrollViewCommands::ScrollToEnd) {
92
92
  bool animated = commandArgs[0].AsBoolean();
93
- bool horiz = scrollViewer.HorizontalScrollMode() == winrt::ScrollMode::Auto;
94
- if (horiz)
93
+ if (m_isHorizontal)
95
94
  scrollViewer.ChangeView(scrollViewer.ScrollableWidth(), nullptr, nullptr, !animated /*disableAnimation*/);
96
95
  else
97
96
  scrollViewer.ChangeView(nullptr, scrollViewer.ScrollableHeight(), nullptr, !animated /*disableAnimation*/);
@@ -490,7 +489,6 @@ XamlView ScrollViewManager::CreateViewCore(int64_t /*tag*/, const winrt::Microso
490
489
  scrollViewer.VerticalSnapPointsAlignment(winrt::SnapPointsAlignment::Near);
491
490
  scrollViewer.VerticalSnapPointsType(winrt::SnapPointsType::Mandatory);
492
491
  scrollViewer.HorizontalSnapPointsType(winrt::SnapPointsType::Mandatory);
493
- scrollViewer.HorizontalScrollMode(winrt::ScrollMode::Disabled);
494
492
 
495
493
  const auto snapPointManager = SnapPointManagingContentControl::Create();
496
494
  scrollViewer.Content(*snapPointManager);
@@ -40,13 +40,7 @@ void TextPropertyChangedParentVisitor::VisitText(ShadowNodeBase *node) {
40
40
  }
41
41
  }
42
42
 
43
- // Update fast text content
44
- if (!m_isNested && node->m_children.size() == 1) {
45
- if (const auto childNode = GetShadowNode(node->m_children[0])) {
46
- const auto run = static_cast<ShadowNodeBase *>(childNode)->GetView().as<winrt::Run>();
47
- element.Text(run.Text());
48
- }
49
- }
43
+ TextViewManager::UpdateOptimizedText(node);
50
44
  }
51
45
 
52
46
  // Refresh text highlighters
@@ -39,8 +39,7 @@ class TextShadowNode final : public ShadowNodeBase {
39
39
  friend TextViewManager;
40
40
 
41
41
  private:
42
- ShadowNode *m_firstChildNode;
43
-
42
+ bool m_isTextOptimized{true};
44
43
  bool m_hasDescendantTextHighlighter{false};
45
44
  bool m_hasDescendantPressable{false};
46
45
  std::optional<winrt::Windows::UI::Color> m_backgroundColor{};
@@ -49,9 +48,6 @@ class TextShadowNode final : public ShadowNodeBase {
49
48
  winrt::event_revoker<xaml::Controls::ITextBlock> m_selectionChangedRevoker;
50
49
 
51
50
  public:
52
- TextShadowNode() {
53
- m_firstChildNode = nullptr;
54
- };
55
51
  bool ImplementsPadding() override {
56
52
  return true;
57
53
  }
@@ -66,25 +62,23 @@ class TextShadowNode final : public ShadowNodeBase {
66
62
  m_hasDescendantPressable |= textChildNode.hasDescendantPressable;
67
63
  }
68
64
 
69
- auto addInline = true;
70
- // Only convert to fast text when exactly one child is attached to the root Text node
71
- if (index == 0 && m_children.size() == 1) {
72
- auto run = childNode.GetView().try_as<winrt::Run>();
73
- if (run != nullptr) {
74
- m_firstChildNode = &child;
75
- auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
76
- textBlock.Text(run.Text());
77
- addInline = false;
65
+ const auto wasOptimized = m_isTextOptimized;
66
+ m_isTextOptimized = IsRawTextShadowNode(&childNode) && m_isTextOptimized;
67
+ if (m_isTextOptimized) {
68
+ // Re-build optimized text from children
69
+ UpdateOptimizedText();
70
+ } else if (wasOptimized) {
71
+ // Remove optimized text and re-construct as Inline tree
72
+ UpdateOptimizedText();
73
+ if (const auto uiManager = GetNativeUIManager(GetViewManager()->GetReactContext()).lock()) {
74
+ for (size_t i = 0; i < m_children.size(); ++i) {
75
+ if (const auto childNode =
76
+ static_cast<ShadowNodeBase *>(uiManager->getHost()->FindShadowNodeForTag(m_children[i]))) {
77
+ Super::AddView(*childNode, i);
78
+ }
79
+ }
78
80
  }
79
- } else if (m_firstChildNode != nullptr) {
80
- assert(m_children.size() == 2);
81
- auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
82
- textBlock.ClearValue(xaml::Controls::TextBlock::TextProperty());
83
- Super::AddView(*m_firstChildNode, 0);
84
- m_firstChildNode = nullptr;
85
- }
86
-
87
- if (addInline) {
81
+ } else {
88
82
  Super::AddView(child, index);
89
83
  }
90
84
 
@@ -92,10 +86,9 @@ class TextShadowNode final : public ShadowNodeBase {
92
86
  }
93
87
 
94
88
  void removeAllChildren() override {
95
- if (m_firstChildNode) {
89
+ if (m_isTextOptimized) {
96
90
  auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
97
91
  textBlock.ClearValue(xaml::Controls::TextBlock::TextProperty());
98
- m_firstChildNode = nullptr;
99
92
  } else {
100
93
  Super::removeAllChildren();
101
94
  }
@@ -103,17 +96,33 @@ class TextShadowNode final : public ShadowNodeBase {
103
96
  }
104
97
 
105
98
  void RemoveChildAt(int64_t indexToRemove) override {
106
- if (m_firstChildNode) {
107
- assert(indexToRemove == 0);
108
- auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
109
- textBlock.ClearValue(xaml::Controls::TextBlock::TextProperty());
110
- m_firstChildNode = nullptr;
99
+ if (m_isTextOptimized) {
100
+ UpdateOptimizedText();
111
101
  } else {
112
102
  Super::RemoveChildAt(indexToRemove);
113
103
  }
114
104
  RecalculateTextHighlighters();
115
105
  }
116
106
 
107
+ void UpdateOptimizedText() {
108
+ if (m_children.size() > 0 && m_isTextOptimized) {
109
+ if (const auto uiManager = GetNativeUIManager(GetViewManager()->GetReactContext()).lock()) {
110
+ winrt::hstring text = L"";
111
+ for (const auto childTag : m_children) {
112
+ if (const auto childNode =
113
+ static_cast<ShadowNodeBase *>(uiManager->getHost()->FindShadowNodeForTag(childTag))) {
114
+ text = text + childNode->GetView().as<winrt::Run>().Text();
115
+ }
116
+ }
117
+ auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
118
+ textBlock.Text(text);
119
+ }
120
+ } else {
121
+ auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
122
+ textBlock.ClearValue(xaml::Controls::TextBlock::TextProperty());
123
+ }
124
+ }
125
+
117
126
  void RecalculateTextHighlighters() {
118
127
  const auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
119
128
  textBlock.TextHighlighters().Clear();
@@ -389,6 +398,13 @@ void TextViewManager::OnPointerEvent(
389
398
  }
390
399
  }
391
400
 
401
+ /*static*/ void TextViewManager::UpdateOptimizedText(ShadowNodeBase *node) {
402
+ if (IsTextShadowNode(node)) {
403
+ const auto textNode = static_cast<TextShadowNode *>(node);
404
+ textNode->UpdateOptimizedText();
405
+ }
406
+ }
407
+
392
408
  /*static*/ void TextViewManager::SetDescendantPressable(ShadowNodeBase *node) {
393
409
  if (IsTextShadowNode(node)) {
394
410
  const auto textNode = static_cast<TextShadowNode *>(node);
@@ -29,6 +29,8 @@ class TextViewManager : public FrameworkElementViewManager {
29
29
 
30
30
  static void UpdateTextHighlighters(ShadowNodeBase *node, bool highlightAdded);
31
31
 
32
+ static void UpdateOptimizedText(ShadowNodeBase *node);
33
+
32
34
  static void SetDescendantPressable(ShadowNodeBase *node);
33
35
 
34
36
  static TextTransform GetTextTransformValue(ShadowNodeBase *node);
@@ -10,7 +10,7 @@
10
10
  -->
11
11
  <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
12
12
  <PropertyGroup>
13
- <ReactNativeWindowsVersion>0.0.0-canary.575</ReactNativeWindowsVersion>
13
+ <ReactNativeWindowsVersion>0.0.0-canary.577</ReactNativeWindowsVersion>
14
14
  <ReactNativeWindowsMajor>0</ReactNativeWindowsMajor>
15
15
  <ReactNativeWindowsMinor>0</ReactNativeWindowsMinor>
16
16
  <ReactNativeWindowsPatch>0</ReactNativeWindowsPatch>
@@ -53,6 +53,34 @@ using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue;
53
53
 
54
54
  namespace Microsoft::React::Networking {
55
55
 
56
+ // May throw winrt::hresult_error
57
+ void AttachMultipartHeaders(IHttpContent content, const dynamic &headers) {
58
+ HttpMediaTypeHeaderValue contentType{nullptr};
59
+
60
+ // Headers are generally case-insensitive
61
+ // https://www.ietf.org/rfc/rfc2616.txt section 4.2
62
+ // TODO: Consolidate with PerformRequest's header parsing.
63
+ for (auto &header : headers.items()) {
64
+ auto &name = header.first.getString();
65
+ auto &value = header.second.getString();
66
+
67
+ if (boost::iequals(name.c_str(), "Content-Type")) {
68
+ contentType = HttpMediaTypeHeaderValue::Parse(to_hstring(value));
69
+ } else if (boost::iequals(name.c_str(), "Authorization")) {
70
+ bool success = content.Headers().TryAppendWithoutValidation(to_hstring(name), to_hstring(value));
71
+ if (!success) {
72
+ throw hresult_error{E_INVALIDARG, L"Failed to append Authorization"};
73
+ }
74
+ } else {
75
+ content.Headers().Append(to_hstring(name), to_hstring(value));
76
+ }
77
+ }
78
+
79
+ if (contentType) {
80
+ content.Headers().ContentType(contentType);
81
+ }
82
+ }
83
+
56
84
  #pragma region WinRTHttpResource
57
85
 
58
86
  WinRTHttpResource::WinRTHttpResource(IHttpClient &&client) noexcept : m_client{std::move(client)} {}
@@ -81,20 +109,23 @@ IAsyncOperation<HttpRequestMessage> WinRTHttpResource::CreateRequest(
81
109
  // Headers are generally case-insensitive
82
110
  // https://www.ietf.org/rfc/rfc2616.txt section 4.2
83
111
  for (auto &header : reqArgs->Headers) {
84
- if (boost::iequals(header.first.c_str(), "Content-Type")) {
85
- bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(header.second), contentType);
112
+ auto &name = header.first;
113
+ auto &value = header.second;
114
+
115
+ if (boost::iequals(name.c_str(), "Content-Type")) {
116
+ bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(value), contentType);
86
117
  if (!success) {
87
118
  if (self->m_onError) {
88
119
  self->m_onError(reqArgs->RequestId, "Failed to parse Content-Type", false);
89
120
  }
90
121
  co_return nullptr;
91
122
  }
92
- } else if (boost::iequals(header.first.c_str(), "Content-Encoding")) {
93
- contentEncoding = header.second;
94
- } else if (boost::iequals(header.first.c_str(), "Content-Length")) {
95
- contentLength = header.second;
96
- } else if (boost::iequals(header.first.c_str(), "Authorization")) {
97
- bool success = request.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second));
123
+ } else if (boost::iequals(name.c_str(), "Content-Encoding")) {
124
+ contentEncoding = value;
125
+ } else if (boost::iequals(name.c_str(), "Content-Length")) {
126
+ contentLength = value;
127
+ } else if (boost::iequals(name.c_str(), "Authorization")) {
128
+ bool success = request.Headers().TryAppendWithoutValidation(to_hstring(name), to_hstring(value));
98
129
  if (!success) {
99
130
  if (self->m_onError) {
100
131
  self->m_onError(reqArgs->RequestId, "Failed to append Authorization", false);
@@ -103,7 +134,7 @@ IAsyncOperation<HttpRequestMessage> WinRTHttpResource::CreateRequest(
103
134
  }
104
135
  } else {
105
136
  try {
106
- request.Headers().Append(to_hstring(header.first), to_hstring(header.second));
137
+ request.Headers().Append(to_hstring(name), to_hstring(value));
107
138
  } catch (hresult_error const &e) {
108
139
  if (self->m_onError) {
109
140
  self->m_onError(reqArgs->RequestId, Utilities::HResultToString(e), false);
@@ -146,9 +177,31 @@ IAsyncOperation<HttpRequestMessage> WinRTHttpResource::CreateRequest(
146
177
  auto file = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{to_hstring(data["uri"].asString())});
147
178
  auto stream = co_await file.OpenReadAsync();
148
179
  content = HttpStreamContent{std::move(stream)};
149
- } else if (!data["form"].empty()) {
150
- // #9535 - HTTP form data support
151
- // winrt::Windows::Web::Http::HttpMultipartFormDataContent()
180
+ } else if (!data["formData"].empty()) {
181
+ winrt::Windows::Web::Http::HttpMultipartFormDataContent multiPartContent;
182
+ auto formData = data["formData"];
183
+
184
+ // #6046 - Overwriting WinRT's HttpMultipartFormDataContent implicit Content-Type clears the generated boundary
185
+ contentType = nullptr;
186
+
187
+ for (auto &formDataPart : formData) {
188
+ IHttpContent formContent{nullptr};
189
+ if (!formDataPart["string"].isNull()) {
190
+ formContent = HttpStringContent{to_hstring(formDataPart["string"].asString())};
191
+ } else if (!formDataPart["uri"].empty()) {
192
+ auto filePath = to_hstring(formDataPart["uri"].asString());
193
+ auto file = co_await StorageFile::GetFileFromPathAsync(filePath);
194
+ auto stream = co_await file.OpenReadAsync();
195
+ formContent = HttpStreamContent{stream};
196
+ }
197
+
198
+ if (formContent) {
199
+ AttachMultipartHeaders(formContent, formDataPart["headers"]);
200
+ multiPartContent.Add(formContent, to_hstring(formDataPart["fieldName"].asString()));
201
+ }
202
+ } // foreach form data part
203
+
204
+ content = multiPartContent;
152
205
  }
153
206
  }
154
207
 
@@ -316,11 +369,18 @@ WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspect
316
369
  auto props = winrt::multi_threaded_map<winrt::hstring, IInspectable>();
317
370
  props.Insert(L"RequestArgs", coArgs);
318
371
 
319
- auto coRequest = co_await CreateRequest(std::move(coMethod), std::move(coUri), props);
320
- if (!coRequest) {
321
- co_return;
372
+ auto coRequestOp = CreateRequest(std::move(coMethod), std::move(coUri), props);
373
+ co_await lessthrow_await_adapter<IAsyncOperation<HttpRequestMessage>>{coRequestOp};
374
+ auto coRequestOpHR = coRequestOp.ErrorCode();
375
+ if (coRequestOpHR < 0) {
376
+ if (self->m_onError) {
377
+ self->m_onError(reqArgs->RequestId, Utilities::HResultToString(std::move(coRequestOpHR)), false);
378
+ }
379
+ co_return self->UntrackResponse(reqArgs->RequestId);
322
380
  }
323
381
 
382
+ auto coRequest = coRequestOp.GetResults();
383
+
324
384
  // If URI handler is available, it takes over request processing.
325
385
  if (auto uriHandler = self->m_uriHandler.lock()) {
326
386
  auto uri = winrt::to_string(coRequest.RequestUri().ToString());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-windows",
3
- "version": "0.0.0-canary.575",
3
+ "version": "0.0.0-canary.577",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",