diff --git a/Source/WebCore/Modules/modern-media-controls/controls/icon-service.js b/Source/WebCore/Modules/modern-media-controls/controls/icon-service.js index b9769e5ea678a..96fad7495801f 100644 --- a/Source/WebCore/Modules/modern-media-controls/controls/icon-service.js +++ b/Source/WebCore/Modules/modern-media-controls/controls/icon-service.js @@ -74,6 +74,15 @@ const iconService = new class IconService { } // Public + get shadowRoot() + { + return this.shadowRootWeakRef ? this.shadowRootWeakRef.deref() : null; + } + + set shadowRoot(shadowRoot) + { + this.shadowRootWeakRef = new WeakRef(shadowRoot); + } imageForIconAndLayoutTraits(icon, layoutTraits) { diff --git a/Source/WebCore/Modules/modern-media-controls/media/media-controller.js b/Source/WebCore/Modules/modern-media-controls/media/media-controller.js index 6279fa7796c32..8ed8708690836 100644 --- a/Source/WebCore/Modules/modern-media-controls/media/media-controller.js +++ b/Source/WebCore/Modules/modern-media-controls/media/media-controller.js @@ -25,11 +25,10 @@ class MediaController { - constructor(shadowRoot, media, host) { - this.shadowRoot = shadowRoot; - this.media = media; + this.shadowRootWeakRef = new WeakRef(shadowRoot); + this.mediaWeakRef = new WeakRef(media); this.host = host; this.fullscreenChangeEventType = media.webkitSupportsPresentationMode ? "webkitpresentationmodechanged" : "webkitfullscreenchange"; @@ -65,6 +64,16 @@ class MediaController } // Public + get media() + { + return this.mediaWeakRef ? this.mediaWeakRef.deref() : null; + } + + get shadowRoot() + { + + return this.shadowRootWeakRef ? this.shadowRootWeakRef.deref() : null; + } get isAudio() { @@ -91,6 +100,9 @@ class MediaController get isFullscreen() { + if (!this.media) + return false; + return this.media.webkitSupportsPresentationMode ? this.media.webkitPresentationMode === "fullscreen" : this.media.webkitDisplayingFullscreen; } @@ -205,6 +217,24 @@ class MediaController } } + // HTMLMediaElement + + deinitialize() + { + this.shadowRoot.removeChild(this.container); + return true; + } + + reinitialize(shadowRoot, media, host) + { + iconService.shadowRoot = shadowRoot; + this.shadowRootWeakRef = new WeakRef(shadowRoot); + this.mediaWeakRef = new WeakRef(media); + this.host = host; + shadowRoot.appendChild(this.container); + return true; + } + // Private _supportingObjectClasses() diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp index 7a0e66aa6e9b4..9c6262a49d787 100644 --- a/Source/WebCore/html/HTMLMediaElement.cpp +++ b/Source/WebCore/html/HTMLMediaElement.cpp @@ -294,6 +294,8 @@ String convertEnumerationToString(HTMLMediaElement::TextTrackVisibilityCheckType return values[static_cast(enumerationValue)]; } +static JSC::JSValue controllerJSValue(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject&, HTMLMediaElement&); + class TrackDisplayUpdateScope { public: TrackDisplayUpdateScope(HTMLMediaElement& element) @@ -444,6 +446,7 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum , m_parsingInProgress(createdByParser) , m_elementIsHidden(document.hidden()) , m_creatingControls(false) + , m_partiallyDeinitialized(false) , m_receivedLayoutSizeChanged(false) , m_hasEverNotifiedAboutPlaying(false) , m_hasEverHadAudio(false) @@ -877,6 +880,37 @@ void HTMLMediaElement::pauseAfterDetachedTask() if (m_videoFullscreenMode == VideoFullscreenModeStandard) exitFullscreen(); +#if ENABLE(MODERN_MEDIA_CONTROLS) + if (!m_creatingControls && !m_partiallyDeinitialized && m_mediaControlsHost) { + // Call MediaController.deinitialize() to get rid of circular references. + m_partiallyDeinitialized = setupAndCallJS([this](JSDOMGlobalObject& globalObject, JSC::JSGlobalObject& lexicalGlobalObject, ScriptController&, DOMWrapperWorld&) { + auto& vm = globalObject.vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto controllerValue = controllerJSValue(lexicalGlobalObject, globalObject, *this); + RETURN_IF_EXCEPTION(scope, false); + auto* controllerObject = controllerValue.toObject(&lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, false); + + auto functionValue = controllerObject->get(&lexicalGlobalObject, JSC::Identifier::fromString(vm, "deinitialize"_s)); + if (UNLIKELY(scope.exception()) || functionValue.isUndefinedOrNull()) + return false; + + auto* function = functionValue.toObject(&lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, false); + + auto callData = JSC::getCallData(function); + if (callData.type == JSC::CallData::Type::None) + return false; + + auto resultValue = JSC::call(&lexicalGlobalObject, function, callData, controllerObject, JSC::MarkedArgumentBuffer()); + RETURN_IF_EXCEPTION(scope, false); + + return resultValue.toBoolean(&lexicalGlobalObject); + }); + } +#endif // ENABLE(MODERN_MEDIA_CONTROLS) + if (!m_player) return; @@ -4642,6 +4676,46 @@ void HTMLMediaElement::ensureMediaControlsShadowRoot() if (m_creatingControls) return; + if (m_partiallyDeinitialized) { + m_partiallyDeinitialized = !setupAndCallJS([this](JSDOMGlobalObject& globalObject, JSC::JSGlobalObject& lexicalGlobalObject, ScriptController&, DOMWrapperWorld&) { + auto& vm = globalObject.vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto controllerValue = controllerJSValue(lexicalGlobalObject, globalObject, *this); + RETURN_IF_EXCEPTION(scope, false); + auto* controllerObject = controllerValue.toObject(&lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, false); + + auto functionValue = controllerObject->get(&lexicalGlobalObject, JSC::Identifier::fromString(vm, "reinitialize"_s)); + if (UNLIKELY(scope.exception()) || functionValue.isUndefinedOrNull()) + return false; + + if (!m_mediaControlsHost) + m_mediaControlsHost = MediaControlsHost::create(*this); + + auto mediaJSWrapper = toJS(&lexicalGlobalObject, &globalObject, *this); + auto mediaControlsHostJSWrapper = toJS(&lexicalGlobalObject, &globalObject, *m_mediaControlsHost.copyRef()); + + JSC::MarkedArgumentBuffer argList; + argList.append(toJS(&lexicalGlobalObject, &globalObject, Ref { ensureUserAgentShadowRoot() })); + argList.append(mediaJSWrapper); + argList.append(mediaControlsHostJSWrapper); + ASSERT(!argList.hasOverflowed()); + + auto* function = functionValue.toObject(&lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, false); + + auto callData = JSC::getCallData(function); + if (callData.type == JSC::CallData::Type::None) + return false; + + auto resultValue = JSC::call(&lexicalGlobalObject, function, callData, controllerObject, argList); + RETURN_IF_EXCEPTION(scope, false); + + return resultValue.toBoolean(&lexicalGlobalObject); + }); + } + m_creatingControls = true; ensureUserAgentShadowRoot(); m_creatingControls = false; @@ -7668,6 +7742,9 @@ bool HTMLMediaElement::ensureMediaControlsInjectedScript() if (mediaControlsScripts.isEmpty()) return false; + if (m_partiallyDeinitialized) + return true; + return setupAndCallJS([mediaControlsScripts = WTFMove(mediaControlsScripts)](JSDOMGlobalObject& globalObject, JSC::JSGlobalObject& lexicalGlobalObject, ScriptController& scriptController, DOMWrapperWorld& world) { auto& vm = globalObject.vm(); auto scope = DECLARE_CATCH_SCOPE(vm); diff --git a/Source/WebCore/html/HTMLMediaElement.h b/Source/WebCore/html/HTMLMediaElement.h index ceeb760928b1d..219c6ef3e6823 100644 --- a/Source/WebCore/html/HTMLMediaElement.h +++ b/Source/WebCore/html/HTMLMediaElement.h @@ -1141,6 +1141,7 @@ class HTMLMediaElement bool m_elementIsHidden : 1; bool m_elementWasRemovedFromDOM : 1; bool m_creatingControls : 1; + bool m_partiallyDeinitialized : 1; bool m_receivedLayoutSizeChanged : 1; bool m_hasEverNotifiedAboutPlaying : 1;