Bug 1431255 - Part II, Create a Shadow Root in HTMLMediaElement when enabled, skipping <xul:videocontrols> r=dholbert,smaug

This prevents XBL binding from being attached, and create the Shadow Root to
host controls to be created by the script.

Shadow Root and the JS controls are lazily constructed when the controls
attribute is set.

Set nsVideoFrame as dynamic-leaf so it will ignore content child frames when
the controls are XBL anonymous content, and handles child frames from controls
in the Shadow DOM. The content nodes are still ignored since there is no
<slot>s in our Shadow DOM.

MozReview-Commit-ID: 3hk41iMa07n

--HG--
extra : rebase_source : f6f8a3facc9d83f5626cf5f3b4e3fa27438a8a8f
This commit is contained in:
Timothy Guan-tin Chien 2018-06-27 11:12:38 -07:00
parent e1bae5c835
commit 8cc930296b
7 changed files with 120 additions and 16 deletions

View file

@ -4510,6 +4510,17 @@ HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID,
if (mDecoder) {
mDecoder->SetLooping(!!aValue);
}
} else if (nsContentUtils::IsUAWidgetEnabled() &&
aName == nsGkAtoms::controls &&
IsInComposedDoc()) {
AsyncEventDispatcher* dispatcher =
new AsyncEventDispatcher(this,
NS_LITERAL_STRING("UAWidgetAttributeChanged"),
CanBubble::eYes,
ChromeOnlyDispatch::eYes);
// This has to happen at this tick so that UA Widget could respond
// before returning to content script.
dispatcher->RunDOMEventWhenSafe();
}
}
@ -4555,6 +4566,34 @@ HTMLMediaElement::BindToTree(nsIDocument* aDocument,
nsresult rv = nsGenericHTMLElement::BindToTree(
aDocument, aParent, aBindingParent);
if (nsContentUtils::IsUAWidgetEnabled() && IsInComposedDoc()) {
// Construct Shadow Root so web content can be hidden in the DOM.
AttachAndSetUAShadowRoot();
#ifdef ANDROID
AsyncEventDispatcher* dispatcher =
new AsyncEventDispatcher(this,
NS_LITERAL_STRING("UAWidgetBindToTree"),
CanBubble::eYes,
ChromeOnlyDispatch::eYes);
dispatcher->RunDOMEventWhenSafe();
#else
// We don't want to call into JS if the website never asks for native
// video controls.
// If controls attribute is set later, controls is constructed lazily
// with the UAWidgetAttributeChanged event.
// This only applies to Desktop because on Fennec we would need to show
// an UI if the video is blocked.
if (Controls()) {
AsyncEventDispatcher* dispatcher =
new AsyncEventDispatcher(this,
NS_LITERAL_STRING("UAWidgetBindToTree"),
CanBubble::eYes,
ChromeOnlyDispatch::eYes);
dispatcher->RunDOMEventWhenSafe();
}
#endif
}
mUnboundFromTree = false;
if (aDocument) {
@ -4804,6 +4843,13 @@ HTMLMediaElement::UnbindFromTree(bool aDeep, bool aNullParent)
MOZ_ASSERT(IsHidden());
NotifyDecoderActivityChanges();
AsyncEventDispatcher* dispatcher =
new AsyncEventDispatcher(this,
NS_LITERAL_STRING("UAWidgetUnbindFromTree"),
CanBubble::eYes,
ChromeOnlyDispatch::eYes);
dispatcher->RunDOMEventWhenSafe();
RefPtr<HTMLMediaElement> self(this);
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree", [self]() {
@ -4892,6 +4938,17 @@ HTMLMediaElement::AssertReadyStateIsNothing()
#endif
}
void
HTMLMediaElement::AttachAndSetUAShadowRoot()
{
if (GetShadowRoot()) {
return;
}
// Add a closed shadow root to host video controls
AttachShadowWithoutNameChecks(ShadowRootMode::Closed);
}
nsresult
HTMLMediaElement::InitializeDecoderAsClone(ChannelMediaDecoder* aOriginal)
{

View file

@ -1886,6 +1886,9 @@ private:
// For debugging bug 1407148.
void AssertReadyStateIsNothing();
// Attach UA Shadow Root if it is not attached.
void AttachAndSetUAShadowRoot();
};
// Check if the context is chrome or has the debugger or tabs permission

View file

@ -276,7 +276,7 @@ TextTrackManager::UpdateCueDisplay()
nsCOMPtr<nsIContent> overlay = videoFrame->GetCaptionOverlay();
nsCOMPtr<nsIContent> controls = videoFrame->GetVideoControls();
if (!overlay) {
if (!overlay || !controls) {
return;
}

View file

@ -1012,8 +1012,14 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
var controlBar;
var controlBarShown;
if (controls) {
controlBar = controls.ownerDocument.getAnonymousElementByAttribute(
controls, "anonid", "controlBar");
if (controls.localName == "videocontrols") {
// controls is a NAC; The control bar is in a XBL binding.
controlBar = controls.ownerDocument.getAnonymousElementByAttribute(
controls, "anonid", "controlBar");
} else {
// controls is a <div> that is the children of the UA Widget Shadow Root.
controlBar = controls.parentNode.getElementById("controlBar");
}
controlBarShown = controlBar ? !!controlBar.clientHeight : false;
}
@ -1381,7 +1387,7 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
}
if (self.state === "ID") {
// If there is no cue identifier, read the next line.
// If there is no cue identifier, read the next line.
if (line == "") {
return;
}

View file

@ -143,7 +143,7 @@ FRAME_ID(nsTextFrame, Text, Leaf)
FRAME_ID(nsTitleBarFrame, Box, NotLeaf)
FRAME_ID(nsTreeBodyFrame, LeafBox, Leaf)
FRAME_ID(nsTreeColFrame, Box, NotLeaf)
FRAME_ID(nsVideoFrame, HTMLVideo, Leaf)
FRAME_ID(nsVideoFrame, HTMLVideo, DynamicLeaf)
FRAME_ID(nsXULLabelFrame, XULLabel, NotLeaf)
FRAME_ID(nsXULScrollFrame, Scroll, NotLeaf)
FRAME_ID(ViewportFrame, Viewport, NotLeaf)

View file

@ -146,9 +146,11 @@ nsVideoFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
nsINode::ELEMENT_NODE);
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
NS_TrustedNewXULElement(getter_AddRefs(mVideoControls), nodeInfo.forget());
if (!aElements.AppendElement(mVideoControls))
return NS_ERROR_OUT_OF_MEMORY;
if (!nsContentUtils::IsUAWidgetEnabled()) {
NS_TrustedNewXULElement(getter_AddRefs(mVideoControls), nodeInfo.forget());
if (!aElements.AppendElement(mVideoControls))
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
@ -170,6 +172,19 @@ nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
}
}
nsIContent*
nsVideoFrame::GetVideoControls()
{
if (mVideoControls) {
return mVideoControls;
}
if (mContent->GetShadowRoot()) {
// The video controls <div> is the only child of the UA Widget Shadow Root.
return mContent->GetShadowRoot()->GetFirstChild();
}
return nullptr;
}
void
nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
{
@ -306,6 +321,8 @@ nsVideoFrame::Reflow(nsPresContext* aPresContext,
nsMargin borderPadding = aReflowInput.ComputedPhysicalBorderPadding();
nsIContent* videoControlsDiv = GetVideoControls();
// Reflow the child frames. We may have up to three: an image
// frame (for the poster image), a container frame for the controls,
// and a container frame for the caption.
@ -349,7 +366,7 @@ nsVideoFrame::Reflow(nsPresContext* aPresContext,
posterRenderRect.x, posterRenderRect.y, 0);
} else if (child->GetContent() == mCaptionDiv ||
child->GetContent() == mVideoControls) {
child->GetContent() == videoControlsDiv) {
// Reflow the caption and control bar frames.
WritingMode wm = child->GetWritingMode();
LogicalSize availableSize = aReflowInput.ComputedSize(wm);
@ -366,7 +383,7 @@ nsVideoFrame::Reflow(nsPresContext* aPresContext,
"We gave our child unconstrained available block-size, "
"so it should be complete!");
if (child->GetContent() == mVideoControls && isBSizeShrinkWrapping) {
if (child->GetContent() == videoControlsDiv && isBSizeShrinkWrapping) {
// Resolve our own BSize based on the controls' size in the same axis.
contentBoxBSize = myWM.IsOrthogonalTo(wm) ?
kidDesiredSize.ISize(wm) : kidDesiredSize.BSize(wm);
@ -375,11 +392,14 @@ nsVideoFrame::Reflow(nsPresContext* aPresContext,
FinishReflowChild(child, aPresContext,
kidDesiredSize, &kidReflowInput,
borderPadding.left, borderPadding.top, 0);
}
if (child->GetContent() == mVideoControls && child->GetSize() != oldChildSize) {
RefPtr<Runnable> event = new DispatchResizeToControls(child->GetContent());
nsContentUtils::AddScriptRunner(event);
if (child->GetContent() == videoControlsDiv && child->GetSize() != oldChildSize) {
RefPtr<Runnable> event = new DispatchResizeToControls(child->GetContent());
nsContentUtils::AddScriptRunner(event);
}
} else {
MOZ_ASSERT_UNREACHABLE("Extra child frame found in nsVideoFrame. "
"Possibly from stray whitespace around the videocontrols container element.");
}
}
@ -411,6 +431,23 @@ nsVideoFrame::Reflow(nsPresContext* aPresContext,
NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
}
/**
* nsVideoFrame should be a non-leaf frame when UA Widget is enabled,
* so the videocontrols container element inserted under the Shadow Root can be
* picked up. No frames will be generated from elements from the web content,
* given that they have been replaced by the Shadow Root without and <slots>
* element in the DOM tree.
*
* When the UA Widget is disabled, i.e. the videocontrols is bound as anonymous
* content with XBL, nsVideoFrame has to be a leaf so no frames from web content
* element will be generated.
*/
bool
nsVideoFrame::IsLeafDynamic() const
{
return !nsContentUtils::IsUAWidgetEnabled();
}
class nsDisplayVideo : public nsDisplayItem {
public:
nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame)

View file

@ -74,6 +74,8 @@ public:
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
bool IsLeafDynamic() const override;
#ifdef ACCESSIBILITY
mozilla::a11y::AccType AccessibleType() override;
#endif
@ -95,8 +97,7 @@ public:
bool ShouldDisplayPoster();
nsIContent *GetCaptionOverlay() { return mCaptionDiv; }
nsIContent *GetVideoControls() { return mVideoControls; }
nsIContent *GetVideoControls();
#ifdef DEBUG_FRAME_DUMP
nsresult GetFrameName(nsAString& aResult) const override;