Bug 1275835 - Part 1: Move custom element codes from nsDocument to CustomElementsRegistry; r=wchen

MozReview-Commit-ID: 9gTSFrYW7o3

--HG--
extra : rebase_source : f123f21aadaa18641ddd7fa7fa67eb27a4152f83
This commit is contained in:
Edgar Chen 2016-08-30 11:48:53 +08:00
parent 5a59692088
commit 67946850ab
15 changed files with 756 additions and 691 deletions

View File

@ -5,17 +5,149 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/CustomElementsRegistry.h"
#include "mozilla/dom/CustomElementsRegistryBinding.h"
#include "mozilla/dom/WebComponentsBinding.h"
namespace mozilla {
namespace dom {
void
CustomElementCallback::Call()
{
ErrorResult rv;
switch (mType) {
case nsIDocument::eCreated:
{
// For the duration of this callback invocation, the element is being created
// flag must be set to true.
mOwnerData->mElementIsBeingCreated = true;
// The callback hasn't actually been invoked yet, but we need to flip
// this now in order to enqueue the attached callback. This is a spec
// bug (w3c bug 27437).
mOwnerData->mCreatedCallbackInvoked = true;
// If ELEMENT is in a document and this document has a browsing context,
// enqueue attached callback for ELEMENT.
nsIDocument* document = mThisObject->GetComposedDoc();
if (document && document->GetDocShell()) {
nsContentUtils::EnqueueLifecycleCallback(
document, nsIDocument::eAttached, mThisObject);
}
static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
mOwnerData->mElementIsBeingCreated = false;
break;
}
case nsIDocument::eAttached:
static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv);
break;
case nsIDocument::eDetached:
static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv);
break;
case nsIDocument::eAttributeChanged:
static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
mArgs.name, mArgs.oldValue, mArgs.newValue, rv);
break;
}
}
void
CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const
{
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
aCb.NoteXPCOMChild(mThisObject);
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
aCb.NoteXPCOMChild(mCallback);
}
CustomElementCallback::CustomElementCallback(Element* aThisObject,
nsIDocument::ElementCallbackType aCallbackType,
mozilla::dom::CallbackFunction* aCallback,
CustomElementData* aOwnerData)
: mThisObject(aThisObject),
mCallback(aCallback),
mType(aCallbackType),
mOwnerData(aOwnerData)
{
}
CustomElementData::CustomElementData(nsIAtom* aType)
: mType(aType),
mCurrentCallback(-1),
mElementIsBeingCreated(false),
mCreatedCallbackInvoked(true),
mAssociatedMicroTask(-1)
{
}
void
CustomElementData::RunCallbackQueue()
{
// Note: It's possible to re-enter this method.
while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) {
mCallbackQueue[mCurrentCallback]->Call();
}
mCallbackQueue.Clear();
mCurrentCallback = -1;
}
// Only needed for refcounted objects.
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CustomElementsRegistry, mWindow)
NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementsRegistry)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementsRegistry)
tmp->mCustomDefinitions.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementsRegistry)
for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
nsAutoPtr<LifecycleCallbacks>& callbacks = iter.UserData()->mCallbacks;
if (callbacks->mAttributeChangedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mCustomDefinitions->mCallbacks->mAttributeChangedCallback");
cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value());
}
if (callbacks->mCreatedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mCustomDefinitions->mCallbacks->mCreatedCallback");
cb.NoteXPCOMChild(callbacks->mCreatedCallback.Value());
}
if (callbacks->mAttachedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mCustomDefinitions->mCallbacks->mAttachedCallback");
cb.NoteXPCOMChild(callbacks->mAttachedCallback.Value());
}
if (callbacks->mDetachedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mCustomDefinitions->mCallbacks->mDetachedCallback");
cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementsRegistry)
for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
aCallbacks.Trace(&iter.UserData()->mPrototype,
"mCustomDefinitions prototype",
aClosure);
}
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementsRegistry)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CustomElementsRegistry)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CustomElementsRegistry)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
@ -43,18 +175,291 @@ CustomElementsRegistry::Create(nsPIDOMWindowInner* aWindow)
return nullptr;
}
if (!Preferences::GetBool("dom.webcomponents.enabled") &&
!Preferences::GetBool("dom.webcomponents.customelement.enabled")) {
return nullptr;
}
RefPtr<CustomElementsRegistry> customElementsRegistry =
new CustomElementsRegistry(aWindow);
return customElementsRegistry.forget();
}
/* static */ void
CustomElementsRegistry::ProcessTopElementQueue()
{
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
nsTArray<RefPtr<CustomElementData>>& stack = *sProcessingStack;
uint32_t firstQueue = stack.LastIndexOf((CustomElementData*) nullptr);
for (uint32_t i = firstQueue + 1; i < stack.Length(); ++i) {
// Callback queue may have already been processed in an earlier
// element queue or in an element queue that was popped
// off more recently.
if (stack[i]->mAssociatedMicroTask != -1) {
stack[i]->RunCallbackQueue();
stack[i]->mAssociatedMicroTask = -1;
}
}
// If this was actually the base element queue, don't bother trying to pop
// the first "queue" marker (sentinel).
if (firstQueue != 0) {
stack.SetLength(firstQueue);
} else {
// Don't pop sentinel for base element queue.
stack.SetLength(1);
}
}
/* static */ void
CustomElementsRegistry::XPCOMShutdown()
{
sProcessingStack.reset();
}
/* static */ Maybe<nsTArray<RefPtr<CustomElementData>>>
CustomElementsRegistry::sProcessingStack;
CustomElementsRegistry::CustomElementsRegistry(nsPIDOMWindowInner* aWindow)
: mWindow(aWindow)
{
mozilla::HoldJSObjects(this);
if (!sProcessingStack) {
sProcessingStack.emplace();
// Add the base queue sentinel to the processing stack.
sProcessingStack->AppendElement((CustomElementData*) nullptr);
}
}
CustomElementsRegistry::~CustomElementsRegistry()
{
mozilla::DropJSObjects(this);
}
CustomElementDefinition*
CustomElementsRegistry::LookupCustomElementDefinition(const nsAString& aLocalName,
const nsAString* aIs) const
{
nsCOMPtr<nsIAtom> localNameAtom = NS_Atomize(aLocalName);
nsCOMPtr<nsIAtom> typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom;
CustomElementHashKey key(kNameSpaceID_XHTML, typeAtom);
CustomElementDefinition* data = mCustomDefinitions.Get(&key);
if (data && data->mLocalName == localNameAtom) {
return data;
}
return nullptr;
}
void
CustomElementsRegistry::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName)
{
mozilla::dom::NodeInfo* info = aElement->NodeInfo();
// Candidate may be a custom element through extension,
// in which case the custom element type name will not
// match the element tag name. e.g. <button is="x-button">.
nsCOMPtr<nsIAtom> typeName = aTypeName;
if (!typeName) {
typeName = info->NameAtom();
}
CustomElementHashKey key(info->NamespaceID(), typeName);
if (mCustomDefinitions.Get(&key)) {
return;
}
nsTArray<nsWeakPtr>* unresolved = mCandidatesMap.Get(&key);
if (!unresolved) {
unresolved = new nsTArray<nsWeakPtr>();
// Ownership of unresolved is taken by customElements.
mCandidatesMap.Put(&key, unresolved);
}
nsWeakPtr* elem = unresolved->AppendElement();
*elem = do_GetWeakReference(aElement);
aElement->AddStates(NS_EVENT_STATE_UNRESOLVED);
return;
}
void
CustomElementsRegistry::SetupCustomElement(Element* aElement,
const nsAString* aTypeExtension)
{
nsCOMPtr<nsIAtom> tagAtom = aElement->NodeInfo()->NameAtom();
nsCOMPtr<nsIAtom> typeAtom = aTypeExtension ?
NS_Atomize(*aTypeExtension) : tagAtom;
if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
// Custom element setup in the parser happens after the "is"
// attribute is added.
aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true);
}
CustomElementDefinition* data = LookupCustomElementDefinition(
aElement->NodeInfo()->LocalName(), aTypeExtension);
if (!data) {
// The type extension doesn't exist in the registry,
// thus we don't need to enqueue callback or adjust
// the "is" attribute, but it is possibly an upgrade candidate.
RegisterUnresolvedElement(aElement, typeAtom);
return;
}
if (data->mLocalName != tagAtom) {
// The element doesn't match the local name for the
// definition, thus the element isn't a custom element
// and we don't need to do anything more.
return;
}
// Enqueuing the created callback will set the CustomElementData on the
// element, causing prototype swizzling to occur in Element::WrapObject.
EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data);
}
void
CustomElementsRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
LifecycleCallbackArgs* aArgs,
CustomElementDefinition* aDefinition)
{
CustomElementData* elementData = aCustomElement->GetCustomElementData();
// Let DEFINITION be ELEMENT's definition
CustomElementDefinition* definition = aDefinition;
if (!definition) {
mozilla::dom::NodeInfo* info = aCustomElement->NodeInfo();
// Make sure we get the correct definition in case the element
// is a extended custom element e.g. <button is="x-button">.
nsCOMPtr<nsIAtom> typeAtom = elementData ?
elementData->mType.get() : info->NameAtom();
CustomElementHashKey key(info->NamespaceID(), typeAtom);
definition = mCustomDefinitions.Get(&key);
if (!definition || definition->mLocalName != info->NameAtom()) {
// Trying to enqueue a callback for an element that is not
// a custom element. We are done, nothing to do.
return;
}
}
if (!elementData) {
// Create the custom element data the first time
// that we try to enqueue a callback.
elementData = new CustomElementData(definition->mType);
// aCustomElement takes ownership of elementData
aCustomElement->SetCustomElementData(elementData);
MOZ_ASSERT(aType == nsIDocument::eCreated,
"First callback should be the created callback");
}
// Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
CallbackFunction* func = nullptr;
switch (aType) {
case nsIDocument::eCreated:
if (definition->mCallbacks->mCreatedCallback.WasPassed()) {
func = definition->mCallbacks->mCreatedCallback.Value();
}
break;
case nsIDocument::eAttached:
if (definition->mCallbacks->mAttachedCallback.WasPassed()) {
func = definition->mCallbacks->mAttachedCallback.Value();
}
break;
case nsIDocument::eDetached:
if (definition->mCallbacks->mDetachedCallback.WasPassed()) {
func = definition->mCallbacks->mDetachedCallback.Value();
}
break;
case nsIDocument::eAttributeChanged:
if (definition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
func = definition->mCallbacks->mAttributeChangedCallback.Value();
}
break;
}
// If there is no such callback, stop.
if (!func) {
return;
}
if (aType == nsIDocument::eCreated) {
elementData->mCreatedCallbackInvoked = false;
} else if (!elementData->mCreatedCallbackInvoked) {
// Callbacks other than created callback must not be enqueued
// until after the created callback has been invoked.
return;
}
// Add CALLBACK to ELEMENT's callback queue.
CustomElementCallback* callback = new CustomElementCallback(aCustomElement,
aType,
func,
elementData);
// Ownership of callback is taken by mCallbackQueue.
elementData->mCallbackQueue.AppendElement(callback);
if (aArgs) {
callback->SetArgs(*aArgs);
}
if (!elementData->mElementIsBeingCreated) {
CustomElementData* lastData =
sProcessingStack->SafeLastElement(nullptr);
// A new element queue needs to be pushed if the queue at the
// top of the stack is associated with another microtask level.
bool shouldPushElementQueue =
(!lastData || lastData->mAssociatedMicroTask <
static_cast<int32_t>(nsContentUtils::MicroTaskLevel()));
// Push a new element queue onto the processing stack when appropriate
// (when we enter a new microtask).
if (shouldPushElementQueue) {
// Push a sentinel value on the processing stack to mark the
// boundary between the element queues.
sProcessingStack->AppendElement((CustomElementData*) nullptr);
}
sProcessingStack->AppendElement(elementData);
elementData->mAssociatedMicroTask =
static_cast<int32_t>(nsContentUtils::MicroTaskLevel());
// Add a script runner to pop and process the element queue at
// the top of the processing stack.
if (shouldPushElementQueue) {
// Lifecycle callbacks enqueued by user agent implementation
// should be invoked prior to returning control back to script.
// Create a script runner to process the top of the processing
// stack as soon as it is safe to run script.
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction(&CustomElementsRegistry::ProcessTopElementQueue);
nsContentUtils::AddScriptRunner(runnable);
}
}
}
void
CustomElementsRegistry::GetCustomPrototype(nsIAtom* aAtom,
JS::MutableHandle<JSObject*> aPrototype)
{
mozilla::dom::CustomElementHashKey key(kNameSpaceID_XHTML, aAtom);
mozilla::dom::CustomElementDefinition* definition = mCustomDefinitions.Get(&key);
if (definition) {
aPrototype.set(definition->mPrototype);
} else {
aPrototype.set(nullptr);
}
}
JSObject*
@ -68,10 +473,11 @@ nsISupports* CustomElementsRegistry::GetParentObject() const
return mWindow;
}
void CustomElementsRegistry::Define(const nsAString& aName,
Function& aFunctionConstructor,
const ElementDefinitionOptions& aOptions,
ErrorResult& aRv)
void
CustomElementsRegistry::Define(const nsAString& aName,
Function& aFunctionConstructor,
const ElementDefinitionOptions& aOptions,
ErrorResult& aRv)
{
// TODO: This function will be implemented in bug 1275835
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);

View File

@ -15,14 +15,87 @@
#include "nsWrapperCache.h"
#include "mozilla/dom/FunctionBinding.h"
class nsDocument;
namespace mozilla {
namespace dom {
struct CustomElementData;
struct ElementDefinitionOptions;
struct LifecycleCallbacks;
class CallbackFunction;
class Function;
class Promise;
struct LifecycleCallbackArgs
{
nsString name;
nsString oldValue;
nsString newValue;
};
class CustomElementCallback
{
public:
CustomElementCallback(Element* aThisObject,
nsIDocument::ElementCallbackType aCallbackType,
CallbackFunction* aCallback,
CustomElementData* aOwnerData);
void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
void Call();
void SetArgs(LifecycleCallbackArgs& aArgs)
{
MOZ_ASSERT(mType == nsIDocument::eAttributeChanged,
"Arguments are only used by attribute changed callback.");
mArgs = aArgs;
}
private:
// The this value to use for invocation of the callback.
RefPtr<Element> mThisObject;
RefPtr<CallbackFunction> mCallback;
// The type of callback (eCreated, eAttached, etc.)
nsIDocument::ElementCallbackType mType;
// Arguments to be passed to the callback,
// used by the attribute changed callback.
LifecycleCallbackArgs mArgs;
// CustomElementData that contains this callback in the
// callback queue.
CustomElementData* mOwnerData;
};
// Each custom element has an associated callback queue and an element is
// being created flag.
struct CustomElementData
{
NS_INLINE_DECL_REFCOUNTING(CustomElementData)
explicit CustomElementData(nsIAtom* aType);
// Objects in this array are transient and empty after each microtask
// checkpoint.
nsTArray<nsAutoPtr<CustomElementCallback>> mCallbackQueue;
// Custom element type, for <button is="x-button"> or <x-button>
// this would be x-button.
nsCOMPtr<nsIAtom> mType;
// The callback that is next to be processed upon calling RunCallbackQueue.
int32_t mCurrentCallback;
// Element is being created flag as described in the custom elements spec.
bool mElementIsBeingCreated;
// Flag to determine if the created callback has been invoked, thus it
// determines if other callbacks can be enqueued.
bool mCreatedCallbackInvoked;
// The microtask level associated with the callbacks in the callback queue,
// it is used to determine if a new queue needs to be pushed onto the
// processing stack.
int32_t mAssociatedMicroTask;
// Empties the callback queue.
void RunCallbackQueue();
private:
virtual ~CustomElementData() {}
};
class CustomElementHashKey : public PLDHashEntryHdr
{
public:
@ -101,6 +174,9 @@ struct CustomElementDefinition
class CustomElementsRegistry final : public nsISupports,
public nsWrapperCache
{
// Allow nsDocument to access mCustomDefinitions and mCandidatesMap.
friend class ::nsDocument;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementsRegistry)
@ -108,13 +184,72 @@ public:
public:
static bool IsCustomElementsEnabled(JSContext* aCx, JSObject* aObject);
static already_AddRefed<CustomElementsRegistry> Create(nsPIDOMWindowInner* aWindow);
already_AddRefed<nsIDocument> GetOwnerDocument() const;
static void ProcessTopElementQueue();
static void XPCOMShutdown();
/**
* Looking up a custom element definition.
* https://html.spec.whatwg.org/#look-up-a-custom-element-definition
*/
CustomElementDefinition* LookupCustomElementDefinition(
const nsAString& aLocalName, const nsAString* aIs = nullptr) const;
/**
* Enqueue created callback or register upgrade candidate for
* newly created custom elements, possibly extending an existing type.
* ex. <x-button>, <button is="x-button> (type extension)
*/
void SetupCustomElement(Element* aElement, const nsAString* aTypeExtension);
void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
LifecycleCallbackArgs* aArgs,
CustomElementDefinition* aDefinition);
void GetCustomPrototype(nsIAtom* aAtom,
JS::MutableHandle<JSObject*> aPrototype);
private:
explicit CustomElementsRegistry(nsPIDOMWindowInner* aWindow);
~CustomElementsRegistry();
/**
* Registers an unresolved custom element that is a candidate for
* upgrade when the definition is registered via registerElement.
* |aTypeName| is the name of the custom element type, if it is not
* provided, then element name is used. |aTypeName| should be provided
* when registering a custom element that extends an existing
* element. e.g. <button is="x-button">.
*/
void RegisterUnresolvedElement(Element* aElement,
nsIAtom* aTypeName = nullptr);
typedef nsClassHashtable<CustomElementHashKey, CustomElementDefinition>
DefinitionMap;
typedef nsClassHashtable<CustomElementHashKey, nsTArray<nsWeakPtr>>
CandidateMap;
// Hashtable for custom element definitions in web components.
// Custom prototypes are stored in the compartment where
// registerElement was called.
DefinitionMap mCustomDefinitions;
// The "upgrade candidates map" from the web components spec. Maps from a
// namespace id and local name to a list of elements to upgrade if that
// element is registered as a custom element.
CandidateMap mCandidatesMap;
nsCOMPtr<nsPIDOMWindowInner> mWindow;
// Array representing the processing stack in the custom elements
// specification. The processing stack is conceptually a stack of
// element queues. Each queue is represented by a sequence of
// CustomElementData in this array, separated by nullptr that
// represent the boundaries of the items in the stack. The first
// queue in the stack is the base element queue.
static mozilla::Maybe<nsTArray<RefPtr<CustomElementData>>> sProcessingStack;
public:
nsISupports* GetParentObject() const;

View File

@ -133,7 +133,6 @@ DOMImplementation::CreateDocument(const nsAString& aNamespaceURI,
if (aNamespaceURI.EqualsLiteral("http://www.w3.org/1999/xhtml")) {
doc->SetContentType(NS_LITERAL_STRING("application/xhtml+xml"));
doc->UseRegistryFromDocument(mOwner);
} else if (aNamespaceURI.EqualsLiteral("http://www.w3.org/2000/svg")) {
doc->SetContentType(NS_LITERAL_STRING("image/svg+xml"));
} else {
@ -235,10 +234,6 @@ DOMImplementation::CreateHTMLDocument(const nsAString& aTitle,
rv = root->AppendChildTo(body, false);
NS_ENSURE_SUCCESS(rv, rv);
// When the createHTMLDocument method is invoked,
// use the registry of the associated document to the new instance.
doc->UseRegistryFromDocument(mOwner);
doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE);
doc.forget(aDocument);

View File

@ -470,8 +470,8 @@ Element::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
CustomElementData* data = GetCustomElementData();
if (data) {
// If this is a registered custom element then fix the prototype.
nsDocument* document = static_cast<nsDocument*>(OwnerDoc());
document->GetCustomPrototype(NodeInfo()->NamespaceID(), data->mType, &customProto);
nsContentUtils::GetCustomPrototype(OwnerDoc(), NodeInfo()->NamespaceID(),
data->mType, &customProto);
if (customProto &&
NodePrincipal()->SubsumesConsideringDomain(nsContentUtils::ObjectPrincipal(customProto))) {
// Just go ahead and create with the right proto up front. Set
@ -1608,7 +1608,8 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
// document and this document has a browsing context.
if (GetCustomElementData() && composedDoc->GetDocShell()) {
// Enqueue an attached callback for the custom element.
composedDoc->EnqueueLifecycleCallback(nsIDocument::eAttached, this);
nsContentUtils::EnqueueLifecycleCallback(
composedDoc, nsIDocument::eAttached, this);
}
}
@ -1889,7 +1890,8 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
// the document and this document has a browsing context.
if (GetCustomElementData() && document->GetDocShell()) {
// Enqueue a detached callback for the custom element.
document->EnqueueLifecycleCallback(nsIDocument::eDetached, this);
nsContentUtils::EnqueueLifecycleCallback(
document, nsIDocument::eDetached, this);
}
}
@ -2495,7 +2497,8 @@ Element::SetAttrAndNotify(int32_t aNamespaceID,
nsDependentAtomString(newValueAtom)
};
ownerDoc->EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, this, &args);
nsContentUtils::EnqueueLifecycleCallback(
ownerDoc, nsIDocument::eAttributeChanged, this, &args);
}
if (aCallAfterSetAttr) {
@ -2749,7 +2752,8 @@ Element::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
NullString()
};
ownerDoc->EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, this, &args);
nsContentUtils::EnqueueLifecycleCallback(
ownerDoc, nsIDocument::eAttributeChanged, this, &args);
}
if (aNotify) {

View File

@ -136,6 +136,7 @@ class EventStateManager;
namespace dom {
class Animation;
class CustomElementsRegistry;
class Link;
class UndoManager;
class DOMRect;
@ -426,6 +427,9 @@ private:
friend class ::nsFocusManager;
friend class ::nsDocument;
// Allow CusomtElementRegistry to call AddStates.
friend class CustomElementsRegistry;
// Also need to allow Link to call UpdateLinkState.
friend class Link;

View File

@ -38,6 +38,7 @@
#include "mozilla/DebugOnly.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/CustomElementsRegistry.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/Element.h"
@ -9359,3 +9360,123 @@ nsContentUtils::HttpsStateIsModern(nsIDocument* aDocument)
return false;
}
/* static */ CustomElementDefinition*
nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc,
const nsAString& aLocalName,
uint32_t aNameSpaceID,
const nsAString* aIs)
{
MOZ_ASSERT(aDoc);
// To support imported document.
nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
if (aNameSpaceID != kNameSpaceID_XHTML ||
!doc->GetDocShell()) {
return nullptr;
}
nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
if (!window) {
return nullptr;
}
RefPtr<CustomElementsRegistry> registry(window->CustomElements());
if (!registry) {
return nullptr;
}
return registry->LookupCustomElementDefinition(aLocalName, aIs);
}
/* static */ void
nsContentUtils::SetupCustomElement(Element* aElement,
const nsAString* aTypeExtension)
{
MOZ_ASSERT(aElement);
nsCOMPtr<nsIDocument> doc = aElement->OwnerDoc();
if (!doc) {
return;
}
// To support imported document.
doc = doc->MasterDocument();
if (aElement->GetNameSpaceID() != kNameSpaceID_XHTML ||
!doc->GetDocShell()) {
return;
}
nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
if (!window) {
return;
}
RefPtr<CustomElementsRegistry> registry(window->CustomElements());
if (!registry) {
return;
}
return registry->SetupCustomElement(aElement, aTypeExtension);
}
/* static */ void
nsContentUtils::EnqueueLifecycleCallback(nsIDocument* aDoc,
nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
LifecycleCallbackArgs* aArgs,
CustomElementDefinition* aDefinition)
{
MOZ_ASSERT(aDoc);
// To support imported document.
nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
if (!doc->GetDocShell()) {
return;
}
nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
if (!window) {
return;
}
RefPtr<CustomElementsRegistry> registry(window->CustomElements());
if (!registry) {
return;
}
registry->EnqueueLifecycleCallback(aType, aCustomElement, aArgs, aDefinition);
}
/* static */ void
nsContentUtils::GetCustomPrototype(nsIDocument* aDoc,
int32_t aNamespaceID,
nsIAtom* aAtom,
JS::MutableHandle<JSObject*> aPrototype)
{
MOZ_ASSERT(aDoc);
// To support imported document.
nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
if (aNamespaceID != kNameSpaceID_XHTML ||
!doc->GetDocShell()) {
return;
}
nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
if (!window) {
return;
}
RefPtr<CustomElementsRegistry> registry(window->CustomElements());
if (!registry) {
return;
}
return registry->GetCustomPrototype(aAtom, aPrototype);
}

View File

@ -34,6 +34,7 @@
#include "mozilla/Logging.h"
#include "mozilla/NotNull.h"
#include "nsIContentPolicy.h"
#include "nsIDocument.h"
#include "nsPIDOMWindow.h"
#if defined(XP_WIN)
@ -55,7 +56,6 @@ class nsIContent;
class nsIContentPolicy;
class nsIContentSecurityPolicy;
class nsIDocShellTreeItem;
class nsIDocument;
class nsIDocumentLoaderFactory;
class nsIDOMDocument;
class nsIDOMDocumentFragment;
@ -119,11 +119,13 @@ class ErrorResult;
class EventListenerManager;
namespace dom {
struct CustomElementDefinition;
class DocumentFragment;
class Element;
class EventTarget;
class IPCDataTransfer;
class IPCDataTransferItem;
struct LifecycleCallbackArgs;
class NodeInfo;
class nsIContentChild;
class nsIContentParent;
@ -2668,6 +2670,30 @@ public:
*/
static bool HttpsStateIsModern(nsIDocument* aDocument);
/**
* Looking up a custom element definition.
* https://html.spec.whatwg.org/#look-up-a-custom-element-definition
*/
static mozilla::dom::CustomElementDefinition*
LookupCustomElementDefinition(nsIDocument* aDoc,
const nsAString& aLocalName,
uint32_t aNameSpaceID,
const nsAString* aIs = nullptr);
static void SetupCustomElement(Element* aElement,
const nsAString* aTypeExtension = nullptr);
static void EnqueueLifecycleCallback(nsIDocument* aDoc,
nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
mozilla::dom::CustomElementDefinition* aDefinition = nullptr);
static void GetCustomPrototype(nsIDocument* aDoc,
int32_t aNamespaceID,
nsIAtom* aAtom,
JS::MutableHandle<JSObject*> prototype);
private:
static bool InitializeEventTable();

View File

@ -218,6 +218,7 @@
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/UndoManager.h"
#include "mozilla/dom/WebComponentsBinding.h"
#include "mozilla/dom/CustomElementsRegistry.h"
#include "nsFrame.h"
#include "nsDOMCaretPosition.h"
#include "nsIDOMHTMLTextAreaElement.h"
@ -368,156 +369,6 @@ nsIdentifierMapEntry::RemoveContentChangeCallback(nsIDocument::IDTargetObserver
}
}
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(Registry)
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Registry)
for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
aCallbacks.Trace(&iter.UserData()->mPrototype,
"mCustomDefinitions prototype",
aClosure);
}
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Registry)
for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
nsAutoPtr<LifecycleCallbacks>& callbacks = iter.UserData()->mCallbacks;
if (callbacks->mAttributeChangedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mCustomDefinitions->mCallbacks->mAttributeChangedCallback");
cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value());
}
if (callbacks->mCreatedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mCustomDefinitions->mCallbacks->mCreatedCallback");
cb.NoteXPCOMChild(callbacks->mCreatedCallback.Value());
}
if (callbacks->mAttachedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mCustomDefinitions->mCallbacks->mAttachedCallback");
cb.NoteXPCOMChild(callbacks->mAttachedCallback.Value());
}
if (callbacks->mDetachedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mCustomDefinitions->mCallbacks->mDetachedCallback");
cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Registry)
tmp->mCustomDefinitions.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Registry)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(Registry)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Registry)
Registry::Registry()
{
mozilla::HoldJSObjects(this);
}
Registry::~Registry()
{
mozilla::DropJSObjects(this);
}
void
CustomElementCallback::Call()
{
ErrorResult rv;
switch (mType) {
case nsIDocument::eCreated:
{
// For the duration of this callback invocation, the element is being created
// flag must be set to true.
mOwnerData->mElementIsBeingCreated = true;
// The callback hasn't actually been invoked yet, but we need to flip
// this now in order to enqueue the attached callback. This is a spec
// bug (w3c bug 27437).
mOwnerData->mCreatedCallbackInvoked = true;
// If ELEMENT is in a document and this document has a browsing context,
// enqueue attached callback for ELEMENT.
nsIDocument* document = mThisObject->GetComposedDoc();
if (document && document->GetDocShell()) {
document->EnqueueLifecycleCallback(nsIDocument::eAttached, mThisObject);
}
static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
mOwnerData->mElementIsBeingCreated = false;
break;
}
case nsIDocument::eAttached:
static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv);
break;
case nsIDocument::eDetached:
static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv);
break;
case nsIDocument::eAttributeChanged:
static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
mArgs.name, mArgs.oldValue, mArgs.newValue, rv);
break;
}
}
void
CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const
{
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
aCb.NoteXPCOMChild(mThisObject);
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
aCb.NoteXPCOMChild(mCallback);
}
CustomElementCallback::CustomElementCallback(Element* aThisObject,
nsIDocument::ElementCallbackType aCallbackType,
mozilla::dom::CallbackFunction* aCallback,
CustomElementData* aOwnerData)
: mThisObject(aThisObject),
mCallback(aCallback),
mType(aCallbackType),
mOwnerData(aOwnerData)
{
}
CustomElementData::CustomElementData(nsIAtom* aType)
: mType(aType),
mCurrentCallback(-1),
mElementIsBeingCreated(false),
mCreatedCallbackInvoked(true),
mAssociatedMicroTask(-1)
{
}
void
CustomElementData::RunCallbackQueue()
{
// Note: It's possible to re-enter this method.
while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) {
mCallbackQueue[mCurrentCallback]->Call();
}
mCallbackQueue.Clear();
mCurrentCallback = -1;
}
} // namespace dom
} // namespace mozilla
void
nsIdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
Element* aNewElement,
@ -1494,12 +1345,6 @@ nsDocument::nsDocument(const char* aContentType)
// void state used to differentiate an empty source from an unselected source
mPreloadPictureFoundSource.SetIsVoid(true);
if (!sProcessingStack) {
sProcessingStack.emplace();
// Add the base queue sentinel to the processing stack.
sProcessingStack->AppendElement((CustomElementData*) nullptr);
}
mEverInForeground = false;
}
@ -1617,8 +1462,6 @@ nsDocument::~nsDocument()
mInDestructor = true;
mInUnlinkOrDeletion = true;
mRegistry = nullptr;
mozilla::DropJSObjects(this);
// Clear mObservers to keep it in sync with the mutationobserver list
@ -1890,7 +1733,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegistry)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
// Traverse all our nsCOMArrays.
@ -1975,7 +1817,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRegistry)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMasterDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportManager)
@ -2225,13 +2066,6 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
}
mInUnlinkOrDeletion = oldVal;
if (!mMasterDocument) {
// "When creating an import, use the registry of the master document."
// Note: at this point the mMasterDocument is already set for imports
// (and only for imports)
mRegistry = nullptr;
}
// Reset our stylesheets
ResetStylesheetsToURI(aURI);
@ -4732,10 +4566,6 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
}
MaybeRescheduleAnimationFrameNotifications();
if (Preferences::GetBool("dom.webcomponents.enabled") ||
Preferences::GetBool("dom.webcomponents.customelements.enabled")) {
mRegistry = new Registry();
}
}
// Remember the pointer to our window (or lack there of), to avoid
@ -5561,26 +5391,29 @@ bool IsLowercaseASCII(const nsAString& aValue)
return true;
}
CustomElementDefinition*
nsDocument::LookupCustomElementDefinition(const nsAString& aLocalName,
uint32_t aNameSpaceID,
const nsAString* aIs)
already_AddRefed<mozilla::dom::CustomElementsRegistry>
nsDocument::GetCustomElementsRegistry()
{
if (!mRegistry || aNameSpaceID != kNameSpaceID_XHTML) {
nsAutoString contentType;
GetContentType(contentType);
if (!IsHTMLDocument() &&
!contentType.EqualsLiteral("application/xhtml+xml")) {
return nullptr;
}
nsCOMPtr<nsIAtom> localNameAtom = NS_Atomize(aLocalName);
nsCOMPtr<nsIAtom> typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom;
CustomElementDefinition* data;
CustomElementHashKey key(aNameSpaceID, typeAtom);
if (mRegistry->mCustomDefinitions.Get(&key, &data) &&
data->mLocalName == localNameAtom) {
return data;
nsCOMPtr<nsPIDOMWindowInner> window(
do_QueryInterface(mScriptGlobalObject ? mScriptGlobalObject
: GetScopeObject()));
if (!window) {
return nullptr;
}
return nullptr;
RefPtr<CustomElementsRegistry> registry = window->CustomElements();
if (!registry) {
return nullptr;
}
return registry.forget();
}
already_AddRefed<Element>
@ -5612,48 +5445,6 @@ nsDocument::CreateElement(const nsAString& aTagName,
return elem.forget();
}
void
nsDocument::SetupCustomElement(Element* aElement,
uint32_t aNamespaceID,
const nsAString* aTypeExtension)
{
if (!mRegistry || aNamespaceID != kNameSpaceID_XHTML) {
return;
}
nsCOMPtr<nsIAtom> tagAtom = aElement->NodeInfo()->NameAtom();
nsCOMPtr<nsIAtom> typeAtom = aTypeExtension ?
NS_Atomize(*aTypeExtension) : tagAtom;
if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
// Custom element setup in the parser happens after the "is"
// attribute is added.
aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true);
}
CustomElementDefinition* data = LookupCustomElementDefinition(
aElement->NodeInfo()->LocalName(), aNamespaceID, aTypeExtension);
if (!data) {
// The type extension doesn't exist in the registry,
// thus we don't need to enqueue callback or adjust
// the "is" attribute, but it is possibly an upgrade candidate.
RegisterUnresolvedElement(aElement, typeAtom);
return;
}
if (data->mLocalName != tagAtom) {
// The element doesn't match the local name for the
// definition, thus the element isn't a custom element
// and we don't need to do anything more.
return;
}
// Enqueuing the created callback will set the CustomElementData on the
// element, causing prototype swizzling to occur in Element::WrapObject.
EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data);
}
NS_IMETHODIMP
nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
const nsAString& aQualifiedName,
@ -5913,11 +5704,15 @@ nsDocument::CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value*
return true;
}
RefPtr<mozilla::dom::CustomElementsRegistry> registry = window->CustomElements();
if (!registry) {
return true;
}
nsCOMPtr<nsIAtom> typeAtom(NS_Atomize(elemName));
CustomElementHashKey key(kNameSpaceID_Unknown, typeAtom);
CustomElementDefinition* definition;
if (!document->mRegistry ||
!document->mRegistry->mCustomDefinitions.Get(&key, &definition)) {
CustomElementDefinition* definition = registry->mCustomDefinitions.Get(&key);
if (!definition) {
return true;
}
@ -5931,7 +5726,7 @@ nsDocument::CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value*
// This element is a custom element by extension, thus we need to
// do some special setup. For non-extended custom elements, this happens
// when the element is created.
document->SetupCustomElement(element, definition->mNamespaceID, &elemName);
nsContentUtils::SetupCustomElement(element, &elemName);
}
nsresult rv = nsContentUtils::WrapNative(aCx, element, element, args.rval());
@ -5972,228 +5767,18 @@ nsDocument::IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject)
return false;
}
nsresult
nsDocument::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName)
{
if (!mRegistry) {
return NS_OK;
}
mozilla::dom::NodeInfo* info = aElement->NodeInfo();
// Candidate may be a custom element through extension,
// in which case the custom element type name will not
// match the element tag name. e.g. <button is="x-button">.
nsCOMPtr<nsIAtom> typeName = aTypeName;
if (!typeName) {
typeName = info->NameAtom();
}
CustomElementHashKey key(info->NamespaceID(), typeName);
if (mRegistry->mCustomDefinitions.Get(&key)) {
return NS_OK;
}
nsTArray<nsWeakPtr>* unresolved;
mRegistry->mCandidatesMap.Get(&key, &unresolved);
if (!unresolved) {
unresolved = new nsTArray<nsWeakPtr>();
// Ownership of unresolved is taken by mCandidatesMap.
mRegistry->mCandidatesMap.Put(&key, unresolved);
}
nsWeakPtr* elem = unresolved->AppendElement();
*elem = do_GetWeakReference(aElement);
aElement->AddStates(NS_EVENT_STATE_UNRESOLVED);
return NS_OK;
}
void
nsDocument::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
LifecycleCallbackArgs* aArgs,
CustomElementDefinition* aDefinition)
{
if (!mRegistry) {
// The element might not belong to a document that
// has a browsing context, and thus no registry.
return;
}
CustomElementData* elementData = aCustomElement->GetCustomElementData();
// Let DEFINITION be ELEMENT's definition
CustomElementDefinition* definition = aDefinition;
if (!definition) {
mozilla::dom::NodeInfo* info = aCustomElement->NodeInfo();
// Make sure we get the correct definition in case the element
// is a extended custom element e.g. <button is="x-button">.
nsCOMPtr<nsIAtom> typeAtom = elementData ?
elementData->mType.get() : info->NameAtom();
CustomElementHashKey key(info->NamespaceID(), typeAtom);
if (!mRegistry->mCustomDefinitions.Get(&key, &definition) ||
definition->mLocalName != info->NameAtom()) {
// Trying to enqueue a callback for an element that is not
// a custom element. We are done, nothing to do.
return;
}
}
if (!elementData) {
// Create the custom element data the first time
// that we try to enqueue a callback.
elementData = new CustomElementData(definition->mType);
// aCustomElement takes ownership of elementData
aCustomElement->SetCustomElementData(elementData);
MOZ_ASSERT(aType == nsIDocument::eCreated,
"First callback should be the created callback");
}
// Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
CallbackFunction* func = nullptr;
switch (aType) {
case nsIDocument::eCreated:
if (definition->mCallbacks->mCreatedCallback.WasPassed()) {
func = definition->mCallbacks->mCreatedCallback.Value();
}
break;
case nsIDocument::eAttached:
if (definition->mCallbacks->mAttachedCallback.WasPassed()) {
func = definition->mCallbacks->mAttachedCallback.Value();
}
break;
case nsIDocument::eDetached:
if (definition->mCallbacks->mDetachedCallback.WasPassed()) {
func = definition->mCallbacks->mDetachedCallback.Value();
}
break;
case nsIDocument::eAttributeChanged:
if (definition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
func = definition->mCallbacks->mAttributeChangedCallback.Value();
}
break;
}
// If there is no such callback, stop.
if (!func) {
return;
}
if (aType == nsIDocument::eCreated) {
elementData->mCreatedCallbackInvoked = false;
} else if (!elementData->mCreatedCallbackInvoked) {
// Callbacks other than created callback must not be enqueued
// until after the created callback has been invoked.
return;
}
// Add CALLBACK to ELEMENT's callback queue.
CustomElementCallback* callback = new CustomElementCallback(aCustomElement,
aType,
func,
elementData);
// Ownership of callback is taken by mCallbackQueue.
elementData->mCallbackQueue.AppendElement(callback);
if (aArgs) {
callback->SetArgs(*aArgs);
}
if (!elementData->mElementIsBeingCreated) {
CustomElementData* lastData =
sProcessingStack->SafeLastElement(nullptr);
// A new element queue needs to be pushed if the queue at the
// top of the stack is associated with another microtask level.
bool shouldPushElementQueue =
(!lastData || lastData->mAssociatedMicroTask <
static_cast<int32_t>(nsContentUtils::MicroTaskLevel()));
// Push a new element queue onto the processing stack when appropriate
// (when we enter a new microtask).
if (shouldPushElementQueue) {
// Push a sentinel value on the processing stack to mark the
// boundary between the element queues.
sProcessingStack->AppendElement((CustomElementData*) nullptr);
}
sProcessingStack->AppendElement(elementData);
elementData->mAssociatedMicroTask =
static_cast<int32_t>(nsContentUtils::MicroTaskLevel());
// Add a script runner to pop and process the element queue at
// the top of the processing stack.
if (shouldPushElementQueue) {
// Lifecycle callbacks enqueued by user agent implementation
// should be invoked prior to returning control back to script.
// Create a script runner to process the top of the processing
// stack as soon as it is safe to run script.
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction(&nsDocument::ProcessTopElementQueue);
nsContentUtils::AddScriptRunner(runnable);
}
}
}
// static
void
nsDocument::ProcessTopElementQueue()
{
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
nsTArray<RefPtr<CustomElementData>>& stack = *sProcessingStack;
uint32_t firstQueue = stack.LastIndexOf((CustomElementData*) nullptr);
for (uint32_t i = firstQueue + 1; i < stack.Length(); ++i) {
// Callback queue may have already been processed in an earlier
// element queue or in an element queue that was popped
// off more recently.
if (stack[i]->mAssociatedMicroTask != -1) {
stack[i]->RunCallbackQueue();
stack[i]->mAssociatedMicroTask = -1;
}
}
// If this was actually the base element queue, don't bother trying to pop
// the first "queue" marker (sentinel).
if (firstQueue != 0) {
stack.SetLength(firstQueue);
} else {
// Don't pop sentinel for base element queue.
stack.SetLength(1);
}
}
bool
nsDocument::RegisterEnabled()
{
static bool sPrefValue =
Preferences::GetBool("dom.webcomponents.enabled", false);
return sPrefValue;
}
// static
Maybe<nsTArray<RefPtr<mozilla::dom::CustomElementData>>>
nsDocument::sProcessingStack;
void
nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
const ElementRegistrationOptions& aOptions,
JS::MutableHandle<JSObject*> aRetval,
ErrorResult& rv)
{
if (!mRegistry) {
RefPtr<CustomElementsRegistry> registry(GetCustomElementsRegistry());
if (!registry) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
Registry::DefinitionMap& definitions = mRegistry->mCustomDefinitions;
// Unconditionally convert TYPE to lowercase.
nsAutoString lcType;
nsContentUtils::ASCIIToLower(aType, lcType);
@ -6217,7 +5802,7 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
// DuplicateDefinition and stop.
// Note that we need to find existing custom elements from either namespace.
CustomElementHashKey duplicateFinder(kNameSpaceID_Unknown, typeAtom);
if (definitions.Get(&duplicateFinder)) {
if (registry->mCustomDefinitions.Get(&duplicateFinder)) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
@ -6359,11 +5944,11 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
callbacks,
namespaceID,
0 /* TODO dependent on HTML imports. Bug 877072 */);
definitions.Put(&key, definition);
registry->mCustomDefinitions.Put(&key, definition);
// Do element upgrade.
nsAutoPtr<nsTArray<nsWeakPtr>> candidates;
mRegistry->mCandidatesMap.RemoveAndForget(&key, candidates);
registry->mCandidatesMap.RemoveAndForget(&key, candidates);
if (candidates) {
for (size_t i = 0; i < candidates->Length(); ++i) {
nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i));
@ -6397,7 +5982,10 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
}
}
EnqueueLifecycleCallback(nsIDocument::eCreated, elem, nullptr, definition);
if (GetDocShell()) {
nsContentUtils::EnqueueLifecycleCallback(
this, nsIDocument::eCreated, elem, nullptr, definition);
}
}
}
@ -6435,14 +6023,6 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
aRetval.set(wrappedConstructor);
}
void
nsDocument::UseRegistryFromDocument(nsIDocument* aDocument)
{
nsDocument* doc = static_cast<nsDocument*>(aDocument);
MOZ_ASSERT(!mRegistry, "There should be no existing registry.");
mRegistry = doc->mRegistry;
}
NS_IMETHODIMP
nsDocument::GetElementsByTagName(const nsAString& aTagname,
nsIDOMNodeList** aReturn)
@ -8987,8 +8567,6 @@ nsDocument::Destroy()
// leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
// tearing down all those frame trees right now is the right thing to do.
mExternalResourceMap.Shutdown();
mRegistry = nullptr;
}
void
@ -12577,12 +12155,6 @@ nsDocument::OnAppThemeChanged()
}
}
void
nsDocument::XPCOMShutdown()
{
sProcessingStack.reset();
}
void
nsDocument::UpdateVisibilityState()
{
@ -13467,7 +13039,8 @@ nsDocument::CheckCustomElementName(const ElementCreationOptions& aOptions,
nsString* is = const_cast<nsString*>(&(aOptions.mIs.Value()));
// Throw NotFoundError if 'is' is not-null and definition is null
if (!LookupCustomElementDefinition(aLocalName, aNamespaceID, is)) {
if (!nsContentUtils::LookupCustomElementDefinition(this, aLocalName,
aNamespaceID, is)) {
rv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
}

View File

@ -263,107 +263,6 @@ private:
namespace mozilla {
namespace dom {
struct LifecycleCallbackArgs
{
nsString name;
nsString oldValue;
nsString newValue;
};
struct CustomElementData;
class CustomElementCallback
{
public:
CustomElementCallback(Element* aThisObject,
nsIDocument::ElementCallbackType aCallbackType,
mozilla::dom::CallbackFunction* aCallback,
CustomElementData* aOwnerData);
void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
void Call();
void SetArgs(LifecycleCallbackArgs& aArgs)
{
MOZ_ASSERT(mType == nsIDocument::eAttributeChanged,
"Arguments are only used by attribute changed callback.");
mArgs = aArgs;
}
private:
// The this value to use for invocation of the callback.
RefPtr<mozilla::dom::Element> mThisObject;
RefPtr<mozilla::dom::CallbackFunction> mCallback;
// The type of callback (eCreated, eAttached, etc.)
nsIDocument::ElementCallbackType mType;
// Arguments to be passed to the callback,
// used by the attribute changed callback.
LifecycleCallbackArgs mArgs;
// CustomElementData that contains this callback in the
// callback queue.
CustomElementData* mOwnerData;
};
// Each custom element has an associated callback queue and an element is
// being created flag.
struct CustomElementData
{
NS_INLINE_DECL_REFCOUNTING(CustomElementData)
explicit CustomElementData(nsIAtom* aType);
// Objects in this array are transient and empty after each microtask
// checkpoint.
nsTArray<nsAutoPtr<CustomElementCallback>> mCallbackQueue;
// Custom element type, for <button is="x-button"> or <x-button>
// this would be x-button.
nsCOMPtr<nsIAtom> mType;
// The callback that is next to be processed upon calling RunCallbackQueue.
int32_t mCurrentCallback;
// Element is being created flag as described in the custom elements spec.
bool mElementIsBeingCreated;
// Flag to determine if the created callback has been invoked, thus it
// determines if other callbacks can be enqueued.
bool mCreatedCallbackInvoked;
// The microtask level associated with the callbacks in the callback queue,
// it is used to determine if a new queue needs to be pushed onto the
// processing stack.
int32_t mAssociatedMicroTask;
// Empties the callback queue.
void RunCallbackQueue();
private:
virtual ~CustomElementData() {}
};
class Registry : public nsISupports
{
public:
friend class ::nsDocument;
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Registry)
Registry();
protected:
virtual ~Registry();
typedef nsClassHashtable<mozilla::dom::CustomElementHashKey,
mozilla::dom::CustomElementDefinition>
DefinitionMap;
typedef nsClassHashtable<mozilla::dom::CustomElementHashKey,
nsTArray<nsWeakPtr>>
CandidateMap;
// Hashtable for custom element definitions in web components.
// Custom prototypes are stored in the compartment where
// registerElement was called.
DefinitionMap mCustomDefinitions;
// The "upgrade candidates map" from the web components spec. Maps from a
// namespace id and local name to a list of elements to upgrade if that
// element is registered as a custom element.
CandidateMap mCandidatesMap;
};
} // namespace dom
} // namespace mozilla
@ -1218,36 +1117,6 @@ public:
virtual nsIDOMNode* AsDOMNode() override { return this; }
virtual void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
mozilla::dom::CustomElementDefinition* aDefinition = nullptr) override;
static void ProcessTopElementQueue();
void GetCustomPrototype(int32_t aNamespaceID,
nsIAtom* aAtom,
JS::MutableHandle<JSObject*> prototype)
{
if (!mRegistry) {
prototype.set(nullptr);
return;
}
mozilla::dom::CustomElementHashKey key(aNamespaceID, aAtom);
mozilla::dom::CustomElementDefinition* definition;
if (mRegistry->mCustomDefinitions.Get(&key, &definition)) {
prototype.set(definition->mPrototype);
} else {
prototype.set(nullptr);
}
}
static bool RegisterEnabled();
virtual nsresult RegisterUnresolvedElement(mozilla::dom::Element* aElement,
nsIAtom* aTypeName = nullptr) override;
// WebIDL bits
virtual mozilla::dom::DOMImplementation*
GetImplementation(mozilla::ErrorResult& rv) override;
@ -1268,7 +1137,6 @@ public:
const nsAString& aQualifiedName,
const mozilla::dom::ElementCreationOptions& aOptions,
mozilla::ErrorResult& rv) override;
virtual void UseRegistryFromDocument(nsIDocument* aDocument) override;
virtual nsIDocument* MasterDocument() override
{
@ -1280,7 +1148,6 @@ public:
{
MOZ_ASSERT(master);
mMasterDocument = master;
UseRegistryFromDocument(mMasterDocument);
}
virtual bool IsMasterDocument() override
@ -1397,8 +1264,6 @@ public:
// Set our title
virtual void SetTitle(const nsAString& aTitle, mozilla::ErrorResult& rv) override;
static void XPCOMShutdown();
bool mIsTopLevelContentDocument: 1;
bool mIsContentDocument: 1;
@ -1503,23 +1368,8 @@ protected:
nsWeakPtr mFullscreenRoot;
private:
// Array representing the processing stack in the custom elements
// specification. The processing stack is conceptually a stack of
// element queues. Each queue is represented by a sequence of
// CustomElementData in this array, separated by nullptr that
// represent the boundaries of the items in the stack. The first
// queue in the stack is the base element queue.
static mozilla::Maybe<nsTArray<RefPtr<mozilla::dom::CustomElementData>>> sProcessingStack;
static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
/**
* Looking up a custom element definition.
* https://html.spec.whatwg.org/#look-up-a-custom-element-definition
*/
mozilla::dom::CustomElementDefinition* LookupCustomElementDefinition(
const nsAString& aLocalName, uint32_t aNameSpaceID, const nsAString* aIs);
/**
* Check if the passed custom element name, aOptions.mIs, is a registered
* custom element type or not, then return the custom element name for future
@ -1535,18 +1385,11 @@ private:
ErrorResult& rv);
public:
// Enqueue created callback or register upgrade candidate for
// newly created custom elements, possibly extending an existing type.
// ex. <x-button>, <button is="x-button> (type extension)
virtual void SetupCustomElement(Element* aElement,
uint32_t aNamespaceID,
const nsAString* aTypeExtension) override;
virtual already_AddRefed<mozilla::dom::CustomElementsRegistry>
GetCustomElementsRegistry() override;
static bool IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject);
// The "registry" from the web components spec.
RefPtr<mozilla::dom::Registry> mRegistry;
RefPtr<mozilla::EventListenerManager> mListenerManager;
RefPtr<mozilla::dom::StyleSheetList> mDOMStyleSheets;
RefPtr<nsDOMStyleSheetSetList> mStyleSheetSetList;

View File

@ -2482,34 +2482,13 @@ public:
nsIDocument* GetTopLevelContentDocument();
/**
* Registers an unresolved custom element that is a candidate for
* upgrade when the definition is registered via registerElement.
* |aTypeName| is the name of the custom element type, if it is not
* provided, then element name is used. |aTypeName| should be provided
* when registering a custom element that extends an existing
* element. e.g. <button is="x-button">.
*/
virtual nsresult RegisterUnresolvedElement(Element* aElement,
nsIAtom* aTypeName = nullptr) = 0;
virtual void EnqueueLifecycleCallback(ElementCallbackType aType,
Element* aCustomElement,
mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
mozilla::dom::CustomElementDefinition* aDefinition = nullptr) = 0;
virtual void SetupCustomElement(Element* aElement,
uint32_t aNamespaceID,
const nsAString* aTypeExtension = nullptr) = 0;
virtual void
RegisterElement(JSContext* aCx, const nsAString& aName,
const mozilla::dom::ElementRegistrationOptions& aOptions,
JS::MutableHandle<JSObject*> aRetval,
mozilla::ErrorResult& rv) = 0;
/**
* In some cases, new document instances must be associated with
* an existing web components custom element registry as specified.
*/
virtual void UseRegistryFromDocument(nsIDocument* aDocument) = 0;
virtual already_AddRefed<mozilla::dom::CustomElementsRegistry>
GetCustomElementsRegistry() = 0;
already_AddRefed<nsContentList>
GetElementsByTagName(const nsAString& aTagName)

View File

@ -471,15 +471,14 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep,
// enqueing created callback and prototype swizzling.
Element* elem = clone->AsElement();
if (nsContentUtils::IsCustomElementName(nodeInfo->NameAtom())) {
elem->OwnerDoc()->SetupCustomElement(elem, nodeInfo->NamespaceID());
nsContentUtils::SetupCustomElement(elem);
} else {
// Check if node may be custom element by type extension.
// ex. <button is="x-button">
nsAutoString extension;
if (elem->GetAttr(kNameSpaceID_None, nsGkAtoms::is, extension) &&
!extension.IsEmpty()) {
elem->OwnerDoc()->SetupCustomElement(elem, nodeInfo->NamespaceID(),
&extension);
nsContentUtils::SetupCustomElement(elem, &extension);
}
}
}

View File

@ -257,14 +257,12 @@ NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&&
if ((tag == eHTMLTag_userdefined &&
nsContentUtils::IsCustomElementName(name)) ||
aIs) {
nsIDocument* doc = nodeInfo->GetDocument();
NS_IF_ADDREF(*aResult = NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
if (!*aResult) {
return NS_ERROR_OUT_OF_MEMORY;
}
doc->SetupCustomElement(*aResult, kNameSpaceID_XHTML, aIs);
nsContentUtils::SetupCustomElement(*aResult, aIs);
return NS_OK;
}

View File

@ -29,21 +29,6 @@ function createdCallbackFromMainDoc() {
document.createElement("x-associated-doc-callback-elem");
}
function createdCallbackFromAssociatedDoc() {
var createdCallbackCalled = false;
var assocDoc = document.implementation.createHTMLDocument();
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
is(createdCallbackCalled, false, "created callback should only be called once in this tests.");
createdCallbackCalled = true;
runNextTest();
};
assocDoc.registerElement("x-main-doc-callback-elem", { prototype: proto });
assocDoc.createElement("x-main-doc-callback-elem");
}
function createdCallbackFromDocHTMLNamespace() {
var createdCallbackCalled = false;
var assocDoc = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", null);
@ -80,7 +65,6 @@ function runNextTest() {
}
var testFunctions = [
createdCallbackFromAssociatedDoc,
createdCallbackFromMainDoc,
createdCallbackFromDocHTMLNamespace,
registerNoRegistryDoc,

View File

@ -116,10 +116,10 @@ using namespace mozilla::system;
#include "nsPermissionManager.h"
#include "nsCookieService.h"
#include "nsApplicationCacheService.h"
#include "mozilla/dom/CustomElementsRegistry.h"
#include "mozilla/dom/time/DateCacheCleaner.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/IMEStateManager.h"
#include "nsDocument.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "CameraPreferences.h"
#include "TouchManager.h"
@ -437,7 +437,7 @@ nsLayoutStatics::Shutdown()
DisplayItemClip::Shutdown();
nsDocument::XPCOMShutdown();
CustomElementsRegistry::XPCOMShutdown();
CacheObserver::Shutdown();

View File

@ -440,9 +440,7 @@ nsHtml5TreeOperation::CreateElement(int32_t aNs,
// Custom element setup may be needed if there is an "is" attribute.
if (kNameSpaceID_None == nsuri && !prefix && nsGkAtoms::is == localName) {
newContent->OwnerDoc()->SetupCustomElement(newContent,
newContent->GetNameSpaceID(),
&value);
nsContentUtils::SetupCustomElement(newContent, &value);
}
}
}