Bug 1521964 - Add privileged HTMLVideoElement.cloneElementVisually WebIDL method. r=jya,Ehsan,smaug
authorMike Conley <mconley@mozilla.com>
Fri, 01 Mar 2019 22:36:40 +0000
changeset 519921 45807d96ca7f5378567115595b98db0d2f3cd0bc
parent 519920 b7a51e2d1d66f6cc8a9792741dd911014a9e0db3
child 519922 e5e08269a5467ad0d8653645318a9d7a04954073
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya, Ehsan, smaug
bugs1521964
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1521964 - Add privileged HTMLVideoElement.cloneElementVisually WebIDL method. r=jya,Ehsan,smaug Differential Revision: https://phabricator.services.mozilla.com/D20023
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/html/HTMLVideoElement.cpp
dom/html/HTMLVideoElement.h
dom/webidl/HTMLVideoElement.webidl
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2051,16 +2051,17 @@ void HTMLMediaElement::ResetState() {
   if (mVideoFrameContainer) {
     mVideoFrameContainer->ForgetElement();
     mVideoFrameContainer = nullptr;
   }
 }
 
 void HTMLMediaElement::SelectResourceWrapper() {
   SelectResource();
+  MaybeBeginCloningVisually();
   mIsRunningSelectResource = false;
   mHaveQueuedSelectResource = false;
   mIsDoingExplicitLoad = false;
 }
 
 void HTMLMediaElement::SelectResource() {
   if (!mSrcAttrStream && !HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
       !HasSourceChildren(this)) {
@@ -2178,16 +2179,17 @@ void HTMLMediaElement::NotifyMediaTrackE
     if (aTrack->AsVideoTrack()) {
       MOZ_ASSERT(!mSelectedVideoStreamTrack);
       MOZ_ASSERT(!mVideoFrameListener);
 
       mSelectedVideoStreamTrack = aTrack->AsVideoTrack()->GetVideoStreamTrack();
       VideoFrameContainer* container = GetVideoFrameContainer();
       if (mSrcStreamIsPlaying && container) {
         mSelectedVideoStreamTrack->AddVideoOutput(container);
+        MaybeBeginCloningVisually();
       }
       HTMLVideoElement* self = static_cast<HTMLVideoElement*>(this);
       if (self->VideoWidth() <= 1 && self->VideoHeight() <= 1) {
         // MediaInfo uses dummy values of 1 for width and height to
         // mark video as valid. We need a new stream size listener
         // if size is 0x0 or 1x1.
         mVideoFrameListener = new VideoFrameListener(this);
         mSelectedVideoStreamTrack->AddDirectListener(mVideoFrameListener);
@@ -4586,16 +4588,18 @@ nsresult HTMLMediaElement::FinishDecoder
 
   if (!mPaused) {
     SetPlayedOrSeeked(true);
     if (!mPausedForInactiveDocumentOrChannel) {
       mDecoder->Play();
     }
   }
 
+  MaybeBeginCloningVisually();
+
   return NS_OK;
 }
 
 class HTMLMediaElement::MediaStreamTrackListener
     : public DOMMediaStream::TrackListener {
  public:
   explicit MediaStreamTrackListener(HTMLMediaElement* aElement)
       : mElement(aElement) {}
@@ -4676,16 +4680,17 @@ void HTMLMediaElement::UpdateSrcMediaStr
       NS_WARNING(
           "setSinkId() when playing a MediaStream is not supported yet and "
           "will be ignored");
     }
 
     VideoFrameContainer* container = GetVideoFrameContainer();
     if (mSelectedVideoStreamTrack && container) {
       mSelectedVideoStreamTrack->AddVideoOutput(container);
+      MaybeBeginCloningVisually();
     }
 
     SetCapturedOutputStreamsEnabled(true);  // Unmute
     // If the input is a media stream, we don't check its data and always regard
     // it as audible when it's playing.
     SetAudibleState(true);
   } else {
     if (stream) {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -293,17 +293,17 @@ class HTMLMediaElement : public nsGeneri
   void NotifyDecoderPrincipalChanged() final;
 
   void GetEMEInfo(nsString& aEMEInfo);
 
   class StreamCaptureTrackSource;
 
   // Update the visual size of the media. Called from the decoder on the
   // main thread when/if the size changes.
-  void UpdateMediaSize(const nsIntSize& aSize);
+  virtual void UpdateMediaSize(const nsIntSize& aSize);
   // Like UpdateMediaSize, but only updates the size if no size has yet
   // been set.
   void UpdateInitialMediaSize(const nsIntSize& aSize);
 
   void Invalidate(bool aImageSizeChanged, Maybe<nsIntSize>& aNewIntrinsicSize,
                   bool aForceInvalidate) override;
 
   // Returns the CanPlayStatus indicating if we can handle the
@@ -1715,16 +1715,18 @@ class HTMLMediaElement : public nsGeneri
     uint32_t mCount;
   };
 
  private:
   already_AddRefed<PlayPromise> CreatePlayPromise(ErrorResult& aRv) const;
 
   void UpdateHadAudibleAutoplayState();
 
+  virtual void MaybeBeginCloningVisually(){};
+
   /**
    * This function is called by AfterSetAttr and OnAttrSetButNotChanged.
    * It will not be called if the value is being unset.
    *
    * @param aNamespaceID the namespace of the attr being set
    * @param aName the localname of the attribute being set
    * @param aNotify Whether we plan to notify document observers.
    */
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -20,22 +20,24 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 
 #include "nsITimer.h"
 
 #include "FrameStatistics.h"
 #include "MediaError.h"
 #include "MediaDecoder.h"
+#include "MediaDecoderStateMachine.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "mozilla/dom/VideoPlaybackQuality.h"
+#include "mozilla/dom/VideoStreamTrack.h"
 
 #include <algorithm>
 #include <limits>
 
 nsGenericHTMLElement* NS_NewHTMLVideoElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
     mozilla::dom::FromParser aFromParser) {
   mozilla::dom::HTMLVideoElement* element =
@@ -61,28 +63,51 @@ nsresult HTMLVideoElement::Clone(mozilla
     kungFuDeathGrip.swap(*aResult);
   }
   return rv;
 }
 
 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLVideoElement,
                                                HTMLMediaElement)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLVideoElement, HTMLMediaElement,
-                                   mVisualCloneTarget, mVisualCloneSource)
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLVideoElement)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLVideoElement,
+                                                HTMLMediaElement)
+  if (tmp->mVisualCloneTarget) {
+    tmp->EndCloningVisually();
+  }
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneTarget)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneSource)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLVideoElement,
+                                                  HTMLMediaElement)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneTarget)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneSource)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 HTMLVideoElement::HTMLVideoElement(already_AddRefed<NodeInfo>&& aNodeInfo)
     : HTMLMediaElement(std::move(aNodeInfo)), mIsOrientationLocked(false) {
   DecoderDoctorLogger::LogConstruction(this);
 }
 
 HTMLVideoElement::~HTMLVideoElement() {
   DecoderDoctorLogger::LogDestruction(this);
 }
 
+void HTMLVideoElement::UpdateMediaSize(const nsIntSize& aSize) {
+  HTMLMediaElement::UpdateMediaSize(aSize);
+  // If we have a clone target, we should update its size as well.
+  if (mVisualCloneTarget) {
+    Maybe<nsIntSize> newSize = Some(aSize);
+    mVisualCloneTarget->Invalidate(true, newSize, true);
+  }
+}
+
 nsresult HTMLVideoElement::GetVideoSize(nsIntSize* size) {
   if (!mMediaInfo.HasVideo()) {
     return NS_ERROR_FAILURE;
   }
 
   if (mDisableVideo) {
     return NS_ERROR_FAILURE;
   }
@@ -134,16 +159,28 @@ HTMLVideoElement::IsAttributeMapped(cons
   return FindAttributeDependence(aAttribute, map);
 }
 
 nsMapRuleToAttributesFunc HTMLVideoElement::GetAttributeMappingFunction()
     const {
   return &MapAttributesIntoRule;
 }
 
+void HTMLVideoElement::UnbindFromTree(bool aDeep, bool aNullParent) {
+  if (mVisualCloneSource) {
+    mVisualCloneSource->EndCloningVisually();
+    SetVisualCloneSource(nullptr);
+  } else if (mVisualCloneTarget) {
+    mVisualCloneTarget->SetVisualCloneSource(nullptr);
+    EndCloningVisually();
+  }
+
+  HTMLMediaElement::UnbindFromTree(aDeep, aNullParent);
+}
+
 nsresult HTMLVideoElement::SetAcceptHeader(nsIHttpChannel* aChannel) {
   nsAutoCString value(
       "video/webm,"
       "video/ogg,"
       "video/*;q=0.9,"
       "application/ogg;q=0.7,"
       "audio/*;q=0.6,*/*;q=0.5");
 
@@ -391,10 +428,78 @@ double HTMLVideoElement::TotalPlayTime()
         total += now - mCurrentPlayRangeStart;
       }
     }
   }
 
   return total;
 }
 
+void HTMLVideoElement::CloneElementVisually(HTMLVideoElement& aTargetVideo,
+                                            ErrorResult& rv) {
+  MOZ_ASSERT(!mUnboundFromTree,
+             "Can't clone a video that's not bound to a DOM tree.");
+  MOZ_ASSERT(!aTargetVideo.mUnboundFromTree,
+             "Can't clone to a video that's not bound to a DOM tree.");
+  if (mUnboundFromTree || aTargetVideo.mUnboundFromTree) {
+    rv.Throw(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  if (!SetVisualCloneTarget(&aTargetVideo)) {
+    rv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  if (!aTargetVideo.SetVisualCloneSource(this)) {
+    mVisualCloneTarget = nullptr;
+    rv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  aTargetVideo.SetMediaInfo(mMediaInfo);
+
+  MaybeBeginCloningVisually();
+}
+
+void HTMLVideoElement::MaybeBeginCloningVisually() {
+  if (!mVisualCloneTarget) {
+    return;
+  }
+
+  if (mDecoder) {
+    MediaDecoderStateMachine* mdsm = mDecoder->GetStateMachine();
+    VideoFrameContainer* container =
+        mVisualCloneTarget->GetVideoFrameContainer();
+    if (mdsm && container) {
+      mdsm->SetSecondaryVideoContainer(container);
+    }
+  } else if (mSrcStream) {
+    VideoFrameContainer* container =
+        mVisualCloneTarget->GetVideoFrameContainer();
+    if (container && mSelectedVideoStreamTrack) {
+      mSelectedVideoStreamTrack->AddVideoOutput(container);
+    }
+  }
+}
+
+void HTMLVideoElement::EndCloningVisually() {
+  MOZ_ASSERT(mVisualCloneTarget);
+
+  if (mDecoder) {
+    MediaDecoderStateMachine* mdsm = mDecoder->GetStateMachine();
+    if (mdsm) {
+      mdsm->SetSecondaryVideoContainer(nullptr);
+    }
+  } else if (mSrcStream) {
+    VideoFrameContainer* container =
+        mVisualCloneTarget->GetVideoFrameContainer();
+    if (container && mVisualCloneTarget->mSelectedVideoStreamTrack) {
+      mVisualCloneTarget->mSelectedVideoStreamTrack->RemoveVideoOutput(
+          container);
+    }
+  }
+
+  mVisualCloneTarget = nullptr;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/html/HTMLVideoElement.h
+++ b/dom/html/HTMLVideoElement.h
@@ -43,20 +43,25 @@ class HTMLVideoElement final : public HT
 
   static void InitStatics();
 
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction()
       const override;
 
   virtual nsresult Clone(NodeInfo*, nsINode** aResult) const override;
 
+  virtual void UnbindFromTree(bool aDeep = true,
+                              bool aNullParent = true) override;
+
   // Set size with the current video frame's height and width.
   // If there is no video frame, returns NS_ERROR_FAILURE.
   nsresult GetVideoSize(nsIntSize* size);
 
+  virtual void UpdateMediaSize(const nsIntSize& aSize) override;
+
   virtual nsresult SetAcceptHeader(nsIHttpChannel* aChannel) override;
 
   // Element
   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
 
   // WebIDL
 
   uint32_t Width() const { return GetIntAttr(nsGkAtoms::width, 0); }
@@ -124,16 +129,18 @@ class HTMLVideoElement final : public HT
   bool MozOrientationLockEnabled() const {
     return StaticPrefs::MediaVideocontrolsLockVideoOrientation();
   }
 
   bool MozIsOrientationLocked() const { return mIsOrientationLocked; }
 
   void SetMozIsOrientationLocked(bool aLock) { mIsOrientationLocked = aLock; }
 
+  void CloneElementVisually(HTMLVideoElement& aTarget, ErrorResult& rv);
+
  protected:
   virtual ~HTMLVideoElement();
 
   virtual JSObject* WrapNode(JSContext* aCx,
                              JS::Handle<JSObject*> aGivenProto) override;
 
   /**
    * We create video wakelock when the video is playing and release it when
@@ -172,14 +179,17 @@ class HTMLVideoElement final : public HT
   // SetVisualCloneTarget() instead.
   RefPtr<HTMLVideoElement> mVisualCloneSource;
 
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                     MappedDeclarations&);
 
   static bool IsVideoStatsEnabled();
   double TotalPlayTime() const;
+
+  virtual void MaybeBeginCloningVisually() override;
+  void EndCloningVisually();
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_HTMLVideoElement_h
--- a/dom/webidl/HTMLVideoElement.webidl
+++ b/dom/webidl/HTMLVideoElement.webidl
@@ -48,15 +48,21 @@ partial interface HTMLVideoElement {
 
   // Attributes for builtin video controls to lock screen orientation.
   // True if video controls should lock orientation when fullscreen.
   [Pref="media.videocontrols.lock-video-orientation", Func="IsChromeOrXBLOrUAWidget"]
     readonly attribute boolean mozOrientationLockEnabled;
   // True if screen orientation is locked by video controls.
   [Pref="media.videocontrols.lock-video-orientation", Func="IsChromeOrXBLOrUAWidget"]
     attribute boolean mozIsOrientationLocked;
+
+  // Clones the frames playing in this <video> to the target. Cloning
+  // when either node is removed from their DOM trees. Throws if one or
+  // both <video> elements are not attached to a DOM tree.
+  [Throws, Func="IsChromeOrXBLOrUAWidget"]
+    void cloneElementVisually(HTMLVideoElement target);
 };
 
 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#idl-def-HTMLVideoElement
 partial interface HTMLVideoElement {
   [Func="mozilla::dom::MediaSource::Enabled", NewObject]
   VideoPlaybackQuality getVideoPlaybackQuality();
 };