electron_api_view.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // Copyright (c) 2018 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/api/electron_api_view.h"
  5. #include <algorithm>
  6. #include <limits>
  7. #include <memory>
  8. #include <string>
  9. #include <utility>
  10. #include "gin/data_object_builder.h"
  11. #include "gin/wrappable.h"
  12. #include "shell/browser/javascript_environment.h"
  13. #include "shell/common/gin_converters/callback_converter.h"
  14. #include "shell/common/gin_converters/gfx_converter.h"
  15. #include "shell/common/gin_helper/dictionary.h"
  16. #include "shell/common/gin_helper/object_template_builder.h"
  17. #include "shell/common/node_includes.h"
  18. #include "ui/views/background.h"
  19. #include "ui/views/layout/flex_layout.h"
  20. #include "ui/views/layout/layout_manager_base.h"
  21. #if BUILDFLAG(IS_MAC)
  22. #include "shell/browser/animation_util.h"
  23. #endif
  24. namespace gin {
  25. template <>
  26. struct Converter<views::ChildLayout> {
  27. static bool FromV8(v8::Isolate* isolate,
  28. v8::Local<v8::Value> val,
  29. views::ChildLayout* out) {
  30. gin_helper::Dictionary dict;
  31. if (!gin::ConvertFromV8(isolate, val, &dict))
  32. return false;
  33. gin::Handle<electron::api::View> view;
  34. if (!dict.Get("view", &view))
  35. return false;
  36. out->child_view = view->view();
  37. if (dict.Has("bounds"))
  38. dict.Get("bounds", &out->bounds);
  39. out->visible = true;
  40. if (dict.Has("visible"))
  41. dict.Get("visible", &out->visible);
  42. return true;
  43. }
  44. };
  45. template <>
  46. struct Converter<views::ProposedLayout> {
  47. static bool FromV8(v8::Isolate* isolate,
  48. v8::Local<v8::Value> val,
  49. views::ProposedLayout* out) {
  50. gin_helper::Dictionary dict;
  51. if (!gin::ConvertFromV8(isolate, val, &dict))
  52. return false;
  53. if (!dict.Get("size", &out->host_size))
  54. return false;
  55. if (!dict.Get("layouts", &out->child_layouts))
  56. return false;
  57. return true;
  58. }
  59. };
  60. template <>
  61. struct Converter<views::LayoutOrientation> {
  62. static bool FromV8(v8::Isolate* isolate,
  63. v8::Local<v8::Value> val,
  64. views::LayoutOrientation* out) {
  65. std::string orientation = base::ToLowerASCII(gin::V8ToString(isolate, val));
  66. if (orientation == "horizontal") {
  67. *out = views::LayoutOrientation::kHorizontal;
  68. } else if (orientation == "vertical") {
  69. *out = views::LayoutOrientation::kVertical;
  70. } else {
  71. return false;
  72. }
  73. return true;
  74. }
  75. };
  76. template <>
  77. struct Converter<views::LayoutAlignment> {
  78. static bool FromV8(v8::Isolate* isolate,
  79. v8::Local<v8::Value> val,
  80. views::LayoutAlignment* out) {
  81. std::string orientation = base::ToLowerASCII(gin::V8ToString(isolate, val));
  82. if (orientation == "start") {
  83. *out = views::LayoutAlignment::kStart;
  84. } else if (orientation == "center") {
  85. *out = views::LayoutAlignment::kCenter;
  86. } else if (orientation == "end") {
  87. *out = views::LayoutAlignment::kEnd;
  88. } else if (orientation == "stretch") {
  89. *out = views::LayoutAlignment::kStretch;
  90. } else if (orientation == "baseline") {
  91. *out = views::LayoutAlignment::kBaseline;
  92. } else {
  93. return false;
  94. }
  95. return true;
  96. }
  97. };
  98. template <>
  99. struct Converter<views::FlexAllocationOrder> {
  100. static bool FromV8(v8::Isolate* isolate,
  101. v8::Local<v8::Value> val,
  102. views::FlexAllocationOrder* out) {
  103. std::string orientation = base::ToLowerASCII(gin::V8ToString(isolate, val));
  104. if (orientation == "normal") {
  105. *out = views::FlexAllocationOrder::kNormal;
  106. } else if (orientation == "reverse") {
  107. *out = views::FlexAllocationOrder::kReverse;
  108. } else {
  109. return false;
  110. }
  111. return true;
  112. }
  113. };
  114. template <>
  115. struct Converter<views::SizeBound> {
  116. static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
  117. const views::SizeBound& in) {
  118. if (in.is_bounded())
  119. return v8::Integer::New(isolate, in.value());
  120. return v8::Number::New(isolate, std::numeric_limits<double>::infinity());
  121. }
  122. };
  123. template <>
  124. struct Converter<views::SizeBounds> {
  125. static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
  126. const views::SizeBounds& in) {
  127. return gin::DataObjectBuilder(isolate)
  128. .Set("width", in.width())
  129. .Set("height", in.height())
  130. .Build();
  131. }
  132. };
  133. } // namespace gin
  134. namespace electron::api {
  135. using LayoutCallback = base::RepeatingCallback<views::ProposedLayout(
  136. const views::SizeBounds& size_bounds)>;
  137. class JSLayoutManager : public views::LayoutManagerBase {
  138. public:
  139. explicit JSLayoutManager(LayoutCallback layout_callback)
  140. : layout_callback_(std::move(layout_callback)) {}
  141. ~JSLayoutManager() override {}
  142. views::ProposedLayout CalculateProposedLayout(
  143. const views::SizeBounds& size_bounds) const override {
  144. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  145. v8::HandleScope handle_scope(isolate);
  146. return layout_callback_.Run(size_bounds);
  147. }
  148. private:
  149. LayoutCallback layout_callback_;
  150. };
  151. View::View(views::View* view) : view_(view) {
  152. view_->set_owned_by_client();
  153. view_->AddObserver(this);
  154. }
  155. View::View() : View(new views::View()) {}
  156. View::~View() {
  157. if (!view_)
  158. return;
  159. view_->RemoveObserver(this);
  160. if (delete_view_)
  161. delete view_;
  162. }
  163. void View::ReorderChildView(gin::Handle<View> child, size_t index) {
  164. view_->ReorderChildView(child->view(), index);
  165. const auto i = base::ranges::find(child_views_, child.ToV8());
  166. DCHECK(i != child_views_.end());
  167. // If |view| is already at the desired position, there's nothing to do.
  168. const auto pos = std::next(
  169. child_views_.begin(),
  170. static_cast<ptrdiff_t>(std::min(index, child_views_.size() - 1)));
  171. if (i == pos)
  172. return;
  173. if (pos < i) {
  174. std::rotate(pos, i, std::next(i));
  175. } else {
  176. std::rotate(i, std::next(i), std::next(pos));
  177. }
  178. }
  179. void View::AddChildViewAt(gin::Handle<View> child,
  180. std::optional<size_t> maybe_index) {
  181. // TODO(nornagon): !view_ is only for supporting the weird case of
  182. // WebContentsView's view being deleted when the underlying WebContents is
  183. // destroyed (on non-Mac). We should fix that so that WebContentsView always
  184. // has a View, possibly a wrapper view around the underlying platform View.
  185. if (!view_)
  186. return;
  187. // This will CHECK and crash in View::AddChildViewAtImpl if not handled here.
  188. if (view_ == child->view()) {
  189. gin_helper::ErrorThrower(isolate()).ThrowError(
  190. "A view cannot be added as its own child");
  191. return;
  192. }
  193. size_t index =
  194. std::min(child_views_.size(), maybe_index.value_or(child_views_.size()));
  195. // If the child is already a child of this view, just reorder it.
  196. // This matches the behavior of View::AddChildViewAtImpl and
  197. // otherwise will CHECK if the same view is added multiple times.
  198. if (child->view()->parent() == view_) {
  199. ReorderChildView(child, index);
  200. return;
  201. }
  202. child_views_.emplace(child_views_.begin() + index, // index
  203. isolate(), child->GetWrapper()); // v8::Global(args...)
  204. #if BUILDFLAG(IS_MAC)
  205. // Disable the implicit CALayer animations that happen by default when adding
  206. // or removing sublayers.
  207. // See
  208. // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/ReactingtoLayerChanges/ReactingtoLayerChanges.html
  209. // and https://github.com/electron/electron/pull/14911
  210. // TODO(nornagon): Disabling these CALayer animations (which are specific to
  211. // WebContentsView, I think) seems like this is something that remote_cocoa
  212. // or views should be taking care of, but isn't. This should be pushed
  213. // upstream.
  214. ScopedCAActionDisabler disable_animations;
  215. #endif
  216. view_->AddChildViewAt(child->view(), index);
  217. }
  218. void View::RemoveChildView(gin::Handle<View> child) {
  219. if (!view_)
  220. return;
  221. if (!child->view())
  222. return;
  223. const auto it = base::ranges::find(child_views_, child.ToV8());
  224. if (it != child_views_.end()) {
  225. #if BUILDFLAG(IS_MAC)
  226. ScopedCAActionDisabler disable_animations;
  227. #endif
  228. view_->RemoveChildView(child->view());
  229. child_views_.erase(it);
  230. }
  231. }
  232. void View::SetBounds(const gfx::Rect& bounds) {
  233. if (!view_)
  234. return;
  235. view_->SetBoundsRect(bounds);
  236. }
  237. gfx::Rect View::GetBounds() {
  238. if (!view_)
  239. return gfx::Rect();
  240. return view_->bounds();
  241. }
  242. void View::SetLayout(v8::Isolate* isolate, v8::Local<v8::Object> value) {
  243. if (!view_)
  244. return;
  245. gin_helper::Dictionary dict(isolate, value);
  246. LayoutCallback calculate_proposed_layout;
  247. if (dict.Get("calculateProposedLayout", &calculate_proposed_layout)) {
  248. view_->SetLayoutManager(std::make_unique<JSLayoutManager>(
  249. std::move(calculate_proposed_layout)));
  250. } else {
  251. auto* layout =
  252. view_->SetLayoutManager(std::make_unique<views::FlexLayout>());
  253. views::LayoutOrientation orientation;
  254. if (dict.Get("orientation", &orientation))
  255. layout->SetOrientation(orientation);
  256. views::LayoutAlignment main_axis_alignment;
  257. if (dict.Get("mainAxisAlignment", &main_axis_alignment))
  258. layout->SetMainAxisAlignment(main_axis_alignment);
  259. views::LayoutAlignment cross_axis_alignment;
  260. if (dict.Get("crossAxisAlignment", &cross_axis_alignment))
  261. layout->SetCrossAxisAlignment(cross_axis_alignment);
  262. gfx::Insets interior_margin;
  263. if (dict.Get("interiorMargin", &interior_margin))
  264. layout->SetInteriorMargin(interior_margin);
  265. int minimum_cross_axis_size;
  266. if (dict.Get("minimumCrossAxisSize", &minimum_cross_axis_size))
  267. layout->SetMinimumCrossAxisSize(minimum_cross_axis_size);
  268. bool collapse_margins;
  269. if (dict.Has("collapseMargins") &&
  270. dict.Get("collapseMargins", &collapse_margins))
  271. layout->SetCollapseMargins(collapse_margins);
  272. bool include_host_insets_in_layout;
  273. if (dict.Has("includeHostInsetsInLayout") &&
  274. dict.Get("includeHostInsetsInLayout", &include_host_insets_in_layout))
  275. layout->SetIncludeHostInsetsInLayout(include_host_insets_in_layout);
  276. bool ignore_default_main_axis_margins;
  277. if (dict.Has("ignoreDefaultMainAxisMargins") &&
  278. dict.Get("ignoreDefaultMainAxisMargins",
  279. &ignore_default_main_axis_margins))
  280. layout->SetIgnoreDefaultMainAxisMargins(ignore_default_main_axis_margins);
  281. views::FlexAllocationOrder flex_allocation_order;
  282. if (dict.Get("flexAllocationOrder", &flex_allocation_order))
  283. layout->SetFlexAllocationOrder(flex_allocation_order);
  284. }
  285. }
  286. std::vector<v8::Local<v8::Value>> View::GetChildren() {
  287. std::vector<v8::Local<v8::Value>> ret;
  288. ret.reserve(child_views_.size());
  289. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  290. for (auto& child_view : child_views_)
  291. ret.push_back(child_view.Get(isolate));
  292. return ret;
  293. }
  294. void View::SetBackgroundColor(std::optional<WrappedSkColor> color) {
  295. if (!view_)
  296. return;
  297. view_->SetBackground(color ? views::CreateSolidBackground(*color) : nullptr);
  298. }
  299. void View::SetVisible(bool visible) {
  300. if (!view_)
  301. return;
  302. view_->SetVisible(visible);
  303. }
  304. void View::OnViewBoundsChanged(views::View* observed_view) {
  305. Emit("bounds-changed");
  306. }
  307. void View::OnViewIsDeleting(views::View* observed_view) {
  308. DCHECK_EQ(observed_view, view_);
  309. view_ = nullptr;
  310. }
  311. // static
  312. gin_helper::WrappableBase* View::New(gin::Arguments* args) {
  313. View* view = new View();
  314. view->InitWithArgs(args);
  315. return view;
  316. }
  317. // static
  318. v8::Local<v8::Function> View::GetConstructor(v8::Isolate* isolate) {
  319. static base::NoDestructor<v8::Global<v8::Function>> constructor;
  320. if (constructor.get()->IsEmpty()) {
  321. constructor->Reset(isolate, gin_helper::CreateConstructor<View>(
  322. isolate, base::BindRepeating(&View::New)));
  323. }
  324. return v8::Local<v8::Function>::New(isolate, *constructor.get());
  325. }
  326. // static
  327. gin::Handle<View> View::Create(v8::Isolate* isolate) {
  328. v8::Local<v8::Context> context = isolate->GetCurrentContext();
  329. v8::Local<v8::Object> obj;
  330. if (GetConstructor(isolate)->NewInstance(context, 0, nullptr).ToLocal(&obj)) {
  331. gin::Handle<View> view;
  332. if (gin::ConvertFromV8(isolate, obj, &view))
  333. return view;
  334. }
  335. return gin::Handle<View>();
  336. }
  337. // static
  338. void View::BuildPrototype(v8::Isolate* isolate,
  339. v8::Local<v8::FunctionTemplate> prototype) {
  340. prototype->SetClassName(gin::StringToV8(isolate, "View"));
  341. gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
  342. .SetMethod("addChildView", &View::AddChildViewAt)
  343. .SetMethod("removeChildView", &View::RemoveChildView)
  344. .SetProperty("children", &View::GetChildren)
  345. .SetMethod("setBounds", &View::SetBounds)
  346. .SetMethod("getBounds", &View::GetBounds)
  347. .SetMethod("setBackgroundColor", &View::SetBackgroundColor)
  348. .SetMethod("setLayout", &View::SetLayout)
  349. .SetMethod("setVisible", &View::SetVisible);
  350. }
  351. } // namespace electron::api
  352. namespace {
  353. using electron::api::View;
  354. void Initialize(v8::Local<v8::Object> exports,
  355. v8::Local<v8::Value> unused,
  356. v8::Local<v8::Context> context,
  357. void* priv) {
  358. v8::Isolate* isolate = context->GetIsolate();
  359. gin_helper::Dictionary dict(isolate, exports);
  360. dict.Set("View", View::GetConstructor(isolate));
  361. }
  362. } // namespace
  363. NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_view, Initialize)