--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -59,16 +59,17 @@ NS_IMPL_RELEASE_INHERITED(ShadowRoot, Do
ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode,
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
: DocumentFragment(aNodeInfo)
, DocumentOrShadowRoot(*this)
, mMode(aMode)
, mServoStyles(Servo_AuthorStyles_Create())
, mIsComposedDocParticipant(false)
+ , mIsUAWidget(false)
{
SetHost(aElement);
// Nodes in a shadow tree should never store a value
// in the subtree root pointer, nodes in the shadow tree
// track the subtree root using GetContainingShadow().
ClearSubtreeRootPointer();
@@ -114,16 +115,22 @@ ShadowRoot::SetIsComposedDocParticipant(
nsIDocument* doc = OwnerDoc();
if (IsComposedDocParticipant()) {
doc->AddComposedDocShadowRoot(*this);
} else {
doc->RemoveComposedDocShadowRoot(*this);
}
}
+void
+ShadowRoot::SetIsUAWidget(bool aIsUAWidget)
+{
+ mIsUAWidget = aIsUAWidget;
+}
+
JSObject*
ShadowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return mozilla::dom::ShadowRoot_Binding::Wrap(aCx, this, aGivenProto);
}
void
ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther)
@@ -544,16 +551,61 @@ ShadowRoot::GetInnerHTML(nsAString& aInn
}
void
ShadowRoot::SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError)
{
SetInnerHTMLInternal(aInnerHTML, aError);
}
+nsINode*
+ShadowRoot::ImportNodeAndAppendChildAt(nsINode& aParentNode,
+ nsINode& aNode,
+ bool aDeep,
+ mozilla::ErrorResult& rv)
+{
+ MOZ_ASSERT(mIsUAWidget);
+ MOZ_ASSERT(OwnerDoc());
+
+ if (!aParentNode.IsInUAWidget()) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ RefPtr<nsINode> node = OwnerDoc()->ImportNode(aNode, aDeep, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ return aParentNode.AppendChild(*node, rv);
+}
+
+nsINode*
+ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode,
+ const nsAString& aTagName,
+ mozilla::ErrorResult& rv) {
+ MOZ_ASSERT(mIsUAWidget);
+ MOZ_ASSERT(OwnerDoc());
+
+ if (!aParentNode.IsInUAWidget()) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ // This option is not exposed to UA Widgets
+ ElementCreationOptionsOrString options;
+
+ RefPtr<nsINode> node = OwnerDoc()->CreateElement(aTagName, options, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ return aParentNode.AppendChild(*node, rv);
+}
+
void
ShadowRoot::AttributeChanged(Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue)
{
if (aNameSpaceID != kNameSpaceID_None || aAttribute != nsGkAtoms::slot) {
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -2,16 +2,17 @@
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_shadowroot_h__
#define mozilla_dom_shadowroot_h__
+#include "mozilla/dom/DocumentBinding.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DocumentOrShadowRoot.h"
#include "mozilla/dom/NameSpaceConstants.h"
#include "mozilla/dom/ShadowRootBinding.h"
#include "mozilla/ServoBindings.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIdentifierMapEntry.h"
@@ -166,23 +167,43 @@ public:
// WebIDL methods.
using mozilla::dom::DocumentOrShadowRoot::GetElementById;
Element* GetActiveElement();
void GetInnerHTML(nsAString& aInnerHTML);
void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
+ /**
+ * These methods allow UA Widget to insert DOM elements into the Shadow ROM
+ * without putting their DOM reflectors to content scope first.
+ * The inserted DOM will have their reflectors in the UA Widget scope.
+ */
+ nsINode* ImportNodeAndAppendChildAt(nsINode& aParentNode,
+ nsINode& aNode,
+ bool aDeep, mozilla::ErrorResult& rv);
+
+ nsINode* CreateElementAndAppendChildAt(nsINode& aParentNode,
+ const nsAString& aTagName,
+ mozilla::ErrorResult& rv);
+
bool IsComposedDocParticipant() const
{
return mIsComposedDocParticipant;
}
void SetIsComposedDocParticipant(bool aIsComposedDocParticipant);
+ bool IsUAWidget() const
+ {
+ return mIsUAWidget;
+ }
+
+ void SetIsUAWidget(bool aIsUAWidget);
+
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
protected:
// FIXME(emilio): This will need to become more fine-grained.
void ApplicableRulesChanged();
virtual ~ShadowRoot();
@@ -199,15 +220,17 @@ protected:
nsClassHashtable<nsStringHashKey, SlotArray> mSlotMap;
// Flag to indicate whether the descendants of this shadow root are part of the
// composed document. Ideally, we would use a node flag on nodes to
// mark whether it is in the composed document, but we have run out of flags
// so instead we track it here.
bool mIsComposedDocParticipant;
+ bool mIsUAWidget;
+
nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_shadowroot_h__
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -4659,23 +4659,33 @@ nsINode::OwnerDocAsNode() const
// no really good way to include it.
template<typename T>
inline bool ShouldUseXBLScope(const T* aNode)
{
// TODO(emilio): Is the <svg:use> shadow tree check needed now?
return aNode->IsInAnonymousSubtree() && !aNode->IsInSVGUseShadowTree();
}
+template<typename T>
+inline bool ShouldUseUAWidgetScope(const T* aNode)
+{
+ return aNode->IsInUAWidget();
+}
+
inline mozilla::dom::ParentObject
nsINode::GetParentObject() const
{
mozilla::dom::ParentObject p(OwnerDoc());
- // Note that mUseXBLScope is a no-op for chrome, and other places where we
- // don't use XBL scopes.
- p.mUseXBLScope = ShouldUseXBLScope(this);
+ // Note that mReflectionScope is a no-op for chrome, and other places
+ // where we don't check this value.
+ if (ShouldUseXBLScope(this)) {
+ p.mReflectionScope = mozilla::dom::ReflectionScope::XBL;
+ } else if (ShouldUseUAWidgetScope(this)) {
+ p.mReflectionScope = mozilla::dom::ReflectionScope::UAWidget;
+ }
return p;
}
inline nsIDocument*
nsINode::AsDocument()
{
MOZ_ASSERT(IsDocument());
return static_cast<nsIDocument*>(this);
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -541,16 +541,26 @@ operator<<(std::ostream& aStream, const
SVGUseElement*
nsINode::DoGetContainingSVGUseShadowHost() const
{
MOZ_ASSERT(IsInShadowTree());
return SVGUseElement::FromNodeOrNull(AsContent()->GetContainingShadowHost());
}
+bool
+nsINode::IsInUAWidget() const
+{
+ if (!IsInShadowTree()) {
+ return false;
+ }
+ ShadowRoot* shadowRoot = AsContent()->GetContainingShadow();
+ return shadowRoot && shadowRoot->IsUAWidget();
+}
+
void
nsINode::GetNodeValueInternal(nsAString& aNodeValue)
{
SetDOMStringToNull(aNodeValue);
}
nsINode*
nsINode::RemoveChild(nsINode& aOldChild, ErrorResult& aError)
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -1236,16 +1236,18 @@ public:
mozilla::dom::SVGUseElement* GetContainingSVGUseShadowHost() const
{
if (!IsInShadowTree()) {
return nullptr;
}
return DoGetContainingSVGUseShadowHost();
}
+ bool IsInUAWidget() const;
+
// True for native anonymous content and for XBL content if the binding
// has chromeOnlyContent="true".
bool ChromeOnlyAccess() const
{
return HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE | NODE_CHROME_ONLY_ACCESS);
}
bool IsInShadowTree() const
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -516,42 +516,48 @@ GetWrapperCache(void* p)
// GetWrappeCache(void*) and GetWrapperCache(const ParentObject&).
template <template <typename> class SmartPtr, typename T>
inline nsWrapperCache*
GetWrapperCache(const SmartPtr<T>& aObject)
{
return GetWrapperCache(aObject.get());
}
+enum class ReflectionScope {
+ Content,
+ XBL,
+ UAWidget
+};
+
struct MOZ_STACK_CLASS ParentObject {
template<class T>
MOZ_IMPLICIT ParentObject(T* aObject) :
mObject(aObject),
mWrapperCache(GetWrapperCache(aObject)),
- mUseXBLScope(false)
+ mReflectionScope(ReflectionScope::Content)
{}
template<class T, template<typename> class SmartPtr>
MOZ_IMPLICIT ParentObject(const SmartPtr<T>& aObject) :
mObject(aObject.get()),
mWrapperCache(GetWrapperCache(aObject.get())),
- mUseXBLScope(false)
+ mReflectionScope(ReflectionScope::Content)
{}
ParentObject(nsISupports* aObject, nsWrapperCache* aCache) :
mObject(aObject),
mWrapperCache(aCache),
- mUseXBLScope(false)
+ mReflectionScope(ReflectionScope::Content)
{}
// We don't want to make this an nsCOMPtr because of performance reasons, but
// it's safe because ParentObject is a stack class.
nsISupports* const MOZ_NON_OWNING_REF mObject;
nsWrapperCache* const mWrapperCache;
- bool mUseXBLScope;
+ ReflectionScope mReflectionScope;
};
namespace binding_detail {
// Class for simple sequence arguments, only used internally by codegen.
template<typename T>
class AutoSequence : public AutoTArray<T, 16>
{
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1399,26 +1399,26 @@ GetParentPointer(T* aObject)
inline nsISupports*
GetParentPointer(const ParentObject& aObject)
{
return aObject.mObject;
}
template <typename T>
-inline bool
-GetUseXBLScope(T* aParentObject)
+inline mozilla::dom::ReflectionScope
+GetReflectionScope(T* aParentObject)
{
- return false;
+ return mozilla::dom::ReflectionScope::Content;
}
-inline bool
-GetUseXBLScope(const ParentObject& aParentObject)
+inline mozilla::dom::ReflectionScope
+GetReflectionScope(const ParentObject& aParentObject)
{
- return aParentObject.mUseXBLScope;
+ return aParentObject.mReflectionScope;
}
template<class T>
inline void
ClearWrapper(T* p, nsWrapperCache* cache, JSObject* obj)
{
JS::AutoAssertGCCallback inCallback;
@@ -1662,56 +1662,76 @@ struct WrapNativeHelper<T, false>
return obj;
}
};
// Finding the associated global for an object.
template<typename T>
static inline JSObject*
FindAssociatedGlobal(JSContext* cx, T* p, nsWrapperCache* cache,
- bool useXBLScope = false)
+ mozilla::dom::ReflectionScope scope = mozilla::dom::ReflectionScope::Content)
{
if (!p) {
return JS::CurrentGlobalOrNull(cx);
}
JSObject* obj = WrapNativeHelper<T>::Wrap(cx, p, cache);
if (!obj) {
return nullptr;
}
MOZ_ASSERT(JS::ObjectIsNotGray(obj));
// The object is never a CCW but it may not be in the current compartment of
// the JSContext.
obj = JS::GetNonCCWObjectGlobal(obj);
- if (!useXBLScope) {
- return obj;
+ switch (scope) {
+ case mozilla::dom::ReflectionScope::XBL: {
+ // If scope is set to XBLScope, it means that the canonical reflector for this
+ // native object should live in the content XBL scope. Note that we never put
+ // anonymous content inside an add-on scope.
+ if (xpc::IsInContentXBLScope(obj)) {
+ return obj;
+ }
+ JS::Rooted<JSObject*> rootedObj(cx, obj);
+ JSObject* xblScope = xpc::GetXBLScope(cx, rootedObj);
+ MOZ_ASSERT_IF(xblScope, JS_IsGlobalObject(xblScope));
+ MOZ_ASSERT(JS::ObjectIsNotGray(xblScope));
+ return xblScope;
+ }
+
+ case mozilla::dom::ReflectionScope::UAWidget: {
+ // If scope is set to UAWidgetScope, it means that the canonical reflector
+ // for this native object should live in the UA widget scope.
+ if (xpc::IsInUAWidgetScope(obj)) {
+ return obj;
+ }
+ JS::Rooted<JSObject*> rootedObj(cx, obj);
+ JSObject* uaWidgetScope = xpc::GetUAWidgetScope(cx, rootedObj);
+ MOZ_ASSERT_IF(uaWidgetScope, JS_IsGlobalObject(uaWidgetScope));
+ MOZ_ASSERT(JS::ObjectIsNotGray(uaWidgetScope));
+ return uaWidgetScope;
+ }
+
+ case ReflectionScope::Content:
+ return obj;
}
- // If useXBLScope is true, it means that the canonical reflector for this
- // native object should live in the content XBL scope. Note that we never put
- // anonymous content inside an add-on scope.
- if (xpc::IsInContentXBLScope(obj)) {
- return obj;
- }
- JS::Rooted<JSObject*> rootedObj(cx, obj);
- JSObject* xblScope = xpc::GetXBLScope(cx, rootedObj);
- MOZ_ASSERT_IF(xblScope, JS_IsGlobalObject(xblScope));
- MOZ_ASSERT(JS::ObjectIsNotGray(xblScope));
- return xblScope;
+ MOZ_CRASH("Unknown ReflectionScope variant");
+
+ return nullptr;
}
// Finding of the associated global for an object, when we don't want to
// explicitly pass in things like the nsWrapperCache for it.
template<typename T>
static inline JSObject*
FindAssociatedGlobal(JSContext* cx, const T& p)
{
- return FindAssociatedGlobal(cx, GetParentPointer(p), GetWrapperCache(p), GetUseXBLScope(p));
+ return FindAssociatedGlobal(cx, GetParentPointer(p), GetWrapperCache(p), GetReflectionScope(p));
}
// Specialization for the case of nsIGlobalObject, since in that case
// we can just get the JSObject* directly.
template<>
inline JSObject*
FindAssociatedGlobal(JSContext* cx, nsIGlobalObject* const& p)
{
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4936,21 +4936,24 @@ HTMLMediaElement::AssertReadyStateIsNoth
}
#endif
}
void
HTMLMediaElement::AttachAndSetUAShadowRoot()
{
if (GetShadowRoot()) {
+ MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
return;
}
// Add a closed shadow root to host video controls
- AttachShadowWithoutNameChecks(ShadowRootMode::Closed);
+ RefPtr<ShadowRoot> shadowRoot =
+ AttachShadowWithoutNameChecks(ShadowRootMode::Closed);
+ shadowRoot->SetIsUAWidget(true);
}
nsresult
HTMLMediaElement::InitializeDecoderAsClone(ChannelMediaDecoder* aOriginal)
{
NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
AssertReadyStateIsNothing();
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -58,32 +58,35 @@ interface Document : Node {
HTMLCollection getElementsByTagName(DOMString localName);
[Pure, Throws]
HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
[Pure]
HTMLCollection getElementsByClassName(DOMString classNames);
[Pure]
Element? getElementById(DOMString elementId);
- [CEReactions, NewObject, Throws]
+ // These DOM methods cannot be accessed by UA Widget scripts
+ // because the DOM element reflectors will be in the content scope,
+ // instead of the desired UA Widget scope.
+ [CEReactions, NewObject, Throws, Func="IsNotUAWidget"]
Element createElement(DOMString localName, optional (ElementCreationOptions or DOMString) options);
- [CEReactions, NewObject, Throws]
+ [CEReactions, NewObject, Throws, Func="IsNotUAWidget"]
Element createElementNS(DOMString? namespace, DOMString qualifiedName, optional (ElementCreationOptions or DOMString) options);
[NewObject]
DocumentFragment createDocumentFragment();
- [NewObject]
+ [NewObject, Func="IsNotUAWidget"]
Text createTextNode(DOMString data);
- [NewObject]
+ [NewObject, Func="IsNotUAWidget"]
Comment createComment(DOMString data);
[NewObject, Throws]
ProcessingInstruction createProcessingInstruction(DOMString target, DOMString data);
- [CEReactions, Throws]
+ [CEReactions, Throws, Func="IsNotUAWidget"]
Node importNode(Node node, optional boolean deep = false);
- [CEReactions, Throws]
+ [CEReactions, Throws, Func="IsNotUAWidget"]
Node adoptNode(Node node);
[NewObject, Throws, NeedsCallerType]
Event createEvent(DOMString interface);
[NewObject, Throws]
Range createRange();
@@ -169,17 +172,17 @@ partial interface Document {
[Pref="dom.select_events.enabled"]
attribute EventHandler onselectionchange;
/**
* True if this document is synthetic : stand alone image, video, audio file,
* etc.
*/
- [Func="IsChromeOrXBL"] readonly attribute boolean mozSyntheticDocument;
+ [Func="IsChromeOrXBLOrUAWidget"] readonly attribute boolean mozSyntheticDocument;
[Throws, Func="IsChromeOrXBL"]
BoxObject? getBoxObjectFor(Element? element);
/**
* Returns the script element whose script is currently being processed.
*
* @see <https://developer.mozilla.org/en/DOM/document.currentScript>
*/
[Pure]
--- a/dom/webidl/EventTarget.webidl
+++ b/dom/webidl/EventTarget.webidl
@@ -9,17 +9,17 @@
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
dictionary EventListenerOptions {
boolean capture = false;
/* Setting to true make the listener be added to the system group. */
- [Func="ThreadSafeIsChromeOrXBL"]
+ [Func="ThreadSafeIsChromeOrXBLOrUAWidget"]
boolean mozSystemGroup = false;
};
dictionary AddEventListenerOptions : EventListenerOptions {
boolean passive;
boolean once = false;
[ChromeOnly]
boolean wantUntrusted;
--- a/dom/webidl/HTMLVideoElement.webidl
+++ b/dom/webidl/HTMLVideoElement.webidl
@@ -43,20 +43,20 @@ partial interface HTMLVideoElement {
// Time which the last painted video frame was late by, in seconds.
readonly attribute double mozFrameDelay;
// True if the video has an audio track available.
readonly attribute boolean mozHasAudio;
// 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="IsChromeOrXBL"]
+ [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="IsChromeOrXBL"]
+ [Pref="media.videocontrols.lock-video-orientation", Func="IsChromeOrXBLOrUAWidget"]
attribute boolean mozIsOrientationLocked;
};
// 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();
};
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -57,28 +57,31 @@ interface Node : EventTarget {
[Pure]
readonly attribute Node? nextSibling;
[CEReactions, SetterThrows, Pure]
attribute DOMString? nodeValue;
[CEReactions, SetterThrows, GetterCanOOM,
SetterNeedsSubjectPrincipal=NonSystem, Pure]
attribute DOMString? textContent;
- [CEReactions, Throws]
+ // These DOM methods cannot be accessed by UA Widget scripts
+ // because the DOM element reflectors will be in the content scope,
+ // instead of the desired UA Widget scope.
+ [CEReactions, Throws, Func="IsNotUAWidget"]
Node insertBefore(Node node, Node? child);
- [CEReactions, Throws]
+ [CEReactions, Throws, Func="IsNotUAWidget"]
Node appendChild(Node node);
- [CEReactions, Throws]
+ [CEReactions, Throws, Func="IsNotUAWidget"]
Node replaceChild(Node node, Node child);
[CEReactions, Throws]
Node removeChild(Node child);
[CEReactions]
void normalize();
- [CEReactions, Throws]
+ [CEReactions, Throws, Func="IsNotUAWidget"]
Node cloneNode(optional boolean deep = false);
[Pure]
boolean isSameNode(Node? node);
[Pure]
boolean isEqualNode(Node? node);
const unsigned short DOCUMENT_POSITION_DISCONNECTED = 0x01;
const unsigned short DOCUMENT_POSITION_PRECEDING = 0x02;
--- a/dom/webidl/ShadowRoot.webidl
+++ b/dom/webidl/ShadowRoot.webidl
@@ -26,11 +26,28 @@ interface ShadowRoot : DocumentFragment
// [deprecated] Shadow DOM v0
Element? getElementById(DOMString elementId);
HTMLCollection getElementsByTagName(DOMString localName);
HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
HTMLCollection getElementsByClassName(DOMString classNames);
[CEReactions, SetterThrows, TreatNullAs=EmptyString]
attribute DOMString innerHTML;
+
+ // When JS invokes importNode or createElement, the binding code needs to
+ // create a reflector, and so invoking those methods directly on the content
+ // document would cause the reflector to be created in the content scope,
+ // at which point it would be difficult to move into the UA Widget scope.
+ // As such, these methods allow UA widget code to simultaneously create nodes
+ // and associate them with the UA widget tree, so that the reflectors get
+ // created in the right scope.
+ [CEReactions, Throws, Func="IsChromeOrXBLOrUAWidget"]
+ Node importNodeAndAppendChildAt(Node parentNode, Node node, optional boolean deep = false);
+
+ [CEReactions, Throws, Func="IsChromeOrXBLOrUAWidget"]
+ Node createElementAndAppendChildAt(Node parentNode, DOMString localName);
+
+ // For triggering UA Widget scope in tests.
+ [ChromeOnly]
+ void setIsUAWidget(boolean isUAWidget);
};
ShadowRoot implements DocumentOrShadowRoot;
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -1107,16 +1107,17 @@ xpc::CreateSandboxObject(JSContext* cx,
principal, realmOptions));
if (!sandbox)
return NS_ERROR_FAILURE;
CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox);
priv->allowWaivers = options.allowWaivers;
priv->isWebExtensionContentScript = options.isWebExtensionContentScript;
priv->isContentXBLCompartment = options.isContentXBLScope;
+ priv->isUAWidgetCompartment = options.isUAWidgetScope;
priv->isSandboxCompartment = true;
// Set up the wantXrays flag, which indicates whether xrays are desired even
// for same-origin access.
//
// This flag has historically been ignored for chrome sandboxes due to
// quirks in the wrapping implementation that have now been removed. Indeed,
// same-origin Xrays for chrome->chrome access seems a bit superfluous.
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2178,18 +2178,17 @@ nsXPCComponents_Utils::EvalInSandbox(con
NS_IMETHODIMP
nsXPCComponents_Utils::GetUAWidgetScope(nsIPrincipal* principal,
JSContext* cx,
MutableHandleValue rval)
{
rval.set(UndefinedValue());
- JSObject* scope = XPCJSRuntime::Get()->GetUAWidgetScope(cx, principal);
- NS_ENSURE_TRUE(scope, NS_ERROR_OUT_OF_MEMORY); // See bug 858642.
+ JSObject* scope = xpc::GetUAWidgetScope(cx, principal);
rval.set(JS::ObjectValue(*scope));
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::GetSandboxMetadata(HandleValue sandboxVal,
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -179,17 +179,19 @@ public:
namespace xpc {
CompartmentPrivate::CompartmentPrivate(JS::Compartment* c)
: wantXrays(false)
, allowWaivers(true)
, isWebExtensionContentScript(false)
, allowCPOWs(false)
, isContentXBLCompartment(false)
+ , isUAWidgetCompartment(false)
, isSandboxCompartment(false)
+ , isAddonCompartment(false)
, universalXPConnectEnabled(false)
, forcePermissiveCOWs(false)
, wasNuked(false)
, mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH))
{
MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
mozilla::PodArrayZero(wrapperDenialWarnings);
}
@@ -464,16 +466,36 @@ IsContentXBLScope(JS::Realm* realm)
bool
IsInContentXBLScope(JSObject* obj)
{
return IsContentXBLCompartment(js::GetObjectCompartment(obj));
}
bool
+IsUAWidgetCompartment(JS::Compartment* compartment)
+{
+ // We always eagerly create compartment privates for UA Widget compartments.
+ CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
+ return priv && priv->isUAWidgetCompartment;
+}
+
+bool
+IsUAWidgetScope(JS::Realm* realm)
+{
+ return IsUAWidgetCompartment(JS::GetCompartmentForRealm(realm));
+}
+
+bool
+IsInUAWidgetScope(JSObject* obj)
+{
+ return IsUAWidgetCompartment(js::GetObjectCompartment(obj));
+}
+
+bool
IsInSandboxCompartment(JSObject* obj)
{
JS::Compartment* comp = js::GetObjectCompartment(obj);
// We always eagerly create compartment privates for sandbox compartments.
CompartmentPrivate* priv = CompartmentPrivate::Get(comp);
return priv && priv->isSandboxCompartment;
}
@@ -2823,17 +2845,16 @@ XPCJSRuntime::XPCJSRuntime(JSContext* aC
mDyingWrappedNativeProtoMap(XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_LENGTH)),
mGCIsRunning(false),
mNativesToReleaseArray(),
mDoingFinalization(false),
mVariantRoots(nullptr),
mWrappedJSRoots(nullptr),
mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite())
{
- MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.init());
MOZ_COUNT_CTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
}
/* static */
XPCJSRuntime*
XPCJSRuntime::Get()
{
return nsXPConnect::GetRuntimeInstance();
@@ -3151,16 +3172,17 @@ XPCJSRuntime::GetUAWidgetScope(JSContext
if (Principal2JSObjectMap::Ptr p = mUAWidgetScopeMap.lookup(key)) {
return p->value();
}
SandboxOptions options;
options.sandboxName.AssignLiteral("UA Widget Scope");
options.wantXrays = false;
options.wantComponents = false;
+ options.isUAWidgetScope = true;
// Use an ExpandedPrincipal to create asymmetric security.
MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal));
nsTArray<nsCOMPtr<nsIPrincipal>> principalAsArray(1);
principalAsArray.AppendElement(principal);
RefPtr<ExpandedPrincipal> ep =
ExpandedPrincipal::Create(principalAsArray,
principal->OriginAttributesRef());
@@ -3168,16 +3190,18 @@ XPCJSRuntime::GetUAWidgetScope(JSContext
// Create the sandbox.
RootedValue v(cx);
nsresult rv = CreateSandboxObject(cx, &v,
static_cast<nsIExpandedPrincipal*>(ep),
options);
NS_ENSURE_SUCCESS(rv, nullptr);
JSObject* scope = &v.toObject();
+ MOZ_ASSERT(xpc::IsInUAWidgetScope(js::UncheckedUnwrap(scope)));
+
MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, scope));
return scope;
}
void
XPCJSRuntime::InitSingletonScopes()
{
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -297,16 +297,42 @@ GetXBLScope(JSContext* cx, JSObject* con
RootedObject scope(cx, nativeScope->EnsureContentXBLScope(cx));
NS_ENSURE_TRUE(scope, nullptr); // See bug 858642.
scope = js::UncheckedUnwrap(scope);
JS::ExposeObjectToActiveJS(scope);
return scope;
}
+JSObject*
+GetUAWidgetScope(JSContext* cx, JSObject* contentScopeArg)
+{
+ JS::RootedObject contentScope(cx, contentScopeArg);
+ JSAutoRealm ar(cx, contentScope);
+ nsIPrincipal* principal =
+ nsJSPrincipals::get(JS_GetCompartmentPrincipals(js::GetObjectCompartment(contentScope)));
+
+ if (nsContentUtils::IsSystemPrincipal(principal)) {
+ return JS::GetNonCCWObjectGlobal(contentScope);
+ }
+
+ return GetUAWidgetScope(cx, principal);
+}
+
+JSObject*
+GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal)
+{
+ RootedObject scope(cx, XPCJSRuntime::Get()->GetUAWidgetScope(cx, principal));
+ NS_ENSURE_TRUE(scope, nullptr); // See bug 858642.
+
+ scope = js::UncheckedUnwrap(scope);
+ JS::ExposeObjectToActiveJS(scope);
+ return scope;
+}
+
bool
AllowContentXBLScope(JS::Realm* realm)
{
XPCWrappedNativeScope* scope = RealmPrivate::Get(realm)->scope;
return scope && scope->AllowContentXBLScope();
}
bool
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -1175,23 +1175,50 @@ IsChromeOrXBL(JSContext* cx, JSObject* /
// compat and not security for remote XUL, we just always claim to be XBL.
//
// Note that, for performance, we don't check AllowXULXBLForPrincipal here,
// and instead rely on the fact that AllowContentXBLScope() only returns false in
// remote XUL situations.
return AccessCheck::isChrome(c) || IsContentXBLCompartment(c) || !AllowContentXBLScope(realm);
}
+bool
+IsNotUAWidget(JSContext* cx, JSObject* /* unused */)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ JS::Realm* realm = JS::GetCurrentRealmOrNull(cx);
+ MOZ_ASSERT(realm);
+ JS::Compartment* c = JS::GetCompartmentForRealm(realm);
+
+ return !IsUAWidgetCompartment(c);
+}
+
+bool
+IsChromeOrXBLOrUAWidget(JSContext* cx, JSObject* /* unused */)
+{
+ if (IsChromeOrXBL(cx, nullptr)) {
+ return true;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ JS::Realm* realm = JS::GetCurrentRealmOrNull(cx);
+ MOZ_ASSERT(realm);
+ JS::Compartment* c = JS::GetCompartmentForRealm(realm);
+
+ return IsUAWidgetCompartment(c);
+}
+
+
extern bool IsCurrentThreadRunningChromeWorker();
bool
-ThreadSafeIsChromeOrXBL(JSContext* cx, JSObject* obj)
+ThreadSafeIsChromeOrXBLOrUAWidget(JSContext* cx, JSObject* obj)
{
if (NS_IsMainThread()) {
- return IsChromeOrXBL(cx, obj);
+ return IsChromeOrXBLOrUAWidget(cx, obj);
}
return IsCurrentThreadRunningChromeWorker();
}
} // namespace dom
} // namespace mozilla
void
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -2661,16 +2661,17 @@ public:
, allowWaivers(true)
, wantComponents(true)
, wantExportHelpers(false)
, isWebExtensionContentScript(false)
, proto(cx)
, sameZoneAs(cx)
, freshZone(false)
, isContentXBLScope(false)
+ , isUAWidgetScope(false)
, invisibleToDebugger(false)
, discardSource(false)
, metadata(cx)
, userContextId(0)
, originAttributes(cx)
{ }
virtual bool Parse() override;
@@ -2680,16 +2681,17 @@ public:
bool wantComponents;
bool wantExportHelpers;
bool isWebExtensionContentScript;
JS::RootedObject proto;
nsCString sandboxName;
JS::RootedObject sameZoneAs;
bool freshZone;
bool isContentXBLScope;
+ bool isUAWidgetScope;
bool invisibleToDebugger;
bool discardSource;
GlobalProperties globalProperties;
JS::RootedValue metadata;
uint32_t userContextId;
JS::RootedObject originAttributes;
protected:
@@ -2930,19 +2932,27 @@ public:
// to opt into CPOWs. It's necessary for the implementation of
// RemoteAddonsParent.jsm.
bool allowCPOWs;
// True if this compartment is a content XBL compartment. Every global in
// such a compartment is a content XBL scope.
bool isContentXBLCompartment;
+ // True if this compartment is a UA widget compartment.
+ bool isUAWidgetCompartment;
+
// True if this is a sandbox compartment. See xpc::CreateSandboxObject.
bool isSandboxCompartment;
+ // True if EnsureAddonCompartment has been called for this compartment.
+ // Note that this is false for extensions that ship with the browser, like
+ // browser/extensions/activity-stream.
+ bool isAddonCompartment;
+
// This is only ever set during mochitest runs when enablePrivilege is called.
// It's intended as a temporary stopgap measure until we can finish ripping out
// enablePrivilege. Once set, this value is never unset (i.e., it doesn't follow
// the old scoping rules of enablePrivilege).
//
// Using it in production is inherently unsafe.
bool universalXPConnectEnabled;
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -81,16 +81,20 @@ TransplantObject(JSContext* cx, JS::Hand
JSObject*
TransplantObjectRetainingXrayExpandos(JSContext* cx, JS::HandleObject origobj, JS::HandleObject target);
bool IsContentXBLCompartment(JS::Compartment* compartment);
bool IsContentXBLScope(JS::Realm* realm);
bool IsInContentXBLScope(JSObject* obj);
+bool IsUAWidgetCompartment(JS::Compartment* compartment);
+bool IsUAWidgetScope(JS::Realm* realm);
+bool IsInUAWidgetScope(JSObject* obj);
+
bool IsInSandboxCompartment(JSObject* obj);
// Return a raw XBL scope object corresponding to contentScope, which must
// be an object whose global is a DOM window.
//
// The return value is not wrapped into cx->compartment, so be sure to enter
// its compartment before doing anything meaningful.
//
@@ -99,16 +103,22 @@ bool IsInSandboxCompartment(JSObject* ob
// exist.
//
// This function asserts if |contentScope| is itself in an XBL scope to catch
// sloppy consumers. Conversely, GetXBLScopeOrGlobal will handle objects that
// are in XBL scope (by just returning the global).
JSObject*
GetXBLScope(JSContext* cx, JSObject* contentScope);
+JSObject*
+GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal);
+
+JSObject*
+GetUAWidgetScope(JSContext* cx, JSObject* contentScope);
+
inline JSObject*
GetXBLScopeOrGlobal(JSContext* cx, JSObject* obj)
{
MOZ_ASSERT(!js::IsCrossCompartmentWrapper(obj));
if (IsInContentXBLScope(obj))
return JS::GetNonCCWObjectGlobal(obj);
return GetXBLScope(cx, obj);
}
@@ -718,16 +728,29 @@ namespace dom {
/**
* A test for whether WebIDL methods that should only be visible to
* chrome or XBL scopes should be exposed.
*/
bool IsChromeOrXBL(JSContext* cx, JSObject* /* unused */);
/**
- * Same as IsChromeOrXBL but can be used in worker threads as well.
+ * This is used to prevent UA widget code from directly creating and adopting
+ * nodes via the content document, since they should use the special
+ * create-and-insert apis instead.
*/
-bool ThreadSafeIsChromeOrXBL(JSContext* cx, JSObject* obj);
+bool IsNotUAWidget(JSContext* cx, JSObject* /* unused */);
+
+/**
+ * A test for whether WebIDL methods that should only be visible to
+ * chrome, XBL scopes, or UA Widget scopes.
+ */
+bool IsChromeOrXBLOrUAWidget(JSContext* cx, JSObject* /* unused */);
+
+/**
+ * Same as IsChromeOrXBLOrUAWidget but can be used in worker threads as well.
+ */
+bool ThreadSafeIsChromeOrXBLOrUAWidget(JSContext* cx, JSObject* obj);
} // namespace dom
} // namespace mozilla
#endif
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -175,16 +175,17 @@ nsVideoFrame::AppendAnonymousContentTo(n
nsIContent*
nsVideoFrame::GetVideoControls()
{
if (mVideoControls) {
return mVideoControls;
}
if (mContent->GetShadowRoot()) {
// The video controls <div> is the only child of the UA Widget Shadow Root.
+ MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget());
return mContent->GetShadowRoot()->GetFirstChild();
}
return nullptr;
}
void
nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
{
--- a/toolkit/content/tests/widgets/mochitest.ini
+++ b/toolkit/content/tests/widgets/mochitest.ini
@@ -20,16 +20,17 @@ support-files =
videocontrols_direction-2d.html
videocontrols_direction-2e.html
videocontrols_direction_test.js
videomask.css
[test_audiocontrols_dimensions.html]
[test_mousecapture_area.html]
skip-if = (verify && debug)
+[test_ua_widget.html]
[test_videocontrols.html]
tags = fullscreen
skip-if = toolkit == 'android' || (verify && debug && (os == 'linux')) #TIMED_OUT
[test_videocontrols_keyhandler.html]
skip-if = toolkit == 'android'
[test_videocontrols_vtt.html]
[test_videocontrols_iframe_fullscreen.html]
[test_videocontrols_size.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/widgets/test_ua_widget.html
@@ -0,0 +1,102 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>UA Widget test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+
+<div id="content">
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const content = document.getElementById("content");
+
+const div = content.appendChild(document.createElement("div"));
+div.attachShadow({ mode: "open"});
+SpecialPowers.wrap(div.shadowRoot).setIsUAWidget(true);
+
+const sandbox = SpecialPowers.Cu.getUAWidgetScope(SpecialPowers.wrap(div).nodePrincipal);
+
+SpecialPowers.setWrapped(sandbox, "info", SpecialPowers.wrap(info));
+SpecialPowers.setWrapped(sandbox, "is", SpecialPowers.wrap(is));
+SpecialPowers.setWrapped(sandbox, "ok", SpecialPowers.wrap(ok));
+
+const sandboxScript = function(shadowRoot) {
+ info("UA Widget scope tests");
+ is(typeof window, "undefined", "The sandbox has no window");
+ is(typeof document, "undefined", "The sandbox has no document");
+
+ let element = shadowRoot.host;
+ let doc = element.ownerDocument;
+ let win = doc.defaultView;
+
+ ok(shadowRoot instanceof win.ShadowRoot, "shadowRoot is a ShadowRoot");
+ ok(element instanceof win.HTMLDivElement, "Element is a <div>");
+
+ is("createElement" in doc, false, "No document.createElement");
+ is("createElementNS" in doc, false, "No document.createElementNS");
+ is("createTextNode" in doc, false, "No document.createTextNode");
+ is("createComment" in doc, false, "No document.createComment");
+ is("importNode" in doc, false, "No document.importNode");
+ is("adoptNode" in doc, false, "No document.adoptNode");
+
+ is("insertBefore" in element, false, "No element.insertBefore");
+ is("appendChild" in element, false, "No element.appendChild");
+ is("replaceChild" in element, false, "No element.replaceChild");
+ is("cloneNode" in element, false, "No element.cloneNode");
+
+ ok("importNodeAndAppendChildAt" in shadowRoot, "shadowRoot.importNodeAndAppendChildAt");
+ ok("createElementAndAppendChildAt" in shadowRoot, "shadowRoot.createElementAndAppendChildAt");
+
+ info("UA Widget special methods tests");
+
+ const span = shadowRoot.createElementAndAppendChildAt(shadowRoot, "span");
+ span.textContent = "Hello from <span>!";
+
+ is(shadowRoot.lastChild, span, "<span> inserted");
+
+ const parser = new win.DOMParser();
+ let parserDoc = parser.parseFromString(
+ `<div xmlns="http://www.w3.org/1999/xhtml">Hello from DOMParser!</div>`, "application/xml");
+ shadowRoot.importNodeAndAppendChildAt(shadowRoot, parserDoc.documentElement, true);
+
+ ok(shadowRoot.lastChild instanceof win.HTMLDivElement, "<div> inserted");
+ is(shadowRoot.lastChild.textContent, "Hello from DOMParser!", "Deep import node worked");
+
+ info("UA Widget reflectors tests");
+
+ win.wrappedJSObject.spanElementFromUAWidget = span;
+ win.wrappedJSObject.divElementFromUAWidget = shadowRoot.lastChild;
+};
+SpecialPowers.Cu.evalInSandbox("this.script = " + sandboxScript.toSource(), sandbox);
+sandbox.script(div.shadowRoot);
+
+ok(window.spanElementFromUAWidget instanceof HTMLSpanElement, "<span> exposed");
+ok(window.divElementFromUAWidget instanceof HTMLDivElement, "<div> exposed");
+
+try {
+ window.spanElementFromUAWidget.textContent;
+ ok(false, "Should throw.");
+} catch (err) {
+ ok(/denied/.test(err), "Permission denied to access <span>");
+}
+
+try {
+ window.divElementFromUAWidget.textContent;
+ ok(false, "Should throw.");
+} catch (err) {
+ ok(/denied/.test(err), "Permission denied to access <div>");
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/toolkit/content/widgets/videocontrols.js
+++ b/toolkit/content/widgets/videocontrols.js
@@ -1646,25 +1646,22 @@ this.VideoControlsImplPageWidget = class
if (tt.mode === "showing") {
this.changeTextTrack(tt.index);
}
return;
}
tt.index = this.textTracksCount++;
- const label = tt.label || "";
- const ttText = this.document.createTextNode(label);
- const ttBtn = this.document.createElement("button");
+ const ttBtn =
+ this.shadowRoot.createElementAndAppendChildAt(this.textTrackList, "button");
+ ttBtn.textContent = tt.label || "";
ttBtn.classList.add("textTrackItem");
ttBtn.setAttribute("index", tt.index);
- ttBtn.appendChild(ttText);
-
- this.textTrackList.appendChild(ttBtn);
if (tt.mode === "showing" && tt.index) {
this.changeTextTrack(tt.index);
}
},
changeTextTrack(index) {
for (let tt of this.overlayableTextTracks) {
@@ -1854,16 +1851,17 @@ this.VideoControlsImplPageWidget = class
this.clickToPlay.hiddenByAdjustment = false;
}
this.clickToPlay.style.width = `${clickToPlayScaledSize}px`;
this.clickToPlay.style.height = `${clickToPlayScaledSize}px`;
}
},
init(shadowRoot) {
+ this.shadowRoot = shadowRoot;
this.video = shadowRoot.host;
this.videocontrols = shadowRoot.firstChild;
this.document = this.videocontrols.ownerDocument;
this.window = this.document.defaultView;
this.shadowRoot = shadowRoot;
this.controlsContainer = this.shadowRoot.getElementById("controlsContainer");
this.statusIcon = this.shadowRoot.getElementById("statusIcon");
@@ -2200,17 +2198,17 @@ this.VideoControlsImplPageWidget = class
class="button fullscreenButton"
enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
</div>
<div id="textTrackList" class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></div>
</div>
</div>
</div>`, "application/xml");
- this.shadowRoot.appendChild(this.document.importNode(parserDoc.documentElement, true));
+ this.shadowRoot.importNodeAndAppendChildAt(this.shadowRoot, parserDoc.documentElement, true);
}
destructor() {
this.Utils.terminate();
this.TouchUtils.terminate();
this.Utils.updateOrientationState(false);
// randomID used to be a <field>, which meant that the XBL machinery
// undefined the property when the element was unbound. The code in
@@ -2317,16 +2315,17 @@ this.NoControlsImplPageWidget = class {
return;
}
this.noControlsOverlay.hidden = true;
this.video.play();
},
init(shadowRoot) {
+ this.shadowRoot = shadowRoot;
this.video = shadowRoot.host;
this.videocontrols = shadowRoot.firstChild;
this.document = this.videocontrols.ownerDocument;
this.window = this.document.defaultView;
this.shadowRoot = shadowRoot;
this.randomID = Math.random();
this.videocontrols.randomID = this.randomID;
@@ -2384,11 +2383,11 @@ this.NoControlsImplPageWidget = class {
<div id="controlsContainer" class="controlsContainer" role="none" hidden="true">
<div class="controlsOverlay stackItem">
<div class="controlsSpacerStack">
<div id="clickToPlay" class="clickToPlay"></div>
</div>
</div>
</div>
</div>`, "application/xml");
- this.shadowRoot.appendChild(this.document.importNode(parserDoc.documentElement, true));
+ this.shadowRoot.importNodeAndAppendChildAt(this.shadowRoot, parserDoc.documentElement, true);
}
};