Commit 618ec90b authored by Francois Beaufort's avatar Francois Beaufort Committed by Commit Bot

Set PictureInPictureWindow in leavepictureinpicture event

This CL makes sure PictureInPictureWindow is accessible from the
leavepictureinpicture event so that users don't have to keep a reference
to the PictureInPictureWindow object they got from the
enterpictureinpicture event.

Spec change: https://github.com/w3c/picture-in-picture/pull/189
Intent to ship: https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/gwaEnJjtfKw

Change-Id: Ibb6972596cd6b766bc2ed35cc6e360b7907c594d
Bug: 1096350
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2241537Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Commit-Queue: François Beaufort <beaufort.francois@gmail.com>
Cr-Commit-Position: refs/heads/master@{#779711}
parent bab102e7
...@@ -100,7 +100,7 @@ interface AuthenticatorResponse ...@@ -100,7 +100,7 @@ interface AuthenticatorResponse
interface AuthenticatorAttestationResponse : AuthenticatorResponse interface AuthenticatorAttestationResponse : AuthenticatorResponse
# Picture-in-Picture API is disabled in Android WebView, crbug.com/852449 # Picture-in-Picture API is disabled in Android WebView, crbug.com/852449
interface EnterPictureInPictureEvent : Event interface PictureInPictureEvent : Event
interface PictureInPictureWindow : EventTarget interface PictureInPictureWindow : EventTarget
interface Document : Node interface Document : Node
method exitPictureInPicture method exitPictureInPicture
......
...@@ -546,8 +546,8 @@ generated_interface_sources_in_modules = [ ...@@ -546,8 +546,8 @@ generated_interface_sources_in_modules = [
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_element.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_element.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_enter_picture_in_picture_event.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_picture_in_picture_event.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_enter_picture_in_picture_event.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_picture_in_picture_event.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_entry.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_entry.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_entry.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_entry.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_entry_sync.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_entry_sync.cc",
......
...@@ -530,8 +530,8 @@ static_idl_files_in_modules = get_path_info( ...@@ -530,8 +530,8 @@ static_idl_files_in_modules = get_path_info(
"//third_party/blink/renderer/modules/permissions/push_permission_descriptor.idl", "//third_party/blink/renderer/modules/permissions/push_permission_descriptor.idl",
"//third_party/blink/renderer/modules/permissions/worker_navigator_permissions.idl", "//third_party/blink/renderer/modules/permissions/worker_navigator_permissions.idl",
"//third_party/blink/renderer/modules/picture_in_picture/document_picture_in_picture.idl", "//third_party/blink/renderer/modules/picture_in_picture/document_picture_in_picture.idl",
"//third_party/blink/renderer/modules/picture_in_picture/enter_picture_in_picture_event.idl", "//third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_event.idl",
"//third_party/blink/renderer/modules/picture_in_picture/enter_picture_in_picture_event_init.idl", "//third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_event_init.idl",
"//third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.idl", "//third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.idl",
"//third_party/blink/renderer/modules/picture_in_picture/html_video_element_picture_in_picture.idl", "//third_party/blink/renderer/modules/picture_in_picture/html_video_element_picture_in_picture.idl",
"//third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_options.idl", "//third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_options.idl",
......
...@@ -53,7 +53,7 @@ generate_event_interfaces("modules_bindings_generated_event_interfaces") { ...@@ -53,7 +53,7 @@ generate_event_interfaces("modules_bindings_generated_event_interfaces") {
"//third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_error_event.idl", "//third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_error_event.idl",
"//third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_event.idl", "//third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_event.idl",
"//third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_event.idl", "//third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_event.idl",
"//third_party/blink/renderer/modules/picture_in_picture/enter_picture_in_picture_event.idl", "//third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_event.idl",
"//third_party/blink/renderer/modules/presentation/presentation_connection_available_event.idl", "//third_party/blink/renderer/modules/presentation/presentation_connection_available_event.idl",
"//third_party/blink/renderer/modules/presentation/presentation_connection_close_event.idl", "//third_party/blink/renderer/modules/presentation/presentation_connection_close_event.idl",
"//third_party/blink/renderer/modules/push_messaging/push_event.idl", "//third_party/blink/renderer/modules/push_messaging/push_event.idl",
......
...@@ -8,14 +8,14 @@ blink_modules_sources("picture_in_picture") { ...@@ -8,14 +8,14 @@ blink_modules_sources("picture_in_picture") {
sources = [ sources = [
"document_picture_in_picture.cc", "document_picture_in_picture.cc",
"document_picture_in_picture.h", "document_picture_in_picture.h",
"enter_picture_in_picture_event.cc",
"enter_picture_in_picture_event.h",
"html_element_picture_in_picture.cc", "html_element_picture_in_picture.cc",
"html_element_picture_in_picture.h", "html_element_picture_in_picture.h",
"html_video_element_picture_in_picture.cc", "html_video_element_picture_in_picture.cc",
"html_video_element_picture_in_picture.h", "html_video_element_picture_in_picture.h",
"picture_in_picture_controller_impl.cc", "picture_in_picture_controller_impl.cc",
"picture_in_picture_controller_impl.h", "picture_in_picture_controller_impl.h",
"picture_in_picture_event.cc",
"picture_in_picture_event.h",
"picture_in_picture_window.cc", "picture_in_picture_window.cc",
"picture_in_picture_window.h", "picture_in_picture_window.h",
"shadow_root_picture_in_picture.cc", "shadow_root_picture_in_picture.cc",
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
# found in the LICENSE file. # found in the LICENSE file.
modules_idl_files = [ modules_idl_files = [
"enter_picture_in_picture_event.idl", "picture_in_picture_event.idl",
"picture_in_picture_window.idl", "picture_in_picture_window.idl",
] ]
modules_dictionary_idl_files = [ modules_dictionary_idl_files = [
"enter_picture_in_picture_event_init.idl", "picture_in_picture_event_init.idl",
"picture_in_picture_options.idl", "picture_in_picture_options.idl",
] ]
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
#include "third_party/blink/renderer/core/fullscreen/fullscreen.h" #include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h" #include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h" #include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/modules/picture_in_picture/enter_picture_in_picture_event.h" #include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_event.h"
#include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h" #include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
...@@ -227,10 +227,9 @@ void PictureInPictureControllerImpl::OnEnteredPictureInPicture( ...@@ -227,10 +227,9 @@ void PictureInPictureControllerImpl::OnEnteredPictureInPicture(
picture_in_picture_window_ = MakeGarbageCollected<PictureInPictureWindow>( picture_in_picture_window_ = MakeGarbageCollected<PictureInPictureWindow>(
GetExecutionContext(), picture_in_picture_window_size); GetExecutionContext(), picture_in_picture_window_size);
picture_in_picture_element_->DispatchEvent( picture_in_picture_element_->DispatchEvent(*PictureInPictureEvent::Create(
*EnterPictureInPictureEvent::Create( event_type_names::kEnterpictureinpicture,
event_type_names::kEnterpictureinpicture, WrapPersistent(picture_in_picture_window_.Get())));
WrapPersistent(picture_in_picture_window_.Get())));
if (resolver) if (resolver)
resolver->Resolve(picture_in_picture_window_); resolver->Resolve(picture_in_picture_window_);
...@@ -256,16 +255,19 @@ void PictureInPictureControllerImpl::OnExitedPictureInPicture( ...@@ -256,16 +255,19 @@ void PictureInPictureControllerImpl::OnExitedPictureInPicture(
if (!GetSupplementable()->IsActive()) if (!GetSupplementable()->IsActive())
return; return;
if (picture_in_picture_window_) // The Picture-in-Picture window and the Picture-in-Picture element
// should be either both set or both null.
DCHECK(!picture_in_picture_element_ == !picture_in_picture_window_);
if (picture_in_picture_element_) {
picture_in_picture_window_->OnClose(); picture_in_picture_window_->OnClose();
if (picture_in_picture_element_) {
HTMLVideoElement* element = picture_in_picture_element_; HTMLVideoElement* element = picture_in_picture_element_;
picture_in_picture_element_ = nullptr; picture_in_picture_element_ = nullptr;
element->OnExitedPictureInPicture(); element->OnExitedPictureInPicture();
element->DispatchEvent( element->DispatchEvent(*PictureInPictureEvent::Create(
*Event::CreateBubble(event_type_names::kLeavepictureinpicture)); event_type_names::kLeavepictureinpicture,
WrapPersistent(picture_in_picture_window_.Get())));
} }
if (resolver) if (resolver)
......
// Copyright 2018 The Chromium Authors. All rights reserved. // Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "third_party/blink/renderer/modules/picture_in_picture/enter_picture_in_picture_event.h" #include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_event.h"
namespace blink { namespace blink {
EnterPictureInPictureEvent* EnterPictureInPictureEvent::Create( PictureInPictureEvent* PictureInPictureEvent::Create(
const AtomicString& type, const AtomicString& type,
PictureInPictureWindow* picture_in_picture_window) { PictureInPictureWindow* picture_in_picture_window) {
return MakeGarbageCollected<EnterPictureInPictureEvent>( return MakeGarbageCollected<PictureInPictureEvent>(type,
type, picture_in_picture_window); picture_in_picture_window);
} }
EnterPictureInPictureEvent* EnterPictureInPictureEvent::Create( PictureInPictureEvent* PictureInPictureEvent::Create(
const AtomicString& type, const AtomicString& type,
const EnterPictureInPictureEventInit* initializer) { const PictureInPictureEventInit* initializer) {
return MakeGarbageCollected<EnterPictureInPictureEvent>(type, initializer); return MakeGarbageCollected<PictureInPictureEvent>(type, initializer);
} }
PictureInPictureWindow* EnterPictureInPictureEvent::pictureInPictureWindow() PictureInPictureWindow* PictureInPictureEvent::pictureInPictureWindow() const {
const {
return picture_in_picture_window_.Get(); return picture_in_picture_window_.Get();
} }
EnterPictureInPictureEvent::EnterPictureInPictureEvent( PictureInPictureEvent::PictureInPictureEvent(
AtomicString const& type, AtomicString const& type,
PictureInPictureWindow* picture_in_picture_window) PictureInPictureWindow* picture_in_picture_window)
: Event(type, Bubbles::kYes, Cancelable::kNo), : Event(type, Bubbles::kYes, Cancelable::kNo),
picture_in_picture_window_(picture_in_picture_window) {} picture_in_picture_window_(picture_in_picture_window) {}
EnterPictureInPictureEvent::EnterPictureInPictureEvent( PictureInPictureEvent::PictureInPictureEvent(
AtomicString const& type, AtomicString const& type,
const EnterPictureInPictureEventInit* initializer) const PictureInPictureEventInit* initializer)
: Event(type, initializer), : Event(type, initializer),
picture_in_picture_window_(initializer->pictureInPictureWindow()) {} picture_in_picture_window_(initializer->pictureInPictureWindow()) {}
void EnterPictureInPictureEvent::Trace(Visitor* visitor) const { void PictureInPictureEvent::Trace(Visitor* visitor) const {
visitor->Trace(picture_in_picture_window_); visitor->Trace(picture_in_picture_window_);
Event::Trace(visitor); Event::Trace(visitor);
} }
......
// Copyright 2018 The Chromium Authors. All rights reserved. // Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_ENTER_PICTURE_IN_PICTURE_EVENT_H_ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_EVENT_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_ENTER_PICTURE_IN_PICTURE_EVENT_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_EVENT_H_
#include "third_party/blink/renderer/bindings/modules/v8/v8_enter_picture_in_picture_event_init.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_picture_in_picture_event_init.h"
#include "third_party/blink/renderer/modules/event_modules.h" #include "third_party/blink/renderer/modules/event_modules.h"
#include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h" #include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink { namespace blink {
// An EnterPictureInPictureEvent is a subclass of Event with an additional // An PictureInPictureEvent is a subclass of Event with an additional
// attribute that points to the Picture-in-Picture window. // attribute that points to the Picture-in-Picture window.
class MODULES_EXPORT EnterPictureInPictureEvent final : public Event { class MODULES_EXPORT PictureInPictureEvent final : public Event {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
static EnterPictureInPictureEvent* Create(const AtomicString&, static PictureInPictureEvent* Create(const AtomicString&,
PictureInPictureWindow*); PictureInPictureWindow*);
static EnterPictureInPictureEvent* Create( static PictureInPictureEvent* Create(const AtomicString&,
const AtomicString&, const PictureInPictureEventInit*);
const EnterPictureInPictureEventInit*);
EnterPictureInPictureEvent(AtomicString const&, PictureInPictureWindow*); PictureInPictureEvent(AtomicString const&, PictureInPictureWindow*);
EnterPictureInPictureEvent(AtomicString const&, PictureInPictureEvent(AtomicString const&, const PictureInPictureEventInit*);
const EnterPictureInPictureEventInit*);
PictureInPictureWindow* pictureInPictureWindow() const; PictureInPictureWindow* pictureInPictureWindow() const;
...@@ -38,4 +36,4 @@ class MODULES_EXPORT EnterPictureInPictureEvent final : public Event { ...@@ -38,4 +36,4 @@ class MODULES_EXPORT EnterPictureInPictureEvent final : public Event {
} // namespace blink } // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_ENTER_PICTURE_IN_PICTURE_EVENT_H_ #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_EVENT_H_
// Copyright 2018 The Chromium Authors. All rights reserved. // Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// https://wicg.github.io/picture-in-picture/#enterpictureinpictureevent // https://wicg.github.io/picture-in-picture/#pictureinpictureevent
[ [
RuntimeEnabled=PictureInPictureAPI, RuntimeEnabled=PictureInPictureAPI,
Exposed=Window Exposed=Window
] interface EnterPictureInPictureEvent : Event { ] interface PictureInPictureEvent : Event {
constructor(DOMString type, EnterPictureInPictureEventInit eventInitDict); constructor(DOMString type, PictureInPictureEventInit eventInitDict);
[SameObject] readonly attribute PictureInPictureWindow pictureInPictureWindow; [SameObject] readonly attribute PictureInPictureWindow pictureInPictureWindow;
}; };
// Copyright 2018 The Chromium Authors. All rights reserved. // Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// https://wicg.github.io/picture-in-picture/#dictdef-enterpictureinpictureeventinit // https://wicg.github.io/picture-in-picture/#dictdef-pictureinpictureeventinit
dictionary EnterPictureInPictureEventInit : EventInit { dictionary PictureInPictureEventInit : EventInit {
required PictureInPictureWindow pictureInPictureWindow; required PictureInPictureWindow pictureInPictureWindow;
}; };
...@@ -32,11 +32,11 @@ interface PictureInPictureWindow : EventTarget { ...@@ -32,11 +32,11 @@ interface PictureInPictureWindow : EventTarget {
}; };
[Exposed=Window] [Exposed=Window]
interface EnterPictureInPictureEvent : Event { interface PictureInPictureEvent : Event {
constructor(DOMString type, EnterPictureInPictureEventInit eventInitDict); constructor(DOMString type, PictureInPictureEventInit eventInitDict);
[SameObject] readonly attribute PictureInPictureWindow pictureInPictureWindow; [SameObject] readonly attribute PictureInPictureWindow pictureInPictureWindow;
}; };
dictionary EnterPictureInPictureEventInit : EventInit { dictionary PictureInPictureEventInit : EventInit {
required PictureInPictureWindow pictureInPictureWindow; required PictureInPictureWindow pictureInPictureWindow;
}; };
...@@ -10,9 +10,16 @@ ...@@ -10,9 +10,16 @@
<body></body> <body></body>
<script> <script>
promise_test(async t => { promise_test(async t => {
let pictureInPictureWindow;
const video = await loadVideo(); const video = await loadVideo();
video.addEventListener('enterpictureinpicture', t.step_func_done(event => {
pictureInPictureWindow = event.pictureInPictureWindow;
}));
video.addEventListener('leavepictureinpicture', t.step_func_done(event => { video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
assert_equals(pictureInPictureWindow, event.pictureInPictureWindow);
assert_equals(event.target, video); assert_equals(event.target, video);
assert_equals(event.bubbles, true); assert_equals(event.bubbles, true);
assert_equals(event.cancelable, false); assert_equals(event.cancelable, false);
...@@ -25,9 +32,15 @@ promise_test(async t => { ...@@ -25,9 +32,15 @@ promise_test(async t => {
}, 'leavepictureinpicture event is fired if document.exitPictureInPicture'); }, 'leavepictureinpicture event is fired if document.exitPictureInPicture');
promise_test(async t => { promise_test(async t => {
let pictureInPictureWindow;
const video = await loadVideo(); const video = await loadVideo();
video.addEventListener('enterpictureinpicture', t.step_func_done(event => {
pictureInPictureWindow = event.pictureInPictureWindow;
}));
video.addEventListener('leavepictureinpicture', t.step_func_done(event => { video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
assert_equals(pictureInPictureWindow, event.pictureInPictureWindow);
assert_equals(event.target, video); assert_equals(event.target, video);
assert_equals(event.bubbles, true); assert_equals(event.bubbles, true);
assert_equals(event.cancelable, false); assert_equals(event.cancelable, false);
......
...@@ -1910,10 +1910,6 @@ interface ElementInternals ...@@ -1910,10 +1910,6 @@ interface ElementInternals
setter ariaValueMin setter ariaValueMin
setter ariaValueNow setter ariaValueNow
setter ariaValueText setter ariaValueText
interface EnterPictureInPictureEvent : Event
attribute @@toStringTag
getter pictureInPictureWindow
method constructor
interface ErrorEvent : Event interface ErrorEvent : Event
attribute @@toStringTag attribute @@toStringTag
getter colno getter colno
...@@ -5135,6 +5131,10 @@ interface PhotoCapabilities ...@@ -5135,6 +5131,10 @@ interface PhotoCapabilities
getter imageWidth getter imageWidth
getter redEyeReduction getter redEyeReduction
method constructor method constructor
interface PictureInPictureEvent : Event
attribute @@toStringTag
getter pictureInPictureWindow
method constructor
interface PictureInPictureWindow : EventTarget interface PictureInPictureWindow : EventTarget
attribute @@toStringTag attribute @@toStringTag
getter height getter height
......
...@@ -2283,10 +2283,6 @@ interface EncodedVideoChunk ...@@ -2283,10 +2283,6 @@ interface EncodedVideoChunk
getter timestamp getter timestamp
getter type getter type
method constructor method constructor
interface EnterPictureInPictureEvent : Event
attribute @@toStringTag
getter pictureInPictureWindow
method constructor
interface ErrorEvent : Event interface ErrorEvent : Event
attribute @@toStringTag attribute @@toStringTag
getter colno getter colno
...@@ -6066,6 +6062,10 @@ interface PhotoCapabilities ...@@ -6066,6 +6062,10 @@ interface PhotoCapabilities
getter imageWidth getter imageWidth
getter redEyeReduction getter redEyeReduction
method constructor method constructor
interface PictureInPictureEvent : Event
attribute @@toStringTag
getter pictureInPictureWindow
method constructor
interface PictureInPictureWindow : EventTarget interface PictureInPictureWindow : EventTarget
attribute @@toStringTag attribute @@toStringTag
getter height getter height
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment