diff options
Diffstat (limited to 'dom/base')
36 files changed, 1842 insertions, 1365 deletions
diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp index 3f202d33b..99452df65 100644 --- a/dom/base/CustomElementRegistry.cpp +++ b/dom/base/CustomElementRegistry.cpp @@ -6,9 +6,11 @@ #include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/CycleCollectedJSContext.h" #include "mozilla/dom/CustomElementRegistryBinding.h" #include "mozilla/dom/HTMLElementBinding.h" #include "mozilla/dom/WebComponentsBinding.h" +#include "mozilla/dom/DocGroup.h" #include "nsIParserService.h" #include "jsapi.h" @@ -18,40 +20,21 @@ namespace dom { void CustomElementCallback::Call() { - ErrorResult rv; + IgnoredErrorResult 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; + case nsIDocument::eConnected: + static_cast<LifecycleConnectedCallback *>(mCallback.get())->Call(mThisObject, rv); break; - } - case nsIDocument::eAttached: - static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv); + case nsIDocument::eDisconnected: + static_cast<LifecycleDisconnectedCallback *>(mCallback.get())->Call(mThisObject, rv); break; - case nsIDocument::eDetached: - static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv); + case nsIDocument::eAdopted: + static_cast<LifecycleAdoptedCallback *>(mCallback.get())->Call(mThisObject, + mAdoptedCallbackArgs.mOldDocument, mAdoptedCallbackArgs.mNewDocument, rv); break; case nsIDocument::eAttributeChanged: static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject, - mArgs.name, mArgs.oldValue, mArgs.newValue, rv); + mArgs.name, mArgs.oldValue, mArgs.newValue, mArgs.namespaceURI, rv); break; } } @@ -68,87 +51,164 @@ CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const CustomElementCallback::CustomElementCallback(Element* aThisObject, nsIDocument::ElementCallbackType aCallbackType, - mozilla::dom::CallbackFunction* aCallback, - CustomElementData* aOwnerData) + mozilla::dom::CallbackFunction* aCallback) : mThisObject(aThisObject), mCallback(aCallback), - mType(aCallbackType), - mOwnerData(aOwnerData) + mType(aCallbackType) +{ +} + +//----------------------------------------------------- +// CustomElementConstructor + +already_AddRefed<Element> +CustomElementConstructor::Construct(const char* aExecutionReason, + ErrorResult& aRv) { + CallSetup s(this, aRv, aExecutionReason, + CallbackFunction::eRethrowExceptions); + + JSContext* cx = s.GetContext(); + if (!cx) { + MOZ_ASSERT(aRv.Failed()); + return nullptr; + } + + JS::Rooted<JSObject*> result(cx); + JS::Rooted<JS::Value> constructor(cx, JS::ObjectValue(*mCallback)); + if (!JS::Construct(cx, constructor, JS::HandleValueArray::empty(), &result)) { + aRv.NoteJSContextException(cx); + return nullptr; + } + + RefPtr<Element> element; + if (NS_FAILED(UNWRAP_OBJECT(Element, &result, element))) { + return nullptr; + } + + return element.forget(); } +//----------------------------------------------------- +// CustomElementData + CustomElementData::CustomElementData(nsIAtom* aType) - : mType(aType), - mCurrentCallback(-1), - mElementIsBeingCreated(false), - mCreatedCallbackInvoked(true), - mAssociatedMicroTask(-1) + : CustomElementData(aType, CustomElementData::State::eUndefined) +{ +} + +CustomElementData::CustomElementData(nsIAtom* aType, State aState) + : mState(aState) + , mType(aType) { } void -CustomElementData::RunCallbackQueue() +CustomElementData::SetCustomElementDefinition(CustomElementDefinition* aDefinition) { - // Note: It's possible to re-enter this method. - while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) { - mCallbackQueue[mCurrentCallback]->Call(); + MOZ_ASSERT(mState == State::eCustom); + MOZ_ASSERT(!mCustomElementDefinition); + MOZ_ASSERT(aDefinition->mType == mType); + + mCustomElementDefinition = aDefinition; +} + +CustomElementDefinition* +CustomElementData::GetCustomElementDefinition() +{ + MOZ_ASSERT(mCustomElementDefinition ? mState == State::eCustom + : mState != State::eCustom); + + return mCustomElementDefinition; +} + +nsIAtom* +CustomElementData::GetCustomElementType() +{ + return mType; +} + +void +CustomElementData::Traverse(nsCycleCollectionTraversalCallback& aCb) const +{ + for (uint32_t i = 0; i < mReactionQueue.Length(); i++) { + if (mReactionQueue[i]) { + mReactionQueue[i]->Traverse(aCb); + } } - mCallbackQueue.Clear(); - mCurrentCallback = -1; + if (mCustomElementDefinition) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCustomElementDefinition"); + aCb.NoteNativeChild(mCustomElementDefinition, + NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition)); + } } +void +CustomElementData::Unlink() +{ + mReactionQueue.Clear(); + mCustomElementDefinition = nullptr; +} + +//----------------------------------------------------- +// CustomElementRegistry + +namespace { + +class MOZ_RAII AutoConstructionStackEntry final +{ +public: + AutoConstructionStackEntry(nsTArray<RefPtr<nsGenericHTMLElement>>& aStack, + nsGenericHTMLElement* aElement) + : mStack(aStack) + { + mIndex = mStack.Length(); + mStack.AppendElement(aElement); + } + + ~AutoConstructionStackEntry() + { + MOZ_ASSERT(mIndex == mStack.Length() - 1, + "Removed element should be the last element"); + mStack.RemoveElementAt(mIndex); + } + +private: + nsTArray<RefPtr<nsGenericHTMLElement>>& mStack; + uint32_t mIndex; +}; + +} // namespace anonymous + // Only needed for refcounted objects. NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry) - tmp->mCustomDefinitions.Clear(); + tmp->mConstructors.clear(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomDefinitions) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap) 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(CustomElementRegistry) - 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(mCustomDefinitions) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry) for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) { - aCallbacks.Trace(&iter.UserData()->mConstructor, - "mCustomDefinitions constructor", - aClosure); aCallbacks.Trace(&iter.UserData()->mPrototype, "mCustomDefinitions prototype", aClosure); } + for (ConstructorMap::Enum iter(tmp->mConstructors); !iter.empty(); iter.popFront()) { + aCallbacks.Trace(&iter.front().mutableKey(), + "mConstructors key", + aClosure); + } NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END @@ -163,97 +223,52 @@ NS_INTERFACE_MAP_END /* static */ bool CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject) { - return Preferences::GetBool("dom.webcomponents.customelements.enabled") || - Preferences::GetBool("dom.webcomponents.enabled"); + return nsContentUtils::IsCustomElementsEnabled(); } -/* static */ already_AddRefed<CustomElementRegistry> -CustomElementRegistry::Create(nsPIDOMWindowInner* aWindow) +CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) + : mWindow(aWindow) + , mIsCustomDefinitionRunning(false) { MOZ_ASSERT(aWindow); MOZ_ASSERT(aWindow->IsInnerWindow()); + MOZ_ALWAYS_TRUE(mConstructors.init()); - if (!aWindow->GetDocShell()) { - return nullptr; - } - - if (!IsCustomElementEnabled()) { - return nullptr; - } - - RefPtr<CustomElementRegistry> customElementRegistry = - new CustomElementRegistry(aWindow); - return customElementRegistry.forget(); -} - -/* static */ void -CustomElementRegistry::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); - } + mozilla::HoldJSObjects(this); } -/* static */ void -CustomElementRegistry::XPCOMShutdown() +CustomElementRegistry::~CustomElementRegistry() { - sProcessingStack.reset(); + mozilla::DropJSObjects(this); } -/* static */ Maybe<nsTArray<RefPtr<CustomElementData>>> -CustomElementRegistry::sProcessingStack; - -CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) - : mWindow(aWindow) - , mIsCustomDefinitionRunning(false) +CustomElementDefinition* +CustomElementRegistry::LookupCustomElementDefinition(nsIAtom* aNameAtom, + nsIAtom* aTypeAtom) const { - mozilla::HoldJSObjects(this); - - if (!sProcessingStack) { - sProcessingStack.emplace(); - // Add the base queue sentinel to the processing stack. - sProcessingStack->AppendElement((CustomElementData*) nullptr); + CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom); + if (data && data->mLocalName == aNameAtom) { + return data; } -} -CustomElementRegistry::~CustomElementRegistry() -{ - mozilla::DropJSObjects(this); + return nullptr; } CustomElementDefinition* -CustomElementRegistry::LookupCustomElementDefinition(const nsAString& aLocalName, - const nsAString* aIs) const +CustomElementRegistry::LookupCustomElementDefinition(JSContext* aCx, + JSObject* aConstructor) const { - nsCOMPtr<nsIAtom> localNameAtom = NS_Atomize(aLocalName); - nsCOMPtr<nsIAtom> typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom; + JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrap(aConstructor)); - CustomElementDefinition* data = mCustomDefinitions.Get(typeAtom); - if (data && data->mLocalName == localNameAtom) { - return data; + const auto& ptr = mConstructors.lookup(constructor); + if (!ptr) { + return nullptr; } - return nullptr; + CustomElementDefinition* definition = mCustomDefinitions.GetWeak(ptr->value()); + MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions"); + + return definition; } void @@ -269,7 +284,7 @@ CustomElementRegistry::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTy typeName = info->NameAtom(); } - if (mCustomDefinitions.Get(typeName)) { + if (mCustomDefinitions.GetWeak(typeName)) { return; } @@ -282,171 +297,129 @@ CustomElementRegistry::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTy } void -CustomElementRegistry::SetupCustomElement(Element* aElement, - const nsAString* aTypeExtension) +CustomElementRegistry::UnregisterUnresolvedElement(Element* aElement, + nsIAtom* aTypeName) { - 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; + nsTArray<nsWeakPtr>* candidates; + if (mCandidatesMap.Get(aTypeName, &candidates)) { + MOZ_ASSERT(candidates); + // We don't need to iterate the candidates array and remove the element from + // the array for performance reason. It'll be handled by bug 1396620. + for (size_t i = 0; i < candidates->Length(); ++i) { + nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i)); + if (elem && elem.get() == aElement) { + candidates->RemoveElementAt(i); + } + } } - - // 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 -CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, - Element* aCustomElement, - LifecycleCallbackArgs* aArgs, - CustomElementDefinition* aDefinition) +/* static */ UniquePtr<CustomElementCallback> +CustomElementRegistry::CreateCustomElementCallback( + nsIDocument::ElementCallbackType aType, Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + 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(); + MOZ_ASSERT(aDefinition, "CustomElementDefinition should not be null"); - definition = mCustomDefinitions.Get(typeAtom); - 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"); - } + MOZ_ASSERT(aCustomElement->GetCustomElementData(), + "CustomElementData should exist"); // 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(); + case nsIDocument::eConnected: + if (aDefinition->mCallbacks->mConnectedCallback.WasPassed()) { + func = aDefinition->mCallbacks->mConnectedCallback.Value(); } break; - case nsIDocument::eAttached: - if (definition->mCallbacks->mAttachedCallback.WasPassed()) { - func = definition->mCallbacks->mAttachedCallback.Value(); + case nsIDocument::eDisconnected: + if (aDefinition->mCallbacks->mDisconnectedCallback.WasPassed()) { + func = aDefinition->mCallbacks->mDisconnectedCallback.Value(); } break; - case nsIDocument::eDetached: - if (definition->mCallbacks->mDetachedCallback.WasPassed()) { - func = definition->mCallbacks->mDetachedCallback.Value(); + case nsIDocument::eAdopted: + if (aDefinition->mCallbacks->mAdoptedCallback.WasPassed()) { + func = aDefinition->mCallbacks->mAdoptedCallback.Value(); } break; case nsIDocument::eAttributeChanged: - if (definition->mCallbacks->mAttributeChangedCallback.WasPassed()) { - func = definition->mCallbacks->mAttributeChangedCallback.Value(); + if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) { + func = aDefinition->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; + return nullptr; } // 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); + auto callback = + MakeUnique<CustomElementCallback>(aCustomElement, aType, func); + 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())); + if (aAdoptedCallbackArgs) { + callback->SetAdoptedCallbackArgs(*aAdoptedCallbackArgs); + } + return Move(callback); +} - // 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); +/* static */ void +CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, + Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + CustomElementDefinition* aDefinition) +{ + CustomElementDefinition* definition = aDefinition; + if (!definition) { + definition = aCustomElement->GetCustomElementDefinition(); + if (!definition || + definition->mLocalName != aCustomElement->NodeInfo()->NameAtom()) { + return; } + } - 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(&CustomElementRegistry::ProcessTopElementQueue); - nsContentUtils::AddScriptRunner(runnable); + auto callback = + CreateCustomElementCallback(aType, aCustomElement, aArgs, + aAdoptedCallbackArgs, definition); + if (!callback) { + return; + } + + DocGroup* docGroup = aCustomElement->OwnerDoc()->GetDocGroup(); + if (!docGroup) { + return; + } + + if (aType == nsIDocument::eAttributeChanged) { + nsCOMPtr<nsIAtom> attrName = NS_Atomize(aArgs->name); + if (definition->mObservedAttributes.IsEmpty() || + !definition->mObservedAttributes.Contains(attrName)) { + return; } } + + CustomElementReactionsStack* reactionsStack = + docGroup->CustomElementReactionsStack(); + reactionsStack->EnqueueCallbackReaction(aCustomElement, Move(callback)); } void CustomElementRegistry::GetCustomPrototype(nsIAtom* aAtom, JS::MutableHandle<JSObject*> aPrototype) { - mozilla::dom::CustomElementDefinition* definition = mCustomDefinitions.Get(aAtom); + mozilla::dom::CustomElementDefinition* definition = + mCustomDefinitions.GetWeak(aAtom); if (definition) { aPrototype.set(definition->mPrototype); } else { @@ -455,48 +428,29 @@ CustomElementRegistry::GetCustomPrototype(nsIAtom* aAtom, } void -CustomElementRegistry::UpgradeCandidates(JSContext* aCx, - nsIAtom* aKey, - CustomElementDefinition* aDefinition) +CustomElementRegistry::UpgradeCandidates(nsIAtom* aKey, + CustomElementDefinition* aDefinition, + ErrorResult& aRv) { + DocGroup* docGroup = mWindow->GetDocGroup(); + if (!docGroup) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + // TODO: Bug 1326028 - Upgrade custom element in shadow-including tree order nsAutoPtr<nsTArray<nsWeakPtr>> candidates; mCandidatesMap.RemoveAndForget(aKey, candidates); if (candidates) { + CustomElementReactionsStack* reactionsStack = + docGroup->CustomElementReactionsStack(); for (size_t i = 0; i < candidates->Length(); ++i) { nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i)); if (!elem) { continue; } - elem->RemoveStates(NS_EVENT_STATE_UNRESOLVED); - - // Make sure that the element name matches the name in the definition. - // (e.g. a definition for x-button extending button should match - // <button is="x-button"> but not <x-button>. - if (elem->NodeInfo()->NameAtom() != aDefinition->mLocalName) { - //Skip over this element because definition does not apply. - continue; - } - - MOZ_ASSERT(elem->IsHTMLElement(aDefinition->mLocalName)); - nsWrapperCache* cache; - CallQueryInterface(elem, &cache); - MOZ_ASSERT(cache, "Element doesn't support wrapper cache?"); - - // We want to set the custom prototype in the caller's comparment. - // In the case that element is in a different compartment, - // this will set the prototype on the element's wrapper and - // thus only visible in the wrapper's compartment. - JS::RootedObject wrapper(aCx); - JS::Rooted<JSObject*> prototype(aCx, aDefinition->mPrototype); - if ((wrapper = cache->GetWrapper()) && JS_WrapObject(aCx, &wrapper)) { - if (!JS_SetPrototype(aCx, wrapper, prototype)) { - continue; - } - } - - nsContentUtils::EnqueueLifecycleCallback( - elem->OwnerDoc(), nsIDocument::eCreated, elem, nullptr, aDefinition); + reactionsStack->EnqueueUpgradeReaction(elem, aDefinition); } } } @@ -516,11 +470,7 @@ static const char* kLifeCycleCallbackNames[] = { "connectedCallback", "disconnectedCallback", "adoptedCallback", - "attributeChangedCallback", - // The life cycle callbacks from v0 spec. - "createdCallback", - "attachedCallback", - "detachedCallback" + "attributeChangedCallback" }; static void @@ -600,7 +550,7 @@ CustomElementRegistry::Define(const nsAString& aName, * 3. If this CustomElementRegistry contains an entry with name name, then * throw a "NotSupportedError" DOMException and abort these steps. */ - if (mCustomDefinitions.Get(nameAtom)) { + if (mCustomDefinitions.GetWeak(nameAtom)) { aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } @@ -609,9 +559,13 @@ CustomElementRegistry::Define(const nsAString& aName, * 4. If this CustomElementRegistry contains an entry with constructor constructor, * then throw a "NotSupportedError" DOMException and abort these steps. */ - // TODO: Step 3 of HTMLConstructor also needs a way to look up definition by - // using constructor. So I plans to figure out a solution to support both of - // them in bug 1274159. + const auto& ptr = mConstructors.lookup(constructorUnwrapped); + if (ptr) { + MOZ_ASSERT(mCustomDefinitions.GetWeak(ptr->value()), + "Definition must be found in mCustomDefinitions"); + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } /** * 5. Let localName be name. @@ -663,6 +617,7 @@ CustomElementRegistry::Define(const nsAString& aName, JS::Rooted<JSObject*> constructorPrototype(cx); nsAutoPtr<LifecycleCallbacks> callbacksHolder(new LifecycleCallbacks()); + nsCOMArray<nsIAtom> observedAttributes; { // Set mIsCustomDefinitionRunning. /** * 9. Set this CustomElementRegistry's element definition is running flag. @@ -724,6 +679,14 @@ CustomElementRegistry::Define(const nsAString& aName, return; } + // Note: We call the init from the constructorProtoUnwrapped's compartment + // here. + JS::RootedValue rootedv(cx, JS::ObjectValue(*constructorProtoUnwrapped)); + if (!JS_WrapValue(cx, &rootedv) || !callbacksHolder->Init(cx, rootedv)) { + aRv.StealExceptionFromJSContext(cx); + return; + } + /** * 10.5. Let observedAttributes be an empty sequence<DOMString>. * 10.6. If the value of the entry in lifecycleCallbacks with key @@ -735,15 +698,54 @@ CustomElementRegistry::Define(const nsAString& aName, * observedAttributesIterable to a sequence<DOMString>. Rethrow * any exceptions from the conversion. */ - // TODO: Bug 1293921 - Implement connected/disconnected/adopted/attributeChanged lifecycle callbacks for custom elements + if (callbacksHolder->mAttributeChangedCallback.WasPassed()) { + // Enter constructor's compartment. + JSAutoCompartment ac(cx, constructor); + JS::Rooted<JS::Value> observedAttributesIterable(cx); + + if (!JS_GetProperty(cx, constructor, "observedAttributes", + &observedAttributesIterable)) { + aRv.StealExceptionFromJSContext(cx); + return; + } - // Note: We call the init from the constructorProtoUnwrapped's compartment - // here. - JS::RootedValue rootedv(cx, JS::ObjectValue(*constructorProtoUnwrapped)); - if (!JS_WrapValue(cx, &rootedv) || !callbacksHolder->Init(cx, rootedv)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } + if (!observedAttributesIterable.isUndefined()) { + if (!observedAttributesIterable.isObject()) { + aRv.ThrowTypeError<MSG_NOT_SEQUENCE>(NS_LITERAL_STRING("observedAttributes")); + return; + } + + JS::ForOfIterator iter(cx); + if (!iter.init(observedAttributesIterable, JS::ForOfIterator::AllowNonIterable)) { + aRv.StealExceptionFromJSContext(cx); + return; + } + + if (!iter.valueIsIterable()) { + aRv.ThrowTypeError<MSG_NOT_SEQUENCE>(NS_LITERAL_STRING("observedAttributes")); + return; + } + + JS::Rooted<JS::Value> attribute(cx); + while (true) { + bool done; + if (!iter.next(&attribute, &done)) { + aRv.StealExceptionFromJSContext(cx); + return; + } + if (done) { + break; + } + + nsAutoString attrStr; + if (!ConvertJSValueToString(cx, attribute, eStringify, eStringify, attrStr)) { + aRv.StealExceptionFromJSContext(cx); + return; + } + observedAttributes.AppendElement(NS_Atomize(attrStr)); + } + } + } // Leave constructor's compartment. } // Leave constructorProtoUnwrapped's compartment. } // Unset mIsCustomDefinitionRunning @@ -756,24 +758,34 @@ CustomElementRegistry::Define(const nsAString& aName, // Associate the definition with the custom element. nsCOMPtr<nsIAtom> localNameAtom(NS_Atomize(localName)); LifecycleCallbacks* callbacks = callbacksHolder.forget(); - CustomElementDefinition* definition = + + /** + * 12. Add definition to this CustomElementRegistry. + */ + if (!mConstructors.put(constructorUnwrapped, nameAtom)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + RefPtr<CustomElementDefinition> definition = new CustomElementDefinition(nameAtom, localNameAtom, - constructor, + &aFunctionConstructor, + Move(observedAttributes), constructorPrototype, callbacks, 0 /* TODO dependent on HTML imports. Bug 877072 */); - /** - * 12. Add definition to this CustomElementRegistry. - */ - mCustomDefinitions.Put(nameAtom, definition); + CustomElementDefinition* def = definition.get(); + mCustomDefinitions.Put(nameAtom, definition.forget()); + + MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(), + "Number of entries should be the same"); /** * 13. 14. 15. Upgrade candidates */ - // TODO: Bug 1299363 - Implement custom element v1 upgrade algorithm - UpgradeCandidates(cx, nameAtom, definition); + UpgradeCandidates(nameAtom, def, aRv); /** * 16. If this CustomElementRegistry's when-defined promise map contains an @@ -796,14 +808,14 @@ CustomElementRegistry::Get(JSContext* aCx, const nsAString& aName, JS::MutableHandle<JS::Value> aRetVal) { nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName)); - CustomElementDefinition* data = mCustomDefinitions.Get(nameAtom); + CustomElementDefinition* data = mCustomDefinitions.GetWeak(nameAtom); if (!data) { aRetVal.setUndefined(); return; } - aRetVal.setObject(*data->mConstructor); + aRetVal.setObjectOrNull(data->mConstructor->Callable()); return; } @@ -823,7 +835,7 @@ CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv) return promise.forget(); } - if (mCustomDefinitions.Get(nameAtom)) { + if (mCustomDefinitions.GetWeak(nameAtom)) { promise->MaybeResolve(JS::UndefinedHandleValue); return promise.forget(); } @@ -837,20 +849,346 @@ CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv) return promise.forget(); } +namespace { + +static void +DoUpgrade(Element* aElement, + CustomElementConstructor* aConstructor, + ErrorResult& aRv) +{ + // Rethrow the exception since it might actually throw the exception from the + // upgrade steps back out to the caller of document.createElement. + RefPtr<Element> constructResult = + aConstructor->Construct("Custom Element Upgrade", aRv); + if (aRv.Failed()) { + return; + } + + if (!constructResult || constructResult.get() != aElement) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } +} + +} // anonymous namespace + +// https://html.spec.whatwg.org/multipage/scripting.html#upgrades +/* static */ void +CustomElementRegistry::Upgrade(Element* aElement, + CustomElementDefinition* aDefinition, + ErrorResult& aRv) +{ + aElement->RemoveStates(NS_EVENT_STATE_UNRESOLVED); + + RefPtr<CustomElementData> data = aElement->GetCustomElementData(); + MOZ_ASSERT(data, "CustomElementData should exist"); + + // Step 1 and step 2. + if (data->mState == CustomElementData::State::eCustom || + data->mState == CustomElementData::State::eFailed) { + return; + } + + // Step 3. + if (!aDefinition->mObservedAttributes.IsEmpty()) { + uint32_t count = aElement->GetAttrCount(); + for (uint32_t i = 0; i < count; i++) { + mozilla::dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(i); + + const nsAttrName* name = info.mName; + nsIAtom* attrName = name->LocalName(); + + if (aDefinition->IsInObservedAttributeList(attrName)) { + int32_t namespaceID = name->NamespaceID(); + nsAutoString attrValue, namespaceURI; + info.mValue->ToString(attrValue); + nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, + namespaceURI); + + LifecycleCallbackArgs args = { + nsDependentAtomString(attrName), + NullString(), + attrValue, + (namespaceURI.IsEmpty() ? NullString() : namespaceURI) + }; + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, + aElement, + &args, nullptr, aDefinition); + } + } + } + + // Step 4. + if (aElement->IsInComposedDoc()) { + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eConnected, aElement, + nullptr, nullptr, aDefinition); + } + + // Step 5. + AutoConstructionStackEntry acs(aDefinition->mConstructionStack, + nsGenericHTMLElement::FromContent(aElement)); + + // Step 6 and step 7. + DoUpgrade(aElement, aDefinition->mConstructor, aRv); + if (aRv.Failed()) { + data->mState = CustomElementData::State::eFailed; + // Empty element's custom element reaction queue. + data->mReactionQueue.Clear(); + return; + } + + // Step 8. + data->mState = CustomElementData::State::eCustom; + + // Step 9. + aElement->SetCustomElementDefinition(aDefinition); +} + +//----------------------------------------------------- +// CustomElementReactionsStack + +void +CustomElementReactionsStack::CreateAndPushElementQueue() +{ + MOZ_ASSERT(mRecursionDepth); + MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth); + + // Push a new element queue onto the custom element reactions stack. + mReactionsStack.AppendElement(MakeUnique<ElementQueue>()); + mIsElementQueuePushedForCurrentRecursionDepth = true; +} + +void +CustomElementReactionsStack::PopAndInvokeElementQueue() +{ + MOZ_ASSERT(mRecursionDepth); + MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth); + MOZ_ASSERT(!mReactionsStack.IsEmpty(), + "Reaction stack shouldn't be empty"); + + // Pop the element queue from the custom element reactions stack, + // and invoke custom element reactions in that queue. + const uint32_t lastIndex = mReactionsStack.Length() - 1; + ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get(); + // Check element queue size in order to reduce function call overhead. + if (!elementQueue->IsEmpty()) { + // It is still not clear what error reporting will look like in custom + // element, see https://github.com/w3c/webcomponents/issues/635. + // We usually report the error to entry global in gecko, so just follow the + // same behavior here. + // This may be null if it's called from parser, see the case of + // attributeChangedCallback in + // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token + // In that case, the exception of callback reactions will be automatically + // reported in CallSetup. + nsIGlobalObject* global = GetEntryGlobal(); + InvokeReactions(elementQueue, global); + } + + // InvokeReactions() might create other custom element reactions, but those + // new reactions should be already consumed and removed at this point. + MOZ_ASSERT(lastIndex == mReactionsStack.Length() - 1, + "reactions created by InvokeReactions() should be consumed and removed"); + + mReactionsStack.RemoveElementAt(lastIndex); + mIsElementQueuePushedForCurrentRecursionDepth = false; +} + +void +CustomElementReactionsStack::EnqueueUpgradeReaction(Element* aElement, + CustomElementDefinition* aDefinition) +{ + Enqueue(aElement, new CustomElementUpgradeReaction(aDefinition)); +} + +void +CustomElementReactionsStack::EnqueueCallbackReaction(Element* aElement, + UniquePtr<CustomElementCallback> aCustomElementCallback) +{ + Enqueue(aElement, new CustomElementCallbackReaction(Move(aCustomElementCallback))); +} + +void +CustomElementReactionsStack::Enqueue(Element* aElement, + CustomElementReaction* aReaction) +{ + RefPtr<CustomElementData> elementData = aElement->GetCustomElementData(); + MOZ_ASSERT(elementData, "CustomElementData should exist"); + + if (mRecursionDepth) { + // If the element queue is not created for current recursion depth, create + // and push an element queue to reactions stack first. + if (!mIsElementQueuePushedForCurrentRecursionDepth) { + CreateAndPushElementQueue(); + } + + MOZ_ASSERT(!mReactionsStack.IsEmpty()); + // Add element to the current element queue. + mReactionsStack.LastElement()->AppendElement(aElement); + elementData->mReactionQueue.AppendElement(aReaction); + return; + } + + // If the custom element reactions stack is empty, then: + // Add element to the backup element queue. + MOZ_ASSERT(mReactionsStack.IsEmpty(), + "custom element reactions stack should be empty"); + MOZ_ASSERT(!aReaction->IsUpgradeReaction(), + "Upgrade reaction should not be scheduled to backup queue"); + mBackupQueue.AppendElement(aElement); + elementData->mReactionQueue.AppendElement(aReaction); + + if (mIsBackupQueueProcessing) { + return; + } + + CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); + RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this); + context->DispatchMicroTaskRunnable(bqmt.forget()); +} + +void +CustomElementReactionsStack::InvokeBackupQueue() +{ + // Check backup queue size in order to reduce function call overhead. + if (!mBackupQueue.IsEmpty()) { + // Upgrade reactions won't be scheduled in backup queue and the exception of + // callback reactions will be automatically reported in CallSetup. + // If the reactions are invoked from backup queue (in microtask check point), + // we don't need to pass global object for error reporting. + InvokeReactions(&mBackupQueue, nullptr); + } + MOZ_ASSERT(mBackupQueue.IsEmpty(), + "There are still some reactions in BackupQueue not being consumed!?!"); +} + +void +CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue, + nsIGlobalObject* aGlobal) +{ + // This is used for error reporting. + Maybe<AutoEntryScript> aes; + if (aGlobal) { + aes.emplace(aGlobal, "custom elements reaction invocation"); + } + + // Note: It's possible to re-enter this method. + for (uint32_t i = 0; i < aElementQueue->Length(); ++i) { + Element* element = aElementQueue->ElementAt(i); + + // ElementQueue hold a element's strong reference, it should not be a nullptr. + MOZ_ASSERT(element); + + RefPtr<CustomElementData> elementData = element->GetCustomElementData(); + if (!elementData) { + // This happens when the document is destroyed and the element is already + // unlinked, no need to fire the callbacks in this case. + continue; + } + + auto& reactions = elementData->mReactionQueue; + for (uint32_t j = 0; j < reactions.Length(); ++j) { + // Transfer the ownership of the entry due to reentrant invocation of + // this funciton. The entry will be removed when bug 1379573 is landed. + auto reaction(Move(reactions.ElementAt(j))); + if (reaction) { + ErrorResult rv; + reaction->Invoke(element, rv); + if (aes) { + JSContext* cx = aes->cx(); + if (rv.MaybeSetPendingException(cx)) { + aes->ReportException(); + } + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + } + MOZ_ASSERT(!rv.Failed()); + } + } + reactions.Clear(); + } + aElementQueue->Clear(); +} + +//----------------------------------------------------- +// CustomElementDefinition + +NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementDefinition) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementDefinition) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mConstructor) + tmp->mPrototype = nullptr; + tmp->mCallbacks = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementDefinition) + mozilla::dom::LifecycleCallbacks* callbacks = tmp->mCallbacks.get(); + + if (callbacks->mAttributeChangedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, + "mCallbacks->mAttributeChangedCallback"); + cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value()); + } + + if (callbacks->mConnectedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mConnectedCallback"); + cb.NoteXPCOMChild(callbacks->mConnectedCallback.Value()); + } + + if (callbacks->mDisconnectedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mDisconnectedCallback"); + cb.NoteXPCOMChild(callbacks->mDisconnectedCallback.Value()); + } + + if (callbacks->mAdoptedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mAdoptedCallback"); + cb.NoteXPCOMChild(callbacks->mAdoptedCallback.Value()); + } + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mConstructor"); + cb.NoteXPCOMChild(tmp->mConstructor); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementDefinition) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPrototype) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CustomElementDefinition, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CustomElementDefinition, Release) + CustomElementDefinition::CustomElementDefinition(nsIAtom* aType, nsIAtom* aLocalName, - JSObject* aConstructor, + Function* aConstructor, + nsCOMArray<nsIAtom>&& aObservedAttributes, JSObject* aPrototype, LifecycleCallbacks* aCallbacks, uint32_t aDocOrder) : mType(aType), mLocalName(aLocalName), - mConstructor(aConstructor), + mConstructor(new CustomElementConstructor(aConstructor)), + mObservedAttributes(Move(aObservedAttributes)), mPrototype(aPrototype), mCallbacks(aCallbacks), mDocOrder(aDocOrder) { } +//----------------------------------------------------- +// CustomElementUpgradeReaction + +/* virtual */ void +CustomElementUpgradeReaction::Invoke(Element* aElement, ErrorResult& aRv) +{ + CustomElementRegistry::Upgrade(aElement, mDefinition, aRv); +} + +//----------------------------------------------------- +// CustomElementCallbackReaction + +/* virtual */ void +CustomElementCallbackReaction::Invoke(Element* aElement, ErrorResult& aRv) +{ + mCustomElementCallback->Call(); +} + } // namespace dom -} // namespace mozilla
\ No newline at end of file +} // namespace mozilla diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h index ff803a054..c416e5043 100644 --- a/dom/base/CustomElementRegistry.h +++ b/dom/base/CustomElementRegistry.h @@ -7,13 +7,16 @@ #ifndef mozilla_dom_CustomElementRegistry_h #define mozilla_dom_CustomElementRegistry_h +#include "js/GCHashTable.h" #include "js/TypeDecls.h" #include "mozilla/Attributes.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/FunctionBinding.h" +#include "mozilla/dom/WebComponentsBinding.h" #include "nsCycleCollectionParticipant.h" +#include "nsGenericHTMLElement.h" #include "nsWrapperCache.h" -#include "mozilla/dom/FunctionBinding.h" class nsDocument; @@ -22,8 +25,8 @@ namespace dom { struct CustomElementData; struct ElementDefinitionOptions; -struct LifecycleCallbacks; class CallbackFunction; +class CustomElementReaction; class Function; class Promise; @@ -32,6 +35,13 @@ struct LifecycleCallbackArgs nsString name; nsString oldValue; nsString newValue; + nsString namespaceURI; +}; + +struct LifecycleAdoptedCallbackArgs +{ + nsCOMPtr<nsIDocument> mOldDocument; + nsCOMPtr<nsIDocument> mNewDocument; }; class CustomElementCallback @@ -39,8 +49,7 @@ class CustomElementCallback public: CustomElementCallback(Element* aThisObject, nsIDocument::ElementCallbackType aCallbackType, - CallbackFunction* aCallback, - CustomElementData* aOwnerData); + CallbackFunction* aCallback); void Traverse(nsCycleCollectionTraversalCallback& aCb) const; void Call(); void SetArgs(LifecycleCallbackArgs& aArgs) @@ -50,6 +59,13 @@ public: mArgs = aArgs; } + void SetAdoptedCallbackArgs(LifecycleAdoptedCallbackArgs& aAdoptedCallbackArgs) + { + MOZ_ASSERT(mType == nsIDocument::eAdopted, + "Arguments are only used by adopted callback."); + mAdoptedCallbackArgs = aAdoptedCallbackArgs; + } + private: // The this value to use for invocation of the callback. RefPtr<Element> mThisObject; @@ -59,9 +75,19 @@ private: // 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; + LifecycleAdoptedCallbackArgs mAdoptedCallbackArgs; +}; + +class CustomElementConstructor final : public CallbackFunction +{ +public: + explicit CustomElementConstructor(CallbackFunction* aOther) + : CallbackFunction(aOther) + { + MOZ_ASSERT(JS::IsConstructor(mCallback)); + } + + already_AddRefed<Element> Construct(const char* aExecutionReason, ErrorResult& aRv); }; // Each custom element has an associated callback queue and an element is @@ -70,63 +96,299 @@ struct CustomElementData { NS_INLINE_DECL_REFCOUNTING(CustomElementData) + // https://dom.spec.whatwg.org/#concept-element-custom-element-state + // CustomElementData is only created on the element which is a custom element + // or an upgrade candidate, so the state of an element without + // CustomElementData is "uncustomized". + enum class State { + eUndefined, + eFailed, + eCustom + }; + 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(); + CustomElementData(nsIAtom* aType, State aState); + + // Custom element state as described in the custom element spec. + State mState; + // custom element reaction queue as described in the custom element spec. + // There is 1 reaction in reaction queue, when 1) it becomes disconnected, + // 2) it’s adopted into a new document, 3) its attributes are changed, + // appended, removed, or replaced. + // There are 3 reactions in reaction queue when doing upgrade operation, + // e.g., create an element, insert a node. + AutoTArray<UniquePtr<CustomElementReaction>, 3> mReactionQueue; + + void SetCustomElementDefinition(CustomElementDefinition* aDefinition); + CustomElementDefinition* GetCustomElementDefinition(); + nsIAtom* GetCustomElementType(); + + void Traverse(nsCycleCollectionTraversalCallback& aCb) const; + void Unlink(); private: virtual ~CustomElementData() {} + + // Custom element type, for <button is="x-button"> or <x-button> + // this would be x-button. + RefPtr<nsIAtom> mType; + RefPtr<CustomElementDefinition> mCustomElementDefinition; }; +#define ALEADY_CONSTRUCTED_MARKER nullptr + // The required information for a custom element as defined in: // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition struct CustomElementDefinition { + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CustomElementDefinition) + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CustomElementDefinition) + CustomElementDefinition(nsIAtom* aType, nsIAtom* aLocalName, - JSObject* aConstructor, + Function* aConstructor, + nsCOMArray<nsIAtom>&& aObservedAttributes, JSObject* aPrototype, mozilla::dom::LifecycleCallbacks* aCallbacks, uint32_t aDocOrder); - // The type (name) for this custom element. + // The type (name) for this custom element, for <button is="x-foo"> or <x-foo> + // this would be x-foo. nsCOMPtr<nsIAtom> mType; // The localname to (e.g. <button is=type> -- this would be button). nsCOMPtr<nsIAtom> mLocalName; // The custom element constructor. - JS::Heap<JSObject *> mConstructor; + RefPtr<CustomElementConstructor> mConstructor; + + // The list of attributes that this custom element observes. + nsCOMArray<nsIAtom> mObservedAttributes; // The prototype to use for new custom elements of this type. JS::Heap<JSObject *> mPrototype; // The lifecycle callbacks to call for this custom element. - nsAutoPtr<mozilla::dom::LifecycleCallbacks> mCallbacks; + UniquePtr<mozilla::dom::LifecycleCallbacks> mCallbacks; - // A construction stack. - // TODO: Bug 1287348 - Implement construction stack for upgrading an element + // A construction stack. Use nullptr to represent an "already constructed marker". + nsTArray<RefPtr<nsGenericHTMLElement>> mConstructionStack; // The document custom element order. uint32_t mDocOrder; + + bool IsCustomBuiltIn() + { + return mType != mLocalName; + } + + bool IsInObservedAttributeList(nsIAtom* aName) + { + if (mObservedAttributes.IsEmpty()) { + return false; + } + + return mObservedAttributes.Contains(aName); + } + +private: + ~CustomElementDefinition() {} +}; + +class CustomElementReaction +{ +public: + virtual ~CustomElementReaction() = default; + virtual void Invoke(Element* aElement, ErrorResult& aRv) = 0; + virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const + { + } + +#if DEBUG + bool IsUpgradeReaction() + { + return mIsUpgradeReaction; + } + +protected: + bool mIsUpgradeReaction = false; +#endif +}; + +class CustomElementUpgradeReaction final : public CustomElementReaction +{ +public: + explicit CustomElementUpgradeReaction(CustomElementDefinition* aDefinition) + : mDefinition(aDefinition) + { +#if DEBUG + mIsUpgradeReaction = true; +#endif + } + +private: + virtual void Invoke(Element* aElement, ErrorResult& aRv) override; + + CustomElementDefinition* mDefinition; +}; + +class CustomElementCallbackReaction final : public CustomElementReaction +{ + public: + explicit CustomElementCallbackReaction(UniquePtr<CustomElementCallback> aCustomElementCallback) + : mCustomElementCallback(Move(aCustomElementCallback)) + { + } + + virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const override + { + mCustomElementCallback->Traverse(aCb); + } + + private: + virtual void Invoke(Element* aElement, ErrorResult& aRv) override; + UniquePtr<CustomElementCallback> mCustomElementCallback; +}; + +// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack +class CustomElementReactionsStack +{ +public: + NS_INLINE_DECL_REFCOUNTING(CustomElementReactionsStack) + + CustomElementReactionsStack() + : mIsBackupQueueProcessing(false) + , mRecursionDepth(0) + , mIsElementQueuePushedForCurrentRecursionDepth(false) + { + } + + // Hold a strong reference of Element so that it does not get cycle collected + // before the reactions in its reaction queue are invoked. + // The element reaction queues are stored in CustomElementData. + // We need to lookup ElementReactionQueueMap again to get relevant reaction queue. + // The choice of 1 for the auto size here is based on gut feeling. + typedef AutoTArray<RefPtr<Element>, 1> ElementQueue; + + /** + * Enqueue a custom element upgrade reaction + * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction + */ + void EnqueueUpgradeReaction(Element* aElement, + CustomElementDefinition* aDefinition); + + /** + * Enqueue a custom element callback reaction + * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-callback-reaction + */ + void EnqueueCallbackReaction(Element* aElement, + UniquePtr<CustomElementCallback> aCustomElementCallback); + + /** + * [CEReactions] Before executing the algorithm's steps. + * Increase the current recursion depth, and the element queue is pushed + * lazily when we really enqueue reactions. + * + * @return true if the element queue is pushed for "previous" recursion depth. + */ + bool EnterCEReactions() + { + bool temp = mIsElementQueuePushedForCurrentRecursionDepth; + mRecursionDepth++; + // The is-element-queue-pushed flag is initially false when entering a new + // recursion level. The original value will be cached in AutoCEReaction + // and restored after leaving this recursion level. + mIsElementQueuePushedForCurrentRecursionDepth = false; + return temp; + } + + /** + * [CEReactions] After executing the algorithm's steps. + * Pop and invoke the element queue if it is created and pushed for current + * recursion depth, then decrease the current recursion depth. + * + * @param aCx JSContext used for handling exception thrown by algorithm's + * steps, this could be a nullptr. + * aWasElementQueuePushed used for restoring status after leaving + * current recursion. + */ + void LeaveCEReactions(JSContext* aCx, bool aWasElementQueuePushed) + { + MOZ_ASSERT(mRecursionDepth); + + if (mIsElementQueuePushedForCurrentRecursionDepth) { + Maybe<JS::AutoSaveExceptionState> ases; + if (aCx) { + ases.emplace(aCx); + } + PopAndInvokeElementQueue(); + } + mRecursionDepth--; + // Restore the is-element-queue-pushed flag cached in AutoCEReaction when + // leaving the recursion level. + mIsElementQueuePushedForCurrentRecursionDepth = aWasElementQueuePushed; + + MOZ_ASSERT_IF(!mRecursionDepth, mReactionsStack.IsEmpty()); + } + +private: + ~CustomElementReactionsStack() {}; + + /** + * Push a new element queue onto the custom element reactions stack. + */ + void CreateAndPushElementQueue(); + + /** + * Pop the element queue from the custom element reactions stack, and invoke + * custom element reactions in that queue. + */ + void PopAndInvokeElementQueue(); + + // The choice of 8 for the auto size here is based on gut feeling. + AutoTArray<UniquePtr<ElementQueue>, 8> mReactionsStack; + ElementQueue mBackupQueue; + // https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue + bool mIsBackupQueueProcessing; + + void InvokeBackupQueue(); + + /** + * Invoke custom element reactions + * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions + */ + void InvokeReactions(ElementQueue* aElementQueue, nsIGlobalObject* aGlobal); + + void Enqueue(Element* aElement, CustomElementReaction* aReaction); + + // Current [CEReactions] recursion depth. + uint32_t mRecursionDepth; + // True if the element queue is pushed into reaction stack for current + // recursion depth. This will be cached in AutoCEReaction when entering a new + // CEReaction recursion and restored after leaving the recursion. + bool mIsElementQueuePushedForCurrentRecursionDepth; + +private: + class BackupQueueMicroTask final : public mozilla::MicroTaskRunnable { + public: + explicit BackupQueueMicroTask(CustomElementReactionsStack* aReactionStack) + : MicroTaskRunnable() + , mReactionStack(aReactionStack) + { + MOZ_ASSERT(!mReactionStack->mIsBackupQueueProcessing, + "mIsBackupQueueProcessing should be initially false"); + mReactionStack->mIsBackupQueueProcessing = true; + } + + virtual void Run(AutoSlowOperation& aAso) override + { + mReactionStack->InvokeBackupQueue(); + mReactionStack->mIsBackupQueueProcessing = false; + } + + private: + RefPtr<CustomElementReactionsStack> mReactionStack; + }; }; class CustomElementRegistry final : public nsISupports, @@ -142,36 +404,33 @@ public: public: static bool IsCustomElementEnabled(JSContext* aCx = nullptr, JSObject* aObject = nullptr); - static already_AddRefed<CustomElementRegistry> Create(nsPIDOMWindowInner* aWindow); - static void ProcessTopElementQueue(); - static void XPCOMShutdown(); + explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow); /** * 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; + nsIAtom* aNameAtom, nsIAtom* aTypeAtom) 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); + CustomElementDefinition* LookupCustomElementDefinition( + JSContext* aCx, JSObject *aConstructor) const; - void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, - Element* aCustomElement, - LifecycleCallbackArgs* aArgs, - CustomElementDefinition* aDefinition); + static void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, + Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + CustomElementDefinition* aDefinition); void GetCustomPrototype(nsIAtom* aAtom, JS::MutableHandle<JSObject*> aPrototype); -private: - explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow); - ~CustomElementRegistry(); + /** + * Upgrade an element. + * https://html.spec.whatwg.org/multipage/scripting.html#upgrades + */ + static void Upgrade(Element* aElement, CustomElementDefinition* aDefinition, ErrorResult& aRv); /** * Registers an unresolved custom element that is a candidate for @@ -184,23 +443,48 @@ private: void RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName = nullptr); - void UpgradeCandidates(JSContext* aCx, - nsIAtom* aKey, - CustomElementDefinition* aDefinition); + /** + * Unregister an unresolved custom element that is a candidate for + * upgrade when a custom element is removed from tree. + */ + void UnregisterUnresolvedElement(Element* aElement, + nsIAtom* aTypeName = nullptr); +private: + ~CustomElementRegistry(); + + static UniquePtr<CustomElementCallback> CreateCustomElementCallback( + nsIDocument::ElementCallbackType aType, Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + CustomElementDefinition* aDefinition); - typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition> + void UpgradeCandidates(nsIAtom* aKey, + CustomElementDefinition* aDefinition, + ErrorResult& aRv); + + typedef nsRefPtrHashtable<nsISupportsHashKey, CustomElementDefinition> DefinitionMap; typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>> CandidateMap; + typedef JS::GCHashMap<JS::Heap<JSObject*>, + nsCOMPtr<nsIAtom>, + js::MovableCellHasher<JS::Heap<JSObject*>>, + js::SystemAllocPolicy> ConstructorMap; // Hashtable for custom element definitions in web components. // Custom prototypes are stored in the compartment where // registerElement was called. DefinitionMap mCustomDefinitions; + // Hashtable for looking up definitions by using constructor as key. + // Custom elements' name are stored here and we need to lookup + // mCustomDefinitions again to get definitions. + ConstructorMap mConstructors; + typedef nsRefPtrHashtable<nsISupportsHashKey, Promise> WhenDefinedPromiseMap; WhenDefinedPromiseMap mWhenDefinedPromiseMap; + // 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. @@ -208,14 +492,6 @@ private: 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; - // It is used to prevent reentrant invocations of element definition. bool mIsCustomDefinitionRunning; @@ -252,6 +528,31 @@ public: already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv); }; +class MOZ_RAII AutoCEReaction final { + public: + // JSContext is allowed to be a nullptr if we are guaranteeing that we're + // not doing something that might throw but not finish reporting a JS + // exception during the lifetime of the AutoCEReaction. + AutoCEReaction(CustomElementReactionsStack* aReactionsStack, JSContext* aCx) + : mReactionsStack(aReactionsStack) + , mCx(aCx) + { + mIsElementQueuePushedForPreviousRecursionDepth = + mReactionsStack->EnterCEReactions(); + } + + ~AutoCEReaction() + { + mReactionsStack->LeaveCEReactions( + mCx, mIsElementQueuePushedForPreviousRecursionDepth); + } + + private: + RefPtr<CustomElementReactionsStack> mReactionsStack; + JSContext* mCx; + bool mIsElementQueuePushedForPreviousRecursionDepth; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/base/DocGroup.cpp b/dom/base/DocGroup.cpp index 226879985..30c058f0c 100644 --- a/dom/base/DocGroup.cpp +++ b/dom/base/DocGroup.cpp @@ -46,6 +46,9 @@ DocGroup::DocGroup(TabGroup* aTabGroup, const nsACString& aKey) DocGroup::~DocGroup() { MOZ_ASSERT(mDocuments.IsEmpty()); + if (!NS_IsMainThread()) { + NS_ReleaseOnMainThread(mReactionsStack.forget()); + } mTabGroup->mDocGroups.RemoveEntry(mKey); } diff --git a/dom/base/DocGroup.h b/dom/base/DocGroup.h index f4f7ac8ad..5b8f627cc 100644 --- a/dom/base/DocGroup.h +++ b/dom/base/DocGroup.h @@ -14,6 +14,7 @@ #include "nsString.h" #include "mozilla/RefPtr.h" +#include "mozilla/dom/CustomElementRegistry.h" namespace mozilla { namespace dom { @@ -52,6 +53,14 @@ public: { return mTabGroup; } + mozilla::dom::CustomElementReactionsStack* CustomElementReactionsStack() + { + if (!mReactionsStack) { + mReactionsStack = new mozilla::dom::CustomElementReactionsStack(); + } + + return mReactionsStack; + } void RemoveDocument(nsIDocument* aWindow); // Iterators for iterating over every document within the DocGroup @@ -71,6 +80,7 @@ private: nsCString mKey; RefPtr<TabGroup> mTabGroup; nsTArray<nsIDocument*> mDocuments; + RefPtr<mozilla::dom::CustomElementReactionsStack> mReactionsStack; }; } // namespace dom diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 0054f4800..c8467e036 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -479,9 +479,13 @@ Element::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) if (data) { // If this is a registered custom element then fix the prototype. nsContentUtils::GetCustomPrototype(OwnerDoc(), NodeInfo()->NamespaceID(), - data->mType, &customProto); + data->GetCustomElementType(), &customProto); if (customProto && NodePrincipal()->SubsumesConsideringDomain(nsContentUtils::ObjectPrincipal(customProto))) { + // The custom element prototype could be in different compartment. + if (!JS_WrapObject(aCx, &customProto)) { + return nullptr; + } // Just go ahead and create with the right proto up front. Set // customProto to null to flag that we don't need to do any post-facto // proto fixups here. @@ -1595,7 +1599,7 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, #endif { if (aBindingParent) { - nsDOMSlots *slots = DOMSlots(); + nsExtendedDOMSlots* slots = ExtendedDOMSlots(); slots->mBindingParent = aBindingParent; // Weak, so no addref happens. } @@ -1618,7 +1622,7 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, } ShadowRoot* parentContainingShadow = aParent->GetContainingShadow(); if (parentContainingShadow) { - DOMSlots()->mContainingShadow = parentContainingShadow; + ExtendedDOMSlots()->mContainingShadow = parentContainingShadow; } } @@ -1684,14 +1688,17 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, SetSubtreeRootPointer(aParent->SubtreeRoot()); } - nsIDocument* composedDoc = GetComposedDoc(); - if (composedDoc) { - // Attached callback must be enqueued whenever custom element is inserted into a - // document and this document has a browsing context. - if (GetCustomElementData() && composedDoc->GetDocShell()) { - // Enqueue an attached callback for the custom element. - nsContentUtils::EnqueueLifecycleCallback( - composedDoc, nsIDocument::eAttached, this); + if (CustomElementRegistry::IsCustomElementEnabled() && IsInComposedDoc()) { + // Connected callback must be enqueued whenever a custom element becomes + // connected. + CustomElementData* data = GetCustomElementData(); + if (data) { + if (data->mState == CustomElementData::State::eCustom) { + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eConnected, this); + } else { + // Step 7.7.2.2 https://dom.spec.whatwg.org/#concept-node-insert + nsContentUtils::TryToUpgradeElement(this); + } } } @@ -1986,12 +1993,21 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent) document->ClearBoxObjectFor(this); - // Detached must be enqueued whenever custom element is removed from - // the document and this document has a browsing context. - if (GetCustomElementData() && document->GetDocShell()) { - // Enqueue a detached callback for the custom element. - nsContentUtils::EnqueueLifecycleCallback( - document, nsIDocument::eDetached, this); + // Disconnected must be enqueued whenever a connected custom element becomes + // disconnected. + if (CustomElementRegistry::IsCustomElementEnabled()) { + CustomElementData* data = GetCustomElementData(); + if (data) { + if (data->mState == CustomElementData::State::eCustom) { + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eDisconnected, + this); + } else { + // Remove an unresolved custom element that is a candidate for + // upgrade when a custom element is disconnected. + // We will make sure it's shadow-including tree order in bug 1326028. + nsContentUtils::UnregisterUnresolvedElement(this); + } + } } } @@ -2007,7 +2023,7 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent) } #endif - nsDOMSlots* slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots) { if (clearBindingParent) { slots->mBindingParent = nullptr; @@ -2055,7 +2071,7 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent) nsICSSDeclaration* Element::GetSMILOverrideStyle() { - Element::nsDOMSlots *slots = DOMSlots(); + Element::nsExtendedDOMSlots* slots = ExtendedDOMSlots(); if (!slots->mSMILOverrideStyle) { slots->mSMILOverrideStyle = new nsDOMCSSAttributeDeclaration(this, true); @@ -2067,7 +2083,7 @@ Element::GetSMILOverrideStyle() DeclarationBlock* Element::GetSMILOverrideStyleDeclaration() { - Element::nsDOMSlots *slots = GetExistingDOMSlots(); + Element::nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); return slots ? slots->mSMILOverrideStyleDeclaration.get() : nullptr; } @@ -2075,7 +2091,7 @@ nsresult Element::SetSMILOverrideStyleDeclaration(DeclarationBlock* aDeclaration, bool aNotify) { - Element::nsDOMSlots *slots = DOMSlots(); + Element::nsExtendedDOMSlots* slots = ExtendedDOMSlots(); slots->mSMILOverrideStyleDeclaration = aDeclaration; @@ -2586,19 +2602,32 @@ Element::SetAttrAndNotify(int32_t aNamespaceID, UpdateState(aNotify); - nsIDocument* ownerDoc = OwnerDoc(); - if (ownerDoc && GetCustomElementData()) { - nsCOMPtr<nsIAtom> oldValueAtom = oldValue->GetAsAtom(); - nsCOMPtr<nsIAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom(); - LifecycleCallbackArgs args = { - nsDependentAtomString(aName), - aModType == nsIDOMMutationEvent::ADDITION ? - NullString() : nsDependentAtomString(oldValueAtom), - nsDependentAtomString(newValueAtom) - }; - - nsContentUtils::EnqueueLifecycleCallback( - ownerDoc, nsIDocument::eAttributeChanged, this, &args); + if (CustomElementRegistry::IsCustomElementEnabled()) { + if (CustomElementData* data = GetCustomElementData()) { + if (CustomElementDefinition* definition = + nsContentUtils::GetElementDefinitionIfObservingAttr(this, + data->GetCustomElementType(), + aName)) { + MOZ_ASSERT(data->mState == CustomElementData::State::eCustom, + "AttributeChanged callback should fire only if " + "custom element state is custom"); + nsCOMPtr<nsIAtom> oldValueAtom = oldValue->GetAsAtom(); + nsCOMPtr<nsIAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom(); + nsAutoString ns; + nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns); + + LifecycleCallbackArgs args = { + nsDependentAtomString(aName), + aModType == nsIDOMMutationEvent::ADDITION ? + NullString() : nsDependentAtomString(oldValueAtom), + nsDependentAtomString(newValueAtom), + (ns.IsEmpty() ? NullString() : ns) + }; + + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, + this, &args, nullptr, definition); + } + } } if (aCallAfterSetAttr) { @@ -2843,17 +2872,30 @@ Element::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName, UpdateState(aNotify); - nsIDocument* ownerDoc = OwnerDoc(); - if (ownerDoc && GetCustomElementData()) { - nsCOMPtr<nsIAtom> oldValueAtom = oldValue.GetAsAtom(); - LifecycleCallbackArgs args = { - nsDependentAtomString(aName), - nsDependentAtomString(oldValueAtom), - NullString() - }; - - nsContentUtils::EnqueueLifecycleCallback( - ownerDoc, nsIDocument::eAttributeChanged, this, &args); + if (CustomElementRegistry::IsCustomElementEnabled()) { + if (CustomElementData* data = GetCustomElementData()) { + if (CustomElementDefinition* definition = + nsContentUtils::GetElementDefinitionIfObservingAttr(this, + data->GetCustomElementType(), + aName)) { + MOZ_ASSERT(data->mState == CustomElementData::State::eCustom, + "AttributeChanged callback should fire only if " + "custom element state is custom"); + nsAutoString ns; + nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, ns); + + nsCOMPtr<nsIAtom> oldValueAtom = oldValue.GetAsAtom(); + LifecycleCallbackArgs args = { + nsDependentAtomString(aName), + nsDependentAtomString(oldValueAtom), + NullString(), + (ns.IsEmpty() ? NullString() : ns) + }; + + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, + this, &args, nullptr, definition); + } + } } if (aNotify) { @@ -3988,7 +4030,7 @@ Element::ClearDataset() nsDataHashtable<nsRefPtrHashKey<DOMIntersectionObserver>, int32_t>* Element::RegisteredIntersectionObservers() { - nsDOMSlots* slots = DOMSlots(); + nsExtendedDOMSlots* slots = ExtendedDOMSlots(); return &slots->mRegisteredIntersectionObservers; } @@ -4037,3 +4079,31 @@ Element::UpdateIntersectionObservation(DOMIntersectionObserver* aObserver, int32 } return false; } + +void +Element::SetCustomElementData(CustomElementData* aData) +{ + nsExtendedDOMSlots *slots = ExtendedDOMSlots(); + MOZ_ASSERT(!slots->mCustomElementData, "Custom element data may not be changed once set."); + slots->mCustomElementData = aData; +} + +CustomElementDefinition* +Element::GetCustomElementDefinition() const +{ + CustomElementData* data = GetCustomElementData(); + if (!data) { + return nullptr; + } + + return data->GetCustomElementDefinition(); +} + +void +Element::SetCustomElementDefinition(CustomElementDefinition* aDefinition) +{ + CustomElementData* data = GetCustomElementData(); + MOZ_ASSERT(data); + + data->SetCustomElementDefinition(aDefinition); +} diff --git a/dom/base/Element.h b/dom/base/Element.h index ce84b74fb..782004703 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -390,6 +390,45 @@ public: Directionality GetComputedDirectionality() const; + /** + * Gets the custom element data used by web components custom element. + * Custom element data is created at the first attempt to enqueue a callback. + * + * @return The custom element data or null if none. + */ + inline CustomElementData* GetCustomElementData() const + { + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); + if (slots) { + return slots->mCustomElementData; + } + return nullptr; + } + + /** + * Sets the custom element data, ownership of the + * callback data is taken by this element. + * + * @param aData The custom element data. + */ + void SetCustomElementData(CustomElementData* aData); + + /** + * Gets the custom element definition used by web components custom element. + * + * @return The custom element definition or null if element is not a custom + * element or custom element is not defined yet. + */ + CustomElementDefinition* GetCustomElementDefinition() const; + + /** + * Sets the custom element definition, called when custom element is created + * or upgraded. + * + * @param aDefinition The custom element definition. + */ + void SetCustomElementDefinition(CustomElementDefinition* aDefinition); + protected: /** * Method to get the _intrinsic_ content state of this element. This is the @@ -814,7 +853,7 @@ public: ShadowRoot *FastGetShadowRoot() const { - nsDOMSlots* slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); return slots ? slots->mShadowRoot.get() : nullptr; } diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index 9106778df..526c3c9d4 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -530,8 +530,7 @@ nsNodeSupportsWeakRefTearoff::GetWeakReference(nsIWeakReference** aInstancePtr) //---------------------------------------------------------------------- FragmentOrElement::nsDOMSlots::nsDOMSlots() : nsINode::nsSlots(), - mDataset(nullptr), - mBindingParent(nullptr) + mDataset(nullptr) { } @@ -543,84 +542,104 @@ FragmentOrElement::nsDOMSlots::~nsDOMSlots() } void -FragmentOrElement::nsDOMSlots::Traverse(nsCycleCollectionTraversalCallback &cb, bool aIsXUL) +FragmentOrElement::nsDOMSlots::Traverse(nsCycleCollectionTraversalCallback &cb) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mStyle"); cb.NoteXPCOMChild(mStyle.get()); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mSMILOverrideStyle"); - cb.NoteXPCOMChild(mSMILOverrideStyle.get()); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mAttributeMap"); cb.NoteXPCOMChild(mAttributeMap.get()); - if (aIsXUL) { - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mControllers"); - cb.NoteXPCOMChild(mControllers); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildrenList"); + cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mChildrenList)); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mClassList"); + cb.NoteXPCOMChild(mClassList.get()); + + if (!mExtendedSlots) { + return; } - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLBinding"); - cb.NoteNativeChild(mXBLBinding, NS_CYCLE_COLLECTION_PARTICIPANT(nsXBLBinding)); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExtendedSlots->mSMILOverrideStyle"); + cb.NoteXPCOMChild(mExtendedSlots->mSMILOverrideStyle.get()); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLInsertionParent"); - cb.NoteXPCOMChild(mXBLInsertionParent.get()); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExtendedSlots->mControllers"); + cb.NoteXPCOMChild(mExtendedSlots->mControllers); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mShadowRoot"); - cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mShadowRoot)); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExtendedSlots->mLabelsList"); + cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*,mExtendedSlots-> mLabelsList)); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mContainingShadow"); - cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow)); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExtendedSlots->mShadowRoot"); + cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mExtendedSlots->mShadowRoot)); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildrenList"); - cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mChildrenList)); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExtendedSlots->mContainingShadow"); + cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mExtendedSlots->mContainingShadow)); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mLabelsList"); - cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mLabelsList)); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExtendedSlots->mXBLBinding"); + cb.NoteNativeChild(mExtendedSlots->mXBLBinding, + NS_CYCLE_COLLECTION_PARTICIPANT(nsXBLBinding)); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mClassList"); - cb.NoteXPCOMChild(mClassList.get()); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExtendedSlots->mXBLInsertionParent"); + cb.NoteXPCOMChild(mExtendedSlots->mXBLInsertionParent.get()); - if (mCustomElementData) { - for (uint32_t i = 0; i < mCustomElementData->mCallbackQueue.Length(); i++) { - mCustomElementData->mCallbackQueue[i]->Traverse(cb); - } + if (mExtendedSlots->mCustomElementData) { + mExtendedSlots->mCustomElementData->Traverse(cb); } - for (auto iter = mRegisteredIntersectionObservers.Iter(); !iter.Done(); iter.Next()) { + for (auto iter = mExtendedSlots->mRegisteredIntersectionObservers.Iter(); + !iter.Done(); iter.Next()) { DOMIntersectionObserver* observer = iter.Key(); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mRegisteredIntersectionObservers[i]"); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, + "mExtendedSlots->mRegisteredIntersectionObservers[i]"); cb.NoteXPCOMChild(observer); } + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExtendedSlots->mFrameLoaderOrOpener"); + cb.NoteXPCOMChild(mExtendedSlots->mFrameLoaderOrOpener); } void -FragmentOrElement::nsDOMSlots::Unlink(bool aIsXUL) +FragmentOrElement::nsDOMSlots::Unlink() { mStyle = nullptr; - mSMILOverrideStyle = nullptr; if (mAttributeMap) { mAttributeMap->DropReference(); mAttributeMap = nullptr; } - if (aIsXUL) - NS_IF_RELEASE(mControllers); - - MOZ_ASSERT(!mXBLBinding); - - mXBLInsertionParent = nullptr; - mShadowRoot = nullptr; - mContainingShadow = nullptr; mChildrenList = nullptr; - mLabelsList = nullptr; - mCustomElementData = nullptr; mClassList = nullptr; - mRegisteredIntersectionObservers.Clear(); + + if (!mExtendedSlots) { + return; + } + + mExtendedSlots->mSMILOverrideStyle = nullptr; + mExtendedSlots->mControllers = nullptr; + mExtendedSlots->mLabelsList = nullptr; + mExtendedSlots->mShadowRoot = nullptr; + mExtendedSlots->mContainingShadow = nullptr; + MOZ_ASSERT(!(mExtendedSlots->mXBLBinding)); + mExtendedSlots->mXBLInsertionParent = nullptr; + if (mExtendedSlots->mCustomElementData) { + mExtendedSlots->mCustomElementData->Unlink(); + mExtendedSlots->mCustomElementData = nullptr; + } + mExtendedSlots->mRegisteredIntersectionObservers.Clear(); + nsCOMPtr<nsIFrameLoader> frameLoader = + do_QueryInterface(mExtendedSlots->mFrameLoaderOrOpener); + if (frameLoader) { + static_cast<nsFrameLoader*>(frameLoader.get())->Destroy(); + } + mExtendedSlots->mFrameLoaderOrOpener = nullptr; } size_t FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { size_t n = aMallocSizeOf(this); + if (mExtendedSlots) { + n += aMallocSizeOf(mExtendedSlots.get()); + } if (mAttributeMap) { n += mAttributeMap->SizeOfIncludingThis(aMallocSizeOf); @@ -641,6 +660,19 @@ FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) c return n; } +FragmentOrElement::nsExtendedDOMSlots::nsExtendedDOMSlots() + : mBindingParent(nullptr) +{ +} + +FragmentOrElement::nsExtendedDOMSlots::~nsExtendedDOMSlots() +{ + nsCOMPtr<nsIFrameLoader> frameLoader = do_QueryInterface(mFrameLoaderOrOpener); + if (frameLoader) { + static_cast<nsFrameLoader*>(frameLoader.get())->Destroy(); + } +} + FragmentOrElement::FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) : nsIContent(aNodeInfo) { @@ -962,7 +994,7 @@ FragmentOrElement::IsLink(nsIURI** aURI) const nsIContent* FragmentOrElement::GetBindingParent() const { - nsDOMSlots *slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots) { return slots->mBindingParent; @@ -974,7 +1006,7 @@ nsXBLBinding* FragmentOrElement::GetXBLBinding() const { if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { - nsDOMSlots *slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots) { return slots->mXBLBinding; } @@ -1009,11 +1041,11 @@ FragmentOrElement::SetXBLBinding(nsXBLBinding* aBinding, if (aBinding) { SetFlags(NODE_MAY_BE_IN_BINDING_MNGR); - nsDOMSlots *slots = DOMSlots(); + nsExtendedDOMSlots* slots = ExtendedDOMSlots(); slots->mXBLBinding = aBinding; bindingManager->AddBoundContent(this); } else { - nsDOMSlots *slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots) { slots->mXBLBinding = nullptr; } @@ -1028,7 +1060,7 @@ nsIContent* FragmentOrElement::GetXBLInsertionParent() const { if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { - nsDOMSlots *slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots) { return slots->mXBLInsertionParent; } @@ -1040,7 +1072,7 @@ FragmentOrElement::GetXBLInsertionParent() const ShadowRoot* FragmentOrElement::GetContainingShadow() const { - nsDOMSlots *slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots) { return slots->mContainingShadow; } @@ -1050,21 +1082,21 @@ FragmentOrElement::GetContainingShadow() const void FragmentOrElement::SetShadowRoot(ShadowRoot* aShadowRoot) { - nsDOMSlots *slots = DOMSlots(); + nsExtendedDOMSlots* slots = ExtendedDOMSlots(); slots->mShadowRoot = aShadowRoot; } nsTArray<nsIContent*>& FragmentOrElement::DestInsertionPoints() { - nsDOMSlots *slots = DOMSlots(); + nsExtendedDOMSlots* slots = ExtendedDOMSlots(); return slots->mDestInsertionPoints; } nsTArray<nsIContent*>* FragmentOrElement::GetExistingDestInsertionPoints() const { - nsDOMSlots *slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots) { return &slots->mDestInsertionPoints; } @@ -1075,35 +1107,17 @@ void FragmentOrElement::SetXBLInsertionParent(nsIContent* aContent) { if (aContent) { - nsDOMSlots *slots = DOMSlots(); + nsExtendedDOMSlots* slots = ExtendedDOMSlots(); SetFlags(NODE_MAY_BE_IN_BINDING_MNGR); slots->mXBLInsertionParent = aContent; } else { - nsDOMSlots *slots = GetExistingDOMSlots(); + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots) { slots->mXBLInsertionParent = nullptr; } } } -CustomElementData* -FragmentOrElement::GetCustomElementData() const -{ - nsDOMSlots *slots = GetExistingDOMSlots(); - if (slots) { - return slots->mCustomElementData; - } - return nullptr; -} - -void -FragmentOrElement::SetCustomElementData(CustomElementData* aData) -{ - nsDOMSlots *slots = DOMSlots(); - MOZ_ASSERT(!slots->mCustomElementData, "Custom element data may not be changed once set."); - slots->mCustomElementData = aData; -} - nsresult FragmentOrElement::InsertChildAt(nsIContent* aKid, uint32_t aIndex, @@ -1366,14 +1380,15 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement) { nsDOMSlots *slots = tmp->GetExistingDOMSlots(); if (slots) { - if (tmp->IsElement()) { + if (slots->mExtendedSlots && tmp->IsElement()) { Element* elem = tmp->AsElement(); - for (auto iter = slots->mRegisteredIntersectionObservers.Iter(); !iter.Done(); iter.Next()) { + for (auto iter = slots->mExtendedSlots->mRegisteredIntersectionObservers.Iter(); + !iter.Done(); iter.Next()) { DOMIntersectionObserver* observer = iter.Key(); observer->UnlinkTarget(*elem); } } - slots->Unlink(tmp->IsXULElement()); + slots->Unlink(); } } @@ -1938,7 +1953,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement) { nsDOMSlots *slots = tmp->GetExistingDOMSlots(); if (slots) { - slots->Traverse(cb, tmp->IsXULElement()); + slots->Traverse(cb); } } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END diff --git a/dom/base/FragmentOrElement.h b/dom/base/FragmentOrElement.h index f0cc29f22..4edd88908 100644 --- a/dom/base/FragmentOrElement.h +++ b/dom/base/FragmentOrElement.h @@ -15,6 +15,7 @@ #include "mozilla/Attributes.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" #include "nsAttrAndChildArray.h" // member #include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_* #include "nsIContent.h" // base class @@ -37,6 +38,7 @@ class nsIURI; namespace mozilla { class DeclarationBlock; namespace dom { +struct CustomElementData; class DOMIntersectionObserver; class Element; } // namespace dom @@ -159,9 +161,6 @@ public: virtual void SetXBLInsertionParent(nsIContent* aContent) override; virtual bool IsLink(nsIURI** aURI) const override; - virtual CustomElementData *GetCustomElementData() const override; - virtual void SetCustomElementData(CustomElementData* aData) override; - virtual void DestroyContent() override; virtual void SaveSubtreeState() override; @@ -241,8 +240,6 @@ protected: nsresult CopyInnerTo(FragmentOrElement* aDest); public: - // Because of a bug in MS C++ compiler nsDOMSlots must be declared public, - // otherwise nsXULElement::nsXULSlots doesn't compile. /** * There are a set of DOM- and scripting-specific instance variables * that may only be instantiated when a content object is accessed @@ -251,29 +248,13 @@ public: * in a side structure that's only allocated when the content is * accessed through the DOM. */ - class nsDOMSlots : public nsINode::nsSlots + + class nsExtendedDOMSlots { public: - nsDOMSlots(); - virtual ~nsDOMSlots(); - - void Traverse(nsCycleCollectionTraversalCallback &cb, bool aIsXUL); - void Unlink(bool aIsXUL); - - size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - - /** - * The .style attribute (an interface that forwards to the actual - * style rules) - * @see nsGenericHTMLElement::GetStyle - */ - nsCOMPtr<nsICSSDeclaration> mStyle; + nsExtendedDOMSlots(); - /** - * The .dataset attribute. - * @see nsGenericHTMLElement::GetDataset - */ - nsDOMStringMap* mDataset; // [Weak] + ~nsExtendedDOMSlots(); /** * SMIL Overridde style rules (for SMIL animation of CSS properties) @@ -287,35 +268,17 @@ public: RefPtr<mozilla::DeclarationBlock> mSMILOverrideStyleDeclaration; /** - * An object implementing nsIDOMMozNamedAttrMap for this content (attributes) - * @see FragmentOrElement::GetAttributes - */ - RefPtr<nsDOMAttributeMap> mAttributeMap; - - union { - /** - * The nearest enclosing content node with a binding that created us. - * @see FragmentOrElement::GetBindingParent - */ - nsIContent* mBindingParent; // [Weak] - - /** - * The controllers of the XUL Element. - */ - nsIControllers* mControllers; // [OWNER] - }; + * The nearest enclosing content node with a binding that created us. + * @see FragmentOrElement::GetBindingParent + */ + nsIContent* mBindingParent; // [Weak] /** - * An object implementing the .children property for this element. - */ - RefPtr<nsContentList> mChildrenList; + * The controllers of the XUL Element. + */ + nsCOMPtr<nsIControllers> mControllers; /** - * An object implementing the .classList property for this element. - */ - RefPtr<nsDOMTokenList> mClassList; - - /* * An object implementing the .labels property for this element. */ RefPtr<nsLabelsNodeList> mLabelsList; @@ -356,6 +319,55 @@ public: */ nsDataHashtable<nsRefPtrHashKey<DOMIntersectionObserver>, int32_t> mRegisteredIntersectionObservers; + + /** + * For XUL to hold either frameloader or opener. + */ + nsCOMPtr<nsISupports> mFrameLoaderOrOpener; + + }; + + class nsDOMSlots : public nsINode::nsSlots + { + public: + nsDOMSlots(); + virtual ~nsDOMSlots(); + + void Traverse(nsCycleCollectionTraversalCallback &cb); + void Unlink(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + /** + * The .style attribute (an interface that forwards to the actual + * style rules) + * @see nsGenericHTMLElement::GetStyle + */ + nsCOMPtr<nsICSSDeclaration> mStyle; + + /** + * The .dataset attribute. + * @see nsGenericHTMLElement::GetDataset + */ + nsDOMStringMap* mDataset; // [Weak] + + /** + * An object implementing nsIDOMMozNamedAttrMap for this content (attributes) + * @see FragmentOrElement::GetAttributes + */ + RefPtr<nsDOMAttributeMap> mAttributeMap; + + /** + * An object implementing the .children property for this element. + */ + RefPtr<nsContentList> mChildrenList; + + /** + * An object implementing the .classList property for this element. + */ + RefPtr<nsDOMTokenList> mClassList; + + mozilla::UniquePtr<nsExtendedDOMSlots> mExtendedSlots; }; protected: @@ -375,6 +387,26 @@ protected: return static_cast<nsDOMSlots*>(GetExistingSlots()); } + nsExtendedDOMSlots* ExtendedDOMSlots() + { + nsDOMSlots* slots = DOMSlots(); + if (!slots->mExtendedSlots) { + slots->mExtendedSlots = MakeUnique<nsExtendedDOMSlots>(); + } + + return slots->mExtendedSlots.get(); + } + + nsExtendedDOMSlots* GetExistingExtendedDOMSlots() const + { + nsDOMSlots* slots = GetExistingDOMSlots(); + if (slots) { + return slots->mExtendedSlots.get(); + } + + return nullptr; + } + /** * Calls SetIsElementInStyleScopeFlagOnSubtree for each shadow tree attached * to this node, which is assumed to be an Element. diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp index 9540754f7..831987a96 100644 --- a/dom/base/ShadowRoot.cpp +++ b/dom/base/ShadowRoot.cpp @@ -75,8 +75,8 @@ ShadowRoot::ShadowRoot(nsIContent* aContent, SetFlags(NODE_IS_IN_SHADOW_TREE); - DOMSlots()->mBindingParent = aContent; - DOMSlots()->mContainingShadow = this; + ExtendedDOMSlots()->mBindingParent = aContent; + ExtendedDOMSlots()->mContainingShadow = this; // Add the ShadowRoot as a mutation observer on the host to watch // for mutations because the insertion points in this ShadowRoot diff --git a/dom/base/crashtests/1341693.html b/dom/base/crashtests/1341693.html new file mode 100644 index 000000000..677305ba5 --- /dev/null +++ b/dom/base/crashtests/1341693.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<body> +<script> + var o1 = document.documentElement; + var o2 = document.createElement("frame"); + document.documentElement.appendChild(o2); + var o3 = o2.contentWindow; + o1.parentNode.removeChild(o1); + o3.customElements; +</script> +</body> +</html> diff --git a/dom/base/crashtests/crashtests.list b/dom/base/crashtests/crashtests.list index a0739f074..0fb597b30 100644 --- a/dom/base/crashtests/crashtests.list +++ b/dom/base/crashtests/crashtests.list @@ -209,3 +209,4 @@ load 1230422.html load 1251361.html load 1304437.html pref(clipboard.autocopy,true) load 1385272-1.html +pref(dom.webcomponents.customelements.enabled,true) load 1341693.html diff --git a/dom/base/nsContentCreatorFunctions.h b/dom/base/nsContentCreatorFunctions.h index 9576d9ba8..a5c210500 100644 --- a/dom/base/nsContentCreatorFunctions.h +++ b/dom/base/nsContentCreatorFunctions.h @@ -24,6 +24,7 @@ namespace mozilla { namespace dom { class Element; class NodeInfo; +struct CustomElementDefinition; } // namespace dom } // namespace mozilla @@ -41,7 +42,8 @@ nsresult NS_NewHTMLElement(mozilla::dom::Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, mozilla::dom::FromParser aFromParser, - const nsAString* aIs = nullptr); + const nsAString* aIs = nullptr, + mozilla::dom::CustomElementDefinition* aDefinition = nullptr); // First argument should be nsHTMLTag, but that adds dependency to parser // for a bunch of files. diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 800f40fa1..b6cbbbace 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -227,6 +227,7 @@ extern "C" int MOZ_XMLCheckQName(const char* ptr, const char* end, int ns_aware, const char** colon); class imgLoader; +class nsIAtom; using namespace mozilla::dom; using namespace mozilla::ipc; @@ -258,7 +259,6 @@ nsIWordBreaker *nsContentUtils::sWordBreaker; nsIBidiKeyboard *nsContentUtils::sBidiKeyboard = nullptr; uint32_t nsContentUtils::sScriptBlockerCount = 0; uint32_t nsContentUtils::sDOMNodeRemovedSuppressCount = 0; -uint32_t nsContentUtils::sMicroTaskLevel = 0; AutoTArray<nsCOMPtr<nsIRunnable>, 8>* nsContentUtils::sBlockedScriptRunners = nullptr; uint32_t nsContentUtils::sRunnersCountAtFirstBlocker = 0; nsIInterfaceRequestor* nsContentUtils::sSameOriginChecker = nullptr; @@ -284,6 +284,8 @@ bool nsContentUtils::sIsResourceTimingEnabled = false; bool nsContentUtils::sIsPerformanceNavigationTimingEnabled = false; bool nsContentUtils::sIsUserTimingLoggingEnabled = false; bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false; +bool nsContentUtils::sIsWebComponentsEnabled = false; +bool nsContentUtils::sIsCustomElementsEnabled = false; bool nsContentUtils::sEncodeDecodeURLHash = false; bool nsContentUtils::sGettersDecodeURLHash = false; bool nsContentUtils::sPrivacyResistFingerprinting = false; @@ -584,6 +586,12 @@ nsContentUtils::Init() Preferences::AddBoolVarCache(&sIsExperimentalAutocompleteEnabled, "dom.forms.autocomplete.experimental", false); + Preferences::AddBoolVarCache(&sIsWebComponentsEnabled, + "dom.webcomponents.enabled", false); + + Preferences::AddBoolVarCache(&sIsCustomElementsEnabled, + "dom.webcomponents.customelements.enabled", false); + Preferences::AddBoolVarCache(&sEncodeDecodeURLHash, "dom.url.encode_decode_hash", false); @@ -5293,51 +5301,6 @@ nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable) CycleCollectedJSContext::Get()->RunInMetastableState(Move(aRunnable)); } -void -nsContentUtils::EnterMicroTask() -{ - MOZ_ASSERT(NS_IsMainThread()); - ++sMicroTaskLevel; -} - -void -nsContentUtils::LeaveMicroTask() -{ - MOZ_ASSERT(NS_IsMainThread()); - if (--sMicroTaskLevel == 0) { - PerformMainThreadMicroTaskCheckpoint(); - } -} - -bool -nsContentUtils::IsInMicroTask() -{ - MOZ_ASSERT(NS_IsMainThread()); - return sMicroTaskLevel != 0; -} - -uint32_t -nsContentUtils::MicroTaskLevel() -{ - MOZ_ASSERT(NS_IsMainThread()); - return sMicroTaskLevel; -} - -void -nsContentUtils::SetMicroTaskLevel(uint32_t aLevel) -{ - MOZ_ASSERT(NS_IsMainThread()); - sMicroTaskLevel = aLevel; -} - -void -nsContentUtils::PerformMainThreadMicroTaskCheckpoint() -{ - MOZ_ASSERT(NS_IsMainThread()); - - nsDOMMutationObserver::HandleMutations(); -} - /* * Helper function for nsContentUtils::ProcessViewportInfo. * @@ -9567,11 +9530,34 @@ nsContentUtils::HttpsStateIsModern(nsIDocument* aDocument) return false; } +/* static */ void +nsContentUtils::TryToUpgradeElement(Element* aElement) +{ + NodeInfo* nodeInfo = aElement->NodeInfo(); + RefPtr<nsIAtom> typeAtom = + aElement->GetCustomElementData()->GetCustomElementType(); + + MOZ_ASSERT(nodeInfo->NameAtom()->Equals(nodeInfo->LocalName())); + CustomElementDefinition* definition = + nsContentUtils::LookupCustomElementDefinition(nodeInfo->GetDocument(), + nodeInfo->NameAtom(), + nodeInfo->NamespaceID(), + typeAtom); + if (definition) { + nsContentUtils::EnqueueUpgradeReaction(aElement, definition); + } else { + // Add an unresolved custom element that is a candidate for + // upgrade when a custom element is connected to the document. + // We will make sure it's shadow-including tree order in bug 1326028. + nsContentUtils::RegisterUnresolvedElement(aElement, typeAtom); + } +} + /* static */ CustomElementDefinition* nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc, - const nsAString& aLocalName, + nsIAtom* aNameAtom, uint32_t aNameSpaceID, - const nsAString* aIs) + nsIAtom* aTypeAtom) { MOZ_ASSERT(aDoc); @@ -9593,30 +9579,37 @@ nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc, return nullptr; } - return registry->LookupCustomElementDefinition(aLocalName, aIs); + return registry->LookupCustomElementDefinition(aNameAtom, aTypeAtom); } /* static */ void -nsContentUtils::SetupCustomElement(Element* aElement, - const nsAString* aTypeExtension) +nsContentUtils::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName) { MOZ_ASSERT(aElement); - nsCOMPtr<nsIDocument> doc = aElement->OwnerDoc(); - - if (!doc) { + nsIDocument* doc = aElement->OwnerDoc(); + nsPIDOMWindowInner* window(doc->GetInnerWindow()); + if (!window) { return; } - // To support imported document. - doc = doc->MasterDocument(); - - if (aElement->GetNameSpaceID() != kNameSpaceID_XHTML || - !doc->GetDocShell()) { + RefPtr<CustomElementRegistry> registry(window->CustomElements()); + if (!registry) { return; } - nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow()); + registry->RegisterUnresolvedElement(aElement, aTypeName); +} + +/* static */ void +nsContentUtils::UnregisterUnresolvedElement(Element* aElement) +{ + MOZ_ASSERT(aElement); + + RefPtr<nsIAtom> typeAtom = + aElement->GetCustomElementData()->GetCustomElementType(); + nsIDocument* doc = aElement->OwnerDoc(); + nsPIDOMWindowInner* window(doc->GetInnerWindow()); if (!window) { return; } @@ -9626,36 +9619,59 @@ nsContentUtils::SetupCustomElement(Element* aElement, return; } - return registry->SetupCustomElement(aElement, aTypeExtension); + registry->UnregisterUnresolvedElement(aElement, typeAtom); +} + +/* static */ CustomElementDefinition* +nsContentUtils::GetElementDefinitionIfObservingAttr(Element* aCustomElement, + nsIAtom* aExtensionType, + nsIAtom* aAttrName) +{ + CustomElementDefinition* definition = + aCustomElement->GetCustomElementDefinition(); + + // Custom element not defined yet or attribute is not in the observed + // attribute list. + if (!definition || !definition->IsInObservedAttributeList(aAttrName)) { + return nullptr; + } + + return definition; } /* static */ void -nsContentUtils::EnqueueLifecycleCallback(nsIDocument* aDoc, - nsIDocument::ElementCallbackType aType, - Element* aCustomElement, - LifecycleCallbackArgs* aArgs, - CustomElementDefinition* aDefinition) +nsContentUtils::EnqueueUpgradeReaction(Element* aElement, + CustomElementDefinition* aDefinition) { - MOZ_ASSERT(aDoc); + MOZ_ASSERT(aElement); - // To support imported document. - nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument(); + nsIDocument* doc = aElement->OwnerDoc(); - if (!doc->GetDocShell()) { + // No DocGroup means no custom element reactions stack. + if (!doc->GetDocGroup()) { return; } - nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow()); - if (!window) { - return; - } + CustomElementReactionsStack* stack = + doc->GetDocGroup()->CustomElementReactionsStack(); + stack->EnqueueUpgradeReaction(aElement, aDefinition); +} - RefPtr<CustomElementRegistry> registry(window->CustomElements()); - if (!registry) { +/* static */ void +nsContentUtils::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, + Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + CustomElementDefinition* aDefinition) +{ + // No DocGroup means no custom element reactions stack. + if (!aCustomElement->OwnerDoc()->GetDocGroup()) { return; } - registry->EnqueueLifecycleCallback(aType, aCustomElement, aArgs, aDefinition); + CustomElementRegistry::EnqueueLifecycleCallback(aType, aCustomElement, aArgs, + aAdoptedCallbackArgs, + aDefinition); } /* static */ void @@ -9834,4 +9850,4 @@ nsContentUtils::IsLocalRefURL(const nsString& aString) } return false; -}
\ No newline at end of file +} diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 606d67de9..bf6a59dcd 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -126,6 +126,7 @@ class EventTarget; class IPCDataTransfer; class IPCDataTransferItem; struct LifecycleCallbackArgs; +struct LifecycleAdoptedCallbackArgs; class NodeInfo; class nsIContentChild; class nsIContentParent; @@ -1739,17 +1740,6 @@ public: */ static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable); - // Call EnterMicroTask when you're entering JS execution. - // Usually the best way to do this is to use nsAutoMicroTask. - static void EnterMicroTask(); - static void LeaveMicroTask(); - - static bool IsInMicroTask(); - static uint32_t MicroTaskLevel(); - static void SetMicroTaskLevel(uint32_t aLevel); - - static void PerformMainThreadMicroTaskCheckpoint(); - /* Process viewport META data. This gives us information for the scale * and zoom of a page on mobile devices. We stick the information in * the document header and use it later on after rendering. @@ -2711,22 +2701,36 @@ public: static bool HttpsStateIsModern(nsIDocument* aDocument); /** + * Try to upgrade an element. + * https://html.spec.whatwg.org/multipage/custom-elements.html#concept-try-upgrade + */ + static void TryToUpgradeElement(Element* aElement); + + /** * 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, + nsIAtom* aNameAtom, uint32_t aNameSpaceID, - const nsAString* aIs = nullptr); + nsIAtom* aTypeAtom); - static void SetupCustomElement(Element* aElement, - const nsAString* aTypeExtension = nullptr); + static void RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName); + static void UnregisterUnresolvedElement(Element* aElement); - static void EnqueueLifecycleCallback(nsIDocument* aDoc, - nsIDocument::ElementCallbackType aType, + static mozilla::dom::CustomElementDefinition* + GetElementDefinitionIfObservingAttr(Element* aCustomElement, + nsIAtom* aExtensionType, + nsIAtom* aAttrName); + + static void EnqueueUpgradeReaction(Element* aElement, + mozilla::dom::CustomElementDefinition* aDefinition); + + static void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, Element* aCustomElement, mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr, + mozilla::dom::LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs = nullptr, mozilla::dom::CustomElementDefinition* aDefinition = nullptr); static void GetCustomPrototype(nsIDocument* aDoc, @@ -2743,6 +2747,12 @@ public: static bool IsLocalRefURL(const nsString& aString); + static bool + IsWebComponentsEnabled() { return sIsWebComponentsEnabled; } + + static bool + IsCustomElementsEnabled() { return sIsCustomElementsEnabled; } + private: static bool InitializeEventTable(); @@ -2829,7 +2839,6 @@ private: static bool sInitialized; static uint32_t sScriptBlockerCount; static uint32_t sDOMNodeRemovedSuppressCount; - static uint32_t sMicroTaskLevel; // Not an nsCOMArray because removing elements from those is slower static AutoTArray<nsCOMPtr<nsIRunnable>, 8>* sBlockedScriptRunners; static uint32_t sRunnersCountAtFirstBlocker; @@ -2850,6 +2859,8 @@ private: static bool sIsUserTimingLoggingEnabled; static bool sIsFrameTimingPrefEnabled; static bool sIsExperimentalAutocompleteEnabled; + static bool sIsWebComponentsEnabled; + static bool sIsCustomElementsEnabled; static bool sEncodeDecodeURLHash; static bool sGettersDecodeURLHash; static bool sPrivacyResistFingerprinting; @@ -2905,19 +2916,6 @@ public: } }; -class MOZ_STACK_CLASS nsAutoMicroTask -{ -public: - nsAutoMicroTask() - { - nsContentUtils::EnterMicroTask(); - } - ~nsAutoMicroTask() - { - nsContentUtils::LeaveMicroTask(); - } -}; - namespace mozilla { namespace dom { diff --git a/dom/base/nsDOMMutationObserver.cpp b/dom/base/nsDOMMutationObserver.cpp index 858a30ce5..4c4731c11 100644 --- a/dom/base/nsDOMMutationObserver.cpp +++ b/dom/base/nsDOMMutationObserver.cpp @@ -32,8 +32,6 @@ using mozilla::dom::Element; AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* nsDOMMutationObserver::sScheduledMutationObservers = nullptr; -nsDOMMutationObserver* nsDOMMutationObserver::sCurrentObserver = nullptr; - uint32_t nsDOMMutationObserver::sMutationLevel = 0; uint64_t nsDOMMutationObserver::sCount = 0; @@ -597,10 +595,32 @@ nsDOMMutationObserver::ScheduleForRun() RescheduleForRun(); } +class MutationObserverMicroTask final : public MicroTaskRunnable +{ +public: + virtual void Run(AutoSlowOperation& aAso) override + { + nsDOMMutationObserver::HandleMutations(aAso); + } + + virtual bool Suppressed() override + { + return nsDOMMutationObserver::AllScheduledMutationObserversAreSuppressed(); + } +}; + void nsDOMMutationObserver::RescheduleForRun() { if (!sScheduledMutationObservers) { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (!ccjs) { + return; + } + + RefPtr<MutationObserverMicroTask> momt = + new MutationObserverMicroTask(); + ccjs->DispatchMicroTaskRunnable(momt.forget()); sScheduledMutationObservers = new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>; } @@ -862,36 +882,9 @@ nsDOMMutationObserver::HandleMutation() mCallback->Call(this, mutations, *this); } -class AsyncMutationHandler : public mozilla::Runnable -{ -public: - NS_IMETHOD Run() override - { - nsDOMMutationObserver::HandleMutations(); - return NS_OK; - } -}; - void -nsDOMMutationObserver::HandleMutationsInternal() +nsDOMMutationObserver::HandleMutationsInternal(AutoSlowOperation& aAso) { - if (!nsContentUtils::IsSafeToRunScript()) { - nsContentUtils::AddScriptRunner(new AsyncMutationHandler()); - return; - } - static RefPtr<nsDOMMutationObserver> sCurrentObserver; - if (sCurrentObserver && !sCurrentObserver->Suppressed()) { - // In normal cases sScheduledMutationObservers will be handled - // after previous mutations are handled. But in case some - // callback calls a sync API, which spins the eventloop, we need to still - // process other mutations happening during that sync call. - // This does *not* catch all cases, but should work for stuff running - // in separate tabs. - return; - } - - mozilla::AutoSlowOperation aso; - nsTArray<RefPtr<nsDOMMutationObserver> >* suppressedObservers = nullptr; while (sScheduledMutationObservers) { @@ -899,20 +892,21 @@ nsDOMMutationObserver::HandleMutationsInternal() sScheduledMutationObservers; sScheduledMutationObservers = nullptr; for (uint32_t i = 0; i < observers->Length(); ++i) { - sCurrentObserver = static_cast<nsDOMMutationObserver*>((*observers)[i]); - if (!sCurrentObserver->Suppressed()) { - sCurrentObserver->HandleMutation(); + RefPtr<nsDOMMutationObserver> currentObserver = + static_cast<nsDOMMutationObserver*>((*observers)[i]); + if (!currentObserver->Suppressed()) { + currentObserver->HandleMutation(); } else { if (!suppressedObservers) { suppressedObservers = new nsTArray<RefPtr<nsDOMMutationObserver> >; } - if (!suppressedObservers->Contains(sCurrentObserver)) { - suppressedObservers->AppendElement(sCurrentObserver); + if (!suppressedObservers->Contains(currentObserver)) { + suppressedObservers->AppendElement(currentObserver); } } } delete observers; - aso.CheckForInterrupt(); + aAso.CheckForInterrupt(); } if (suppressedObservers) { @@ -923,7 +917,6 @@ nsDOMMutationObserver::HandleMutationsInternal() delete suppressedObservers; suppressedObservers = nullptr; } - sCurrentObserver = nullptr; } nsDOMMutationRecord* diff --git a/dom/base/nsDOMMutationObserver.h b/dom/base/nsDOMMutationObserver.h index cde32c57b..a8babc603 100644 --- a/dom/base/nsDOMMutationObserver.h +++ b/dom/base/nsDOMMutationObserver.h @@ -552,13 +552,29 @@ public: } // static methods - static void HandleMutations() + static void HandleMutations(mozilla::AutoSlowOperation& aAso) { if (sScheduledMutationObservers) { - HandleMutationsInternal(); + HandleMutationsInternal(aAso); } } + static bool AllScheduledMutationObserversAreSuppressed() + { + if (sScheduledMutationObservers) { + uint32_t len = sScheduledMutationObservers->Length(); + if (len > 0) { + for (uint32_t i = 0; i < len; ++i) { + if (!(*sScheduledMutationObservers)[i]->Suppressed()) { + return false; + } + } + return true; + } + } + return false; + } + static void EnterMutationHandling(); static void LeaveMutationHandling(); @@ -594,7 +610,7 @@ protected: return false; } - static void HandleMutationsInternal(); + static void HandleMutationsInternal(mozilla::AutoSlowOperation& aAso); static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver, uint32_t aMutationLevel); @@ -622,7 +638,6 @@ protected: static uint64_t sCount; static AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* sScheduledMutationObservers; - static nsDOMMutationObserver* sCurrentObserver; static uint32_t sMutationLevel; static AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>* diff --git a/dom/base/nsDeprecatedOperationList.h b/dom/base/nsDeprecatedOperationList.h index ea4b05289..96a6c3ac0 100644 --- a/dom/base/nsDeprecatedOperationList.h +++ b/dom/base/nsDeprecatedOperationList.h @@ -34,7 +34,6 @@ DEPRECATED_OPERATION(UseOfCaptureEvents) DEPRECATED_OPERATION(UseOfReleaseEvents) DEPRECATED_OPERATION(UseOfDOM3LoadMethod) DEPRECATED_OPERATION(ChromeUseOfDOM3LoadMethod) -DEPRECATED_OPERATION(ShowModalDialog) DEPRECATED_OPERATION(Window_Content) DEPRECATED_OPERATION(SyncXMLHttpRequest) DEPRECATED_OPERATION(DataContainerEvent) diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index afe88a454..293e48eb0 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -1329,7 +1329,8 @@ nsIDocument::nsIDocument() mFrameRequestCallbacksScheduled(false), mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS), mPartID(0), - mUserHasInteracted(false) + mUserHasInteracted(false), + mThrowOnDynamicMarkupInsertionCounter(0) { SetIsInDocument(); @@ -5395,18 +5396,14 @@ nsDocument::CreateElement(const nsAString& aTagName, } const nsString* is = nullptr; - if (aOptions.IsElementCreationOptions()) { - // Throw NotFoundError if 'is' is not-null and definition is null - is = CheckCustomElementName(aOptions.GetAsElementCreationOptions(), - needsLowercase ? lcTagName : aTagName, mDefaultElementType, rv); - if (rv.Failed()) { - return nullptr; - } - } RefPtr<Element> elem = CreateElem( needsLowercase ? lcTagName : aTagName, nullptr, mDefaultElementType, is); + if (is) { + elem->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *is, true); + } + return elem.forget(); } @@ -5443,14 +5440,6 @@ nsDocument::CreateElementNS(const nsAString& aNamespaceURI, } const nsString* is = nullptr; - if (aOptions.IsElementCreationOptions()) { - // Throw NotFoundError if 'is' is not-null and definition is null - is = CheckCustomElementName(aOptions.GetAsElementCreationOptions(), - aQualifiedName, nodeInfo->NamespaceID(), rv); - if (rv.Failed()) { - return nullptr; - } - } nsCOMPtr<Element> element; rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), @@ -5459,6 +5448,10 @@ nsDocument::CreateElementNS(const nsAString& aNamespaceURI, return nullptr; } + if (is) { + element->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *is, true); + } + return element.forget(); } @@ -5681,24 +5674,70 @@ nsDocument::CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* } nsCOMPtr<nsIAtom> typeAtom(NS_Atomize(elemName)); - CustomElementDefinition* definition = registry->mCustomDefinitions.Get(typeAtom); + CustomElementDefinition* definition = + registry->mCustomDefinitions.GetWeak(typeAtom); if (!definition) { return true; } - nsDependentAtomString localName(definition->mLocalName); + RefPtr<Element> element; + + // We integrate with construction stack and do prototype swizzling here, so + // that old upgrade behavior could also share the new upgrade steps. + // And this old upgrade will be remove at some point (when everything is + // switched to latest custom element spec). + nsTArray<RefPtr<nsGenericHTMLElement>>& constructionStack = + definition->mConstructionStack; + if (constructionStack.Length()) { + element = constructionStack.LastElement(); + NS_ENSURE_TRUE(element != ALEADY_CONSTRUCTED_MARKER, false); + + // Do prototype swizzling if dom reflector exists. + JS::Rooted<JSObject*> reflector(aCx, element->GetWrapper()); + if (reflector) { + Maybe<JSAutoCompartment> ac; + JS::Rooted<JSObject*> prototype(aCx, definition->mPrototype); + if (element->NodePrincipal()->SubsumesConsideringDomain(nsContentUtils::ObjectPrincipal(prototype))) { + ac.emplace(aCx, reflector); + if (!JS_WrapObject(aCx, &prototype) || + !JS_SetPrototype(aCx, reflector, prototype)) { + return false; + } + } else { + // We want to set the custom prototype in the compartment where it was + // registered. We store the prototype from define() without unwrapped, + // hence the prototype's compartment is the compartment where it was + // registered. + // In the case that |reflector| and |prototype| are in different + // compartments, this will set the prototype on the |reflector|'s wrapper + // and thus only visible in the wrapper's compartment, since we know + // reflector's principal does not subsume prototype's in this case. + ac.emplace(aCx, prototype); + if (!JS_WrapObject(aCx, &reflector) || + !JS_SetPrototype(aCx, reflector, prototype)) { + return false; + } + } - nsCOMPtr<Element> element = - document->CreateElem(localName, nullptr, kNameSpaceID_XHTML); - NS_ENSURE_TRUE(element, true); + // Wrap into current context. + if (!JS_WrapObject(aCx, &reflector)) { + return false; + } - if (definition->mLocalName != typeAtom) { - // 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. - nsContentUtils::SetupCustomElement(element, &elemName); + args.rval().setObject(*reflector); + return true; + } + } else { + nsDependentAtomString localName(definition->mLocalName); + element = + document->CreateElem(localName, nullptr, kNameSpaceID_XHTML, + (definition->mLocalName != typeAtom) ? &elemName + : nullptr); + NS_ENSURE_TRUE(element, false); } + // The prototype setup happens in Element::WrapObject(). + nsresult rv = nsContentUtils::WrapNative(aCx, element, element, args.rval()); NS_ENSURE_SUCCESS(rv, true); @@ -5710,7 +5749,7 @@ nsDocument::IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject) { JS::Rooted<JSObject*> obj(aCx, aObject); - if (Preferences::GetBool("dom.webcomponents.enabled")) { + if (nsContentUtils::IsWebComponentsEnabled()) { return true; } @@ -5726,7 +5765,7 @@ nsDocument::IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject) bool nsDocument::IsWebComponentsEnabled(dom::NodeInfo* aNodeInfo) { - if (Preferences::GetBool("dom.webcomponents.enabled")) { + if (nsContentUtils::IsWebComponentsEnabled()) { return true; } @@ -5770,6 +5809,8 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType, return; } + AutoCEReaction ceReaction(this->GetDocGroup()->CustomElementReactionsStack(), + aCx); // Unconditionally convert TYPE to lowercase. nsAutoString lcType; nsContentUtils::ASCIIToLower(aType, lcType); @@ -6536,6 +6577,49 @@ nsIDocument::GetHtmlChildElement(nsIAtom* aTag) return nullptr; } +nsGenericHTMLElement* +nsIDocument::GetBody() +{ + Element* html = GetHtmlElement(); + if (!html) { + return nullptr; + } + + for (nsIContent* child = html->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (child->IsHTMLElement(nsGkAtoms::body) || + child->IsHTMLElement(nsGkAtoms::frameset)) { + return static_cast<nsGenericHTMLElement*>(child); + } + } + + return nullptr; +} + +void +nsIDocument::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) +{ + nsCOMPtr<Element> root = GetRootElement(); + + // The body element must be either a body tag or a frameset tag. And we must + // have a root element to be able to add kids to it. + if (!newBody || + !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset) || + !root) { + rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR); + return; + } + + // Use DOM methods so that we pass through the appropriate security checks. + nsCOMPtr<Element> currentBody = GetBody(); + if (currentBody) { + root->ReplaceChild(*newBody, *currentBody, rv); + } else { + root->AppendChild(*newBody, rv); + } +} + Element* nsDocument::GetTitleElement() { @@ -12526,8 +12610,12 @@ MarkDocumentTreeToBeInSyncOperation(nsIDocument* aDoc, void* aData) nsAutoSyncOperation::nsAutoSyncOperation(nsIDocument* aDoc) { - mMicroTaskLevel = nsContentUtils::MicroTaskLevel(); - nsContentUtils::SetMicroTaskLevel(0); + mMicroTaskLevel = 0; + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + mMicroTaskLevel = ccjs->MicroTaskLevel(); + ccjs->SetMicroTaskLevel(0); + } if (aDoc) { if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) { if (nsCOMPtr<nsPIDOMWindowOuter> top = win->GetTop()) { @@ -12543,7 +12631,10 @@ nsAutoSyncOperation::~nsAutoSyncOperation() for (int32_t i = 0; i < mDocuments.Count(); ++i) { mDocuments[i]->SetIsInSyncOperation(false); } - nsContentUtils::SetMicroTaskLevel(mMicroTaskLevel); + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->SetMicroTaskLevel(mMicroTaskLevel); + } } gfxUserFontSet* @@ -12713,30 +12804,6 @@ nsIDocument::UpdateStyleBackendType() #endif } -const nsString* -nsDocument::CheckCustomElementName(const ElementCreationOptions& aOptions, - const nsAString& aLocalName, - uint32_t aNamespaceID, - ErrorResult& rv) -{ - // only check aOptions if 'is' is passed and the webcomponents preference - // is enabled - if (!aOptions.mIs.WasPassed() || - !CustomElementRegistry::IsCustomElementEnabled()) { - return nullptr; - } - - const nsString* is = &aOptions.mIs.Value(); - - // Throw NotFoundError if 'is' is not-null and definition is null - if (!nsContentUtils::LookupCustomElementDefinition(this, aLocalName, - aNamespaceID, is)) { - rv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); - } - - return is; -} - Selection* nsIDocument::GetSelection(ErrorResult& aRv) { diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 8ea4993f0..90e511dcb 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -1388,20 +1388,6 @@ protected: private: static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp); - /** - * 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 - * usage. - * - * If there is no existing custom element definition for this name, throw a - * NotFoundError. - */ - const nsString* CheckCustomElementName( - const mozilla::dom::ElementCreationOptions& aOptions, - const nsAString& aLocalName, - uint32_t aNamespaceID, - ErrorResult& rv); - public: virtual already_AddRefed<mozilla::dom::CustomElementRegistry> GetCustomElementRegistry() override; diff --git a/dom/base/nsGenericDOMDataNode.cpp b/dom/base/nsGenericDOMDataNode.cpp index 0ae15e09e..73463ea5e 100644 --- a/dom/base/nsGenericDOMDataNode.cpp +++ b/dom/base/nsGenericDOMDataNode.cpp @@ -793,17 +793,6 @@ nsGenericDOMDataNode::SetXBLInsertionParent(nsIContent* aContent) } } -CustomElementData * -nsGenericDOMDataNode::GetCustomElementData() const -{ - return nullptr; -} - -void -nsGenericDOMDataNode::SetCustomElementData(CustomElementData* aData) -{ -} - bool nsGenericDOMDataNode::IsNodeOfType(uint32_t aFlags) const { diff --git a/dom/base/nsGenericDOMDataNode.h b/dom/base/nsGenericDOMDataNode.h index 63aa64e74..e8818b518 100644 --- a/dom/base/nsGenericDOMDataNode.h +++ b/dom/base/nsGenericDOMDataNode.h @@ -162,9 +162,6 @@ public: virtual bool IsNodeOfType(uint32_t aFlags) const override; virtual bool IsLink(nsIURI** aURI) const override; - virtual mozilla::dom::CustomElementData* GetCustomElementData() const override; - virtual void SetCustomElementData(mozilla::dom::CustomElementData* aData) override; - NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) override; NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const; virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute, diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 47b46dda0..c965d5b97 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -909,7 +909,6 @@ nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow) mMayHaveMouseEnterLeaveEventListener(false), mMayHavePointerEnterLeaveEventListener(false), mInnerObjectsFreed(false), - mIsModalContentWindow(false), mIsActive(false), mIsBackground(false), mMediaSuspend(Preferences::GetBool("media.block-autoplay-until-in-foreground", true) ? nsISuspendedTypes::SUSPENDED_BLOCK : nsISuspendedTypes::NONE_SUSPENDED), @@ -1957,7 +1956,6 @@ nsGlobalWindow::CleanUp() } mArguments = nullptr; - mDialogArguments = nullptr; CleanupCachedXBLHandlers(this); @@ -2195,7 +2193,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDialogArguments) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValue) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator) @@ -2274,7 +2271,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers) NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mDialogArguments) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValue) NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator) @@ -3019,8 +3015,6 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, } else { if (thisChrome) { newInnerWindow = nsGlobalChromeWindow::Create(this); - } else if (mIsModalContentWindow) { - newInnerWindow = nsGlobalModalWindow::Create(this); } else { newInnerWindow = nsGlobalWindow::Create(this); } @@ -3871,31 +3865,16 @@ nsGlobalWindow::SetArguments(nsIArray *aArguments) MOZ_ASSERT(IsOuterWindow()); nsresult rv; - // Historically, we've used the same machinery to handle openDialog arguments - // (exposed via window.arguments) and showModalDialog arguments (exposed via - // window.dialogArguments), even though the former is XUL-only and uses an XPCOM - // array while the latter is web-exposed and uses an arbitrary JS value. - // Moreover, per-spec |dialogArguments| is a property of the browsing context - // (outer), whereas |arguments| lives on the inner. - // // We've now mostly separated them, but the difference is still opaque to // nsWindowWatcher (the caller of SetArguments in this little back-and-forth // embedding waltz we do here). // // So we need to demultiplex the two cases here. nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); - if (mIsModalContentWindow) { - // nsWindowWatcher blindly converts the original nsISupports into an array - // of length 1. We need to recover it, and then cast it back to the concrete - // object we know it to be. - nsCOMPtr<nsISupports> supports = do_QueryElementAt(aArguments, 0, &rv); - NS_ENSURE_SUCCESS(rv, rv); - mDialogArguments = static_cast<DialogValueHolder*>(supports.get()); - } else { - mArguments = aArguments; - rv = currentInner->DefineArgumentsProperty(aArguments); - NS_ENSURE_SUCCESS(rv, rv); - } + + mArguments = aArguments; + rv = currentInner->DefineArgumentsProperty(aArguments); + NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } @@ -3904,7 +3883,6 @@ nsresult nsGlobalWindow::DefineArgumentsProperty(nsIArray *aArguments) { MOZ_ASSERT(IsInnerWindow()); - MOZ_ASSERT(!mIsModalContentWindow); // Handled separately. nsIScriptContext *ctx = GetOuterWindowInternal()->mContext; NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED); @@ -4263,8 +4241,9 @@ CustomElementRegistry* nsGlobalWindow::CustomElements() { MOZ_RELEASE_ASSERT(IsInnerWindow()); + if (!mCustomElements) { - mCustomElements = CustomElementRegistry::Create(AsInner()); + mCustomElements = new CustomElementRegistry(AsInner()); } return mCustomElements; @@ -5040,21 +5019,6 @@ nsGlobalWindow::IsPrivilegedChromeWindow(JSContext* aCx, JSObject* aObj) nsContentUtils::ObjectPrincipal(aObj) == nsContentUtils::GetSystemPrincipal(); } -/* static */ bool -nsGlobalWindow::IsShowModalDialogEnabled(JSContext*, JSObject*) -{ - static bool sAddedPrefCache = false; - static bool sIsDisabled; - static const char sShowModalDialogPref[] = "dom.disable_window_showModalDialog"; - - if (!sAddedPrefCache) { - Preferences::AddBoolVarCache(&sIsDisabled, sShowModalDialogPref, false); - sAddedPrefCache = true; - } - - return !sIsDisabled && !XRE_IsContentProcess(); -} - nsIDOMOfflineResourceList* nsGlobalWindow::GetApplicationCache(ErrorResult& aError) { @@ -9741,126 +9705,6 @@ nsGlobalWindow::ConvertDialogOptions(const nsAString& aOptions, } } -already_AddRefed<nsIVariant> -nsGlobalWindow::ShowModalDialogOuter(const nsAString& aUrl, - nsIVariant* aArgument, - const nsAString& aOptions, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - MOZ_RELEASE_ASSERT(IsOuterWindow()); - - if (mDoc) { - mDoc->WarnOnceAbout(nsIDocument::eShowModalDialog); - } - - if (!IsShowModalDialogEnabled()) { - aError.Throw(NS_ERROR_NOT_AVAILABLE); - return nullptr; - } - - RefPtr<DialogValueHolder> argHolder = - new DialogValueHolder(&aSubjectPrincipal, aArgument); - - // Before bringing up the window/dialog, unsuppress painting and flush - // pending reflows. - EnsureReflowFlushAndPaint(); - - if (!AreDialogsEnabled()) { - // We probably want to keep throwing here; silently doing nothing is a bit - // weird given the typical use cases of showModalDialog(). - aError.Throw(NS_ERROR_NOT_AVAILABLE); - return nullptr; - } - - if (ShouldPromptToBlockDialogs() && !ConfirmDialogIfNeeded()) { - aError.Throw(NS_ERROR_NOT_AVAILABLE); - return nullptr; - } - - nsCOMPtr<nsPIDOMWindowOuter> dlgWin; - nsAutoString options(NS_LITERAL_STRING("-moz-internal-modal=1,status=1")); - - ConvertDialogOptions(aOptions, options); - - options.AppendLiteral(",scrollbars=1,centerscreen=1,resizable=0"); - - EnterModalState(); - uint32_t oldMicroTaskLevel = nsContentUtils::MicroTaskLevel(); - nsContentUtils::SetMicroTaskLevel(0); - aError = OpenInternal(aUrl, EmptyString(), options, - false, // aDialog - true, // aContentModal - true, // aCalledNoScript - true, // aDoJSFixups - true, // aNavigate - nullptr, argHolder, // args - nullptr, // aLoadInfo - false, // aForceNoOpener - getter_AddRefs(dlgWin)); - nsContentUtils::SetMicroTaskLevel(oldMicroTaskLevel); - LeaveModalState(); - if (aError.Failed()) { - return nullptr; - } - - nsCOMPtr<nsIDOMModalContentWindow> dialog = do_QueryInterface(dlgWin); - if (!dialog) { - return nullptr; - } - - nsCOMPtr<nsIVariant> retVal; - aError = dialog->GetReturnValue(getter_AddRefs(retVal)); - MOZ_ASSERT(!aError.Failed()); - - return retVal.forget(); -} - -already_AddRefed<nsIVariant> -nsGlobalWindow::ShowModalDialog(const nsAString& aUrl, nsIVariant* aArgument, - const nsAString& aOptions, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - FORWARD_TO_OUTER_OR_THROW(ShowModalDialogOuter, - (aUrl, aArgument, aOptions, aSubjectPrincipal, - aError), aError, nullptr); -} - -void -nsGlobalWindow::ShowModalDialog(JSContext* aCx, const nsAString& aUrl, - JS::Handle<JS::Value> aArgument, - const nsAString& aOptions, - JS::MutableHandle<JS::Value> aRetval, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - MOZ_ASSERT(IsInnerWindow()); - - nsCOMPtr<nsIVariant> args; - aError = nsContentUtils::XPConnect()->JSToVariant(aCx, - aArgument, - getter_AddRefs(args)); - if (aError.Failed()) { - return; - } - - nsCOMPtr<nsIVariant> retVal = - ShowModalDialog(aUrl, args, aOptions, aSubjectPrincipal, aError); - if (aError.Failed()) { - return; - } - - JS::Rooted<JS::Value> result(aCx); - if (retVal) { - aError = nsContentUtils::XPConnect()->VariantToJS(aCx, - FastGetGlobalJSObject(), - retVal, aRetval); - } else { - aRetval.setNull(); - } -} - class ChildCommandDispatcher : public Runnable { public: @@ -14613,70 +14457,6 @@ nsGlobalChromeWindow::TakeOpenerForInitialContentBrowser(mozIDOMWindowProxy** aO return NS_OK; } -// nsGlobalModalWindow implementation - -// QueryInterface implementation for nsGlobalModalWindow -NS_INTERFACE_MAP_BEGIN(nsGlobalModalWindow) - NS_INTERFACE_MAP_ENTRY(nsIDOMModalContentWindow) -NS_INTERFACE_MAP_END_INHERITING(nsGlobalWindow) - -NS_IMPL_ADDREF_INHERITED(nsGlobalModalWindow, nsGlobalWindow) -NS_IMPL_RELEASE_INHERITED(nsGlobalModalWindow, nsGlobalWindow) - - -void -nsGlobalWindow::GetDialogArgumentsOuter(JSContext* aCx, - JS::MutableHandle<JS::Value> aRetval, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - MOZ_RELEASE_ASSERT(IsOuterWindow()); - MOZ_ASSERT(IsModalContentWindow(), - "This should only be called on modal windows!"); - - if (!mDialogArguments) { - MOZ_ASSERT(mIsClosed, "This window should be closed!"); - aRetval.setUndefined(); - return; - } - - // This does an internal origin check, and returns undefined if the subject - // does not subsumes the origin of the arguments. - JS::Rooted<JSObject*> wrapper(aCx, GetWrapper()); - JSAutoCompartment ac(aCx, wrapper); - mDialogArguments->Get(aCx, wrapper, &aSubjectPrincipal, aRetval, aError); -} - -void -nsGlobalWindow::GetDialogArguments(JSContext* aCx, - JS::MutableHandle<JS::Value> aRetval, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - FORWARD_TO_OUTER_OR_THROW(GetDialogArgumentsOuter, - (aCx, aRetval, aSubjectPrincipal, aError), - aError, ); -} - -/* static */ already_AddRefed<nsGlobalModalWindow> -nsGlobalModalWindow::Create(nsGlobalWindow *aOuterWindow) -{ - RefPtr<nsGlobalModalWindow> window = new nsGlobalModalWindow(aOuterWindow); - window->InitWasOffline(); - return window.forget(); -} - -NS_IMETHODIMP -nsGlobalModalWindow::GetDialogArguments(nsIVariant **aArguments) -{ - FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetDialogArguments, (aArguments), - NS_ERROR_NOT_INITIALIZED); - - // This does an internal origin check, and returns undefined if the subject - // does not subsumes the origin of the arguments. - return mDialogArguments->Get(nsContentUtils::SubjectPrincipal(), aArguments); -} - /* static */ already_AddRefed<nsGlobalWindow> nsGlobalWindow::Create(nsGlobalWindow *aOuterWindow) { @@ -14691,96 +14471,6 @@ nsGlobalWindow::InitWasOffline() mWasOffline = NS_IsOffline(); } -void -nsGlobalWindow::GetReturnValueOuter(JSContext* aCx, - JS::MutableHandle<JS::Value> aReturnValue, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - MOZ_RELEASE_ASSERT(IsOuterWindow()); - MOZ_ASSERT(IsModalContentWindow(), - "This should only be called on modal windows!"); - - if (mReturnValue) { - JS::Rooted<JSObject*> wrapper(aCx, GetWrapper()); - JSAutoCompartment ac(aCx, wrapper); - mReturnValue->Get(aCx, wrapper, &aSubjectPrincipal, aReturnValue, aError); - } else { - aReturnValue.setUndefined(); - } -} - -void -nsGlobalWindow::GetReturnValue(JSContext* aCx, - JS::MutableHandle<JS::Value> aReturnValue, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - FORWARD_TO_OUTER_OR_THROW(GetReturnValueOuter, - (aCx, aReturnValue, aSubjectPrincipal, aError), - aError, ); -} - -NS_IMETHODIMP -nsGlobalModalWindow::GetReturnValue(nsIVariant **aRetVal) -{ - FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetReturnValue, (aRetVal), NS_OK); - - if (!mReturnValue) { - nsCOMPtr<nsIVariant> variant = CreateVoidVariant(); - variant.forget(aRetVal); - return NS_OK; - } - return mReturnValue->Get(nsContentUtils::SubjectPrincipal(), aRetVal); -} - -void -nsGlobalWindow::SetReturnValueOuter(JSContext* aCx, - JS::Handle<JS::Value> aReturnValue, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - MOZ_RELEASE_ASSERT(IsOuterWindow()); - MOZ_ASSERT(IsModalContentWindow(), - "This should only be called on modal windows!"); - - nsCOMPtr<nsIVariant> returnValue; - aError = - nsContentUtils::XPConnect()->JSToVariant(aCx, aReturnValue, - getter_AddRefs(returnValue)); - if (!aError.Failed()) { - mReturnValue = new DialogValueHolder(&aSubjectPrincipal, returnValue); - } -} - -void -nsGlobalWindow::SetReturnValue(JSContext* aCx, - JS::Handle<JS::Value> aReturnValue, - nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) -{ - FORWARD_TO_OUTER_OR_THROW(SetReturnValueOuter, - (aCx, aReturnValue, aSubjectPrincipal, aError), - aError, ); -} - -NS_IMETHODIMP -nsGlobalModalWindow::SetReturnValue(nsIVariant *aRetVal) -{ - FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(SetReturnValue, (aRetVal), NS_OK); - - mReturnValue = new DialogValueHolder(nsContentUtils::SubjectPrincipal(), - aRetVal); - return NS_OK; -} - -/* static */ -bool -nsGlobalWindow::IsModalContentWindow(JSContext* aCx, JSObject* aGlobal) -{ - return xpc::WindowOrNull(aGlobal)->IsModalContentWindow(); -} - #if defined(MOZ_WIDGET_ANDROID) int16_t nsGlobalWindow::Orientation(CallerType aCallerType) const diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 1f420895c..5cfd3f056 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -458,9 +458,6 @@ public: static bool IsPrivilegedChromeWindow(JSContext* /* unused */, JSObject* aObj); - static bool IsShowModalDialogEnabled(JSContext* /* unused */ = nullptr, - JSObject* /* unused */ = nullptr); - bool DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId, JS::MutableHandle<JS::PropertyDescriptor> aDesc); @@ -583,9 +580,6 @@ public: return mIsChrome; } - using nsPIDOMWindow::IsModalContentWindow; - static bool IsModalContentWindow(JSContext* aCx, JSObject* aGlobal); - // GetScrollFrame does not flush. Callers should do it themselves as needed, // depending on which info they actually want off the scrollable frame. nsIScrollableFrame *GetScrollFrame(); @@ -950,12 +944,6 @@ public: mozilla::ErrorResult& aRv); void PrintOuter(mozilla::ErrorResult& aError); void Print(mozilla::ErrorResult& aError); - void ShowModalDialog(JSContext* aCx, const nsAString& aUrl, - JS::Handle<JS::Value> aArgument, - const nsAString& aOptions, - JS::MutableHandle<JS::Value> aRetval, - nsIPrincipal& aSubjectPrincipal, - mozilla::ErrorResult& aError); void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage, const nsAString& aTargetOrigin, const mozilla::dom::Optional<mozilla::dom::Sequence<JS::Value > >& aTransfer, @@ -1227,12 +1215,6 @@ public: mozilla::dom::Element* aPanel, mozilla::ErrorResult& aError); - void GetDialogArgumentsOuter(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval, - nsIPrincipal& aSubjectPrincipal, - mozilla::ErrorResult& aError); - void GetDialogArguments(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval, - nsIPrincipal& aSubjectPrincipal, - mozilla::ErrorResult& aError); void GetReturnValueOuter(JSContext* aCx, JS::MutableHandle<JS::Value> aReturnValue, nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aError); @@ -1681,18 +1663,6 @@ protected: nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aError); - already_AddRefed<nsIVariant> - ShowModalDialogOuter(const nsAString& aUrl, nsIVariant* aArgument, - const nsAString& aOptions, - nsIPrincipal& aSubjectPrincipal, - mozilla::ErrorResult& aError); - - already_AddRefed<nsIVariant> - ShowModalDialog(const nsAString& aUrl, nsIVariant* aArgument, - const nsAString& aOptions, - nsIPrincipal& aSubjectPrincipal, - mozilla::ErrorResult& aError); - // Ask the user if further dialogs should be blocked, if dialogs are currently // being abused. This is used in the cases where we have no modifiable UI to // show, in that case we show a separate dialog to ask this question. @@ -1832,9 +1802,6 @@ protected: // For |window.arguments|, via |openDialog|. nsCOMPtr<nsIArray> mArguments; - // For |window.dialogArguments|, via |showModalDialog|. - RefPtr<DialogValueHolder> mDialogArguments; - // Only used in the outer. RefPtr<DialogValueHolder> mReturnValue; @@ -2067,40 +2034,14 @@ public: nsCOMPtr<mozIDOMWindowProxy> mOpenerForInitialContentBrowser; }; -/* - * nsGlobalModalWindow inherits from nsGlobalWindow. It is the global - * object created for a modal content windows only (i.e. not modal - * chrome dialogs). - */ -class nsGlobalModalWindow : public nsGlobalWindow, - public nsIDOMModalContentWindow -{ -public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_NSIDOMMODALCONTENTWINDOW - - static already_AddRefed<nsGlobalModalWindow> Create(nsGlobalWindow *aOuterWindow); - -protected: - explicit nsGlobalModalWindow(nsGlobalWindow *aOuterWindow) - : nsGlobalWindow(aOuterWindow) - { - mIsModalContentWindow = true; - } - - ~nsGlobalModalWindow() {} -}; - /* factory function */ inline already_AddRefed<nsGlobalWindow> -NS_NewScriptGlobalObject(bool aIsChrome, bool aIsModalContentWindow) +NS_NewScriptGlobalObject(bool aIsChrome) { RefPtr<nsGlobalWindow> global; if (aIsChrome) { global = nsGlobalChromeWindow::Create(nullptr); - } else if (aIsModalContentWindow) { - global = nsGlobalModalWindow::Create(nullptr); } else { global = nsGlobalWindow::Create(nullptr); } diff --git a/dom/base/nsIContent.h b/dom/base/nsIContent.h index 405090865..dcdc632b4 100644 --- a/dom/base/nsIContent.h +++ b/dom/base/nsIContent.h @@ -26,7 +26,6 @@ namespace mozilla { class EventChainPreVisitor; namespace dom { class ShadowRoot; -struct CustomElementData; } // namespace dom namespace widget { struct IMEState; @@ -730,22 +729,6 @@ public: nsINode *GetFlattenedTreeParentNodeInternal() const; /** - * Gets the custom element data used by web components custom element. - * Custom element data is created at the first attempt to enqueue a callback. - * - * @return The custom element data or null if none. - */ - virtual mozilla::dom::CustomElementData *GetCustomElementData() const = 0; - - /** - * Sets the custom element data, ownership of the - * callback data is taken by this content. - * - * @param aCallbackData The custom element data. - */ - virtual void SetCustomElementData(mozilla::dom::CustomElementData* aData) = 0; - - /** * API to check if this is a link that's traversed in response to user input * (e.g. a click event). Specializations for HTML/SVG/generic XML allow for * different types of link in different types of content. diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index fdaee39ca..125816c95 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -61,6 +61,7 @@ class nsFrameLoader; class nsHTMLCSSStyleSheet; class nsHTMLDocument; class nsHTMLStyleSheet; +class nsGenericHTMLElement; class nsIAtom; class nsIBFCacheEntry; class nsIChannel; @@ -1036,6 +1037,11 @@ public: Element* GetHeadElement() { return GetHtmlChildElement(nsGkAtoms::head); } + // Get the "body" in the sense of document.body: The first <body> or + // <frameset> that's a child of a root <html> + nsGenericHTMLElement* GetBody(); + // Set the "body" in the sense of document.body. + void SetBody(nsGenericHTMLElement* aBody, mozilla::ErrorResult& rv); /** * Accessors to the collection of stylesheets owned by this document. @@ -2573,9 +2579,9 @@ public: } enum ElementCallbackType { - eCreated, - eAttached, - eDetached, + eConnected, + eDisconnected, + eAdopted, eAttributeChanged }; @@ -2872,6 +2878,22 @@ public: virtual void ScheduleIntersectionObserverNotification() = 0; virtual void NotifyIntersectionObservers() = 0; + bool ShouldThrowOnDynamicMarkupInsertion() + { + return mThrowOnDynamicMarkupInsertionCounter; + } + + void IncrementThrowOnDynamicMarkupInsertionCounter() + { + ++mThrowOnDynamicMarkupInsertionCounter; + } + + void DecrementThrowOnDynamicMarkupInsertionCounter() + { + MOZ_ASSERT(mThrowOnDynamicMarkupInsertionCounter); + --mThrowOnDynamicMarkupInsertionCounter; + } + protected: bool GetUseCounter(mozilla::UseCounter aUseCounter) { @@ -3319,6 +3341,11 @@ protected: uint32_t mBlockDOMContentLoaded; + // Used in conjunction with the create-an-element-for-the-token algorithm to + // prevent custom element constructors from being able to use document.open(), + // document.close(), and document.write() when they are invoked by the parser. + uint32_t mThrowOnDynamicMarkupInsertionCounter; + // Our live MediaQueryLists PRCList mDOMMediaQueryLists; @@ -3392,6 +3419,23 @@ private: uint32_t mMicroTaskLevel; }; +class MOZ_RAII AutoSetThrowOnDynamicMarkupInsertionCounter final { + public: + explicit AutoSetThrowOnDynamicMarkupInsertionCounter( + nsIDocument* aDocument) + : mDocument(aDocument) + { + mDocument->IncrementThrowOnDynamicMarkupInsertionCounter(); + } + + ~AutoSetThrowOnDynamicMarkupInsertionCounter() { + mDocument->DecrementThrowOnDynamicMarkupInsertionCounter(); + } + + private: + nsIDocument* mDocument; +}; + // XXX These belong somewhere else nsresult NS_NewHTMLDocument(nsIDocument** aInstancePtrResult, bool aLoadedAsData = false); diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index 98b367b66..b6c843065 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -25,7 +25,7 @@ #include "xpcpublic.h" #include "nsContentUtils.h" #include "nsGlobalWindow.h" - +#include "mozilla/CycleCollectedJSContext.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Date.h" #include "mozilla/dom/Element.h" @@ -159,7 +159,8 @@ nsJSUtils::EvaluateString(JSContext* aCx, aEvaluationGlobal); MOZ_ASSERT_IF(aOffThreadToken, aCompileOptions.noScriptRval); MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(nsContentUtils::IsInMicroTask()); + MOZ_ASSERT(CycleCollectedJSContext::Get() && + CycleCollectedJSContext::Get()->MicroTaskLevel()); // Unfortunately, the JS engine actually compiles scripts with a return value // in a different, less efficient way. Furthermore, it can't JIT them in many @@ -293,7 +294,8 @@ nsJSUtils::CompileModule(JSContext* aCx, aEvaluationGlobal); MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == aEvaluationGlobal); MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(nsContentUtils::IsInMicroTask()); + MOZ_ASSERT(CycleCollectedJSContext::Get() && + CycleCollectedJSContext::Get()->MicroTaskLevel()); NS_ENSURE_TRUE(xpc::Scriptability::Get(aEvaluationGlobal).Allowed(), NS_OK); @@ -330,7 +332,8 @@ nsJSUtils::ModuleEvaluation(JSContext* aCx, JS::Handle<JSObject*> aModule) MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(nsContentUtils::IsInMicroTask()); + MOZ_ASSERT(CycleCollectedJSContext::Get() && + CycleCollectedJSContext::Get()->MicroTaskLevel()); NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK); diff --git a/dom/base/nsNodeUtils.cpp b/dom/base/nsNodeUtils.cpp index 75d408151..384e56cde 100644 --- a/dom/base/nsNodeUtils.cpp +++ b/dom/base/nsNodeUtils.cpp @@ -301,9 +301,12 @@ nsNodeUtils::LastRelease(nsINode* aNode) Element* elem = aNode->AsElement(); FragmentOrElement::nsDOMSlots* domSlots = static_cast<FragmentOrElement::nsDOMSlots*>(slots); - for (auto iter = domSlots->mRegisteredIntersectionObservers.Iter(); !iter.Done(); iter.Next()) { - DOMIntersectionObserver* observer = iter.Key(); - observer->UnlinkTarget(*elem); + if (domSlots->mExtendedSlots) { + for (auto iter = domSlots->mExtendedSlots->mRegisteredIntersectionObservers.Iter(); + !iter.Done(); iter.Next()) { + DOMIntersectionObserver* observer = iter.Key(); + observer->UnlinkTarget(*elem); + } } } @@ -476,19 +479,33 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, rv = aNode->Clone(nodeInfo, getter_AddRefs(clone)); NS_ENSURE_SUCCESS(rv, rv); - if (clone->IsElement()) { + if (CustomElementRegistry::IsCustomElementEnabled() && + clone->IsHTMLElement()) { // The cloned node may be a custom element that may require - // enqueing created callback and prototype swizzling. - Element* elem = clone->AsElement(); - if (nsContentUtils::IsCustomElementName(nodeInfo->NameAtom())) { - 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()) { - nsContentUtils::SetupCustomElement(elem, &extension); + // enqueing upgrade reaction. + Element* cloneElem = clone->AsElement(); + RefPtr<nsIAtom> tagAtom = nodeInfo->NameAtom(); + CustomElementData* data = elem->GetCustomElementData(); + + // Check if node may be custom element by type extension. + // ex. <button is="x-button"> + nsAutoString extension; + if (!data || data->GetCustomElementType() != tagAtom) { + cloneElem->GetAttr(kNameSpaceID_None, nsGkAtoms::is, extension); + } + + if (data || !extension.IsEmpty()) { + RefPtr<nsIAtom> typeAtom = extension.IsEmpty() ? tagAtom : NS_Atomize(extension); + cloneElem->SetCustomElementData(new CustomElementData(typeAtom)); + + MOZ_ASSERT(nodeInfo->NameAtom()->Equals(nodeInfo->LocalName())); + CustomElementDefinition* definition = + nsContentUtils::LookupCustomElementDefinition(nodeInfo->GetDocument(), + nodeInfo->NameAtom(), + nodeInfo->NamespaceID(), + typeAtom); + if (definition) { + nsContentUtils::EnqueueUpgradeReaction(cloneElem, definition); } } } @@ -523,6 +540,23 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, nsIDocument* newDoc = aNode->OwnerDoc(); if (newDoc) { + if (CustomElementRegistry::IsCustomElementEnabled()) { + // Adopted callback must be enqueued whenever a node’s + // shadow-including inclusive descendants that is custom. + Element* element = aNode->IsElement() ? aNode->AsElement() : nullptr; + if (element) { + CustomElementData* data = element->GetCustomElementData(); + if (data && data->mState == CustomElementData::State::eCustom) { + LifecycleAdoptedCallbackArgs args = { + oldDoc, + newDoc + }; + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eAdopted, + element, nullptr, &args); + } + } + } + // XXX what if oldDoc is null, we don't know if this should be // registered or not! Can that really happen? if (wasRegistered) { diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index 47affbb06..5c07bf4d4 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -303,11 +303,6 @@ public: virtual bool CanClose() = 0; virtual void ForceClose() = 0; - bool IsModalContentWindow() const - { - return mIsModalContentWindow; - } - /** * Call this to indicate that some node (this window, its document, * or content in that document) has a paint event listener. @@ -629,11 +624,6 @@ protected: // This member is only used by inner windows. bool mInnerObjectsFreed; - - // This variable is used on both inner and outer windows (and they - // should match). - bool mIsModalContentWindow; - // Tracks activation state that's used for :-moz-window-inactive. // Only used on outer windows. bool mIsActive; diff --git a/dom/base/nsSandboxFlags.h b/dom/base/nsSandboxFlags.h index d18527597..b8c9ac357 100644 --- a/dom/base/nsSandboxFlags.h +++ b/dom/base/nsSandboxFlags.h @@ -29,8 +29,7 @@ const unsigned long SANDBOXED_NAVIGATION = 0x1; /** * This flag prevents content from creating new auxiliary browsing contexts, - * e.g. using the target attribute, the window.open() method, or the - * showModalDialog() method. + * e.g. using the target attribute, or the window.open() method. */ const unsigned long SANDBOXED_AUXILIARY_NAVIGATION = 0x2; diff --git a/dom/base/test/chrome/registerElement_ep.js b/dom/base/test/chrome/registerElement_ep.js index de32ba51c..9189593c0 100644 --- a/dom/base/test/chrome/registerElement_ep.js +++ b/dom/base/test/chrome/registerElement_ep.js @@ -1,8 +1,8 @@ var proto = Object.create(HTMLElement.prototype); proto.magicNumber = 42; -proto.createdCallback = function() { +proto.connectedCallback = function() { finishTest(this.magicNumber === 42); }; document.registerElement("x-foo", { prototype: proto }); -document.createElement("x-foo"); +document.firstChild.appendChild(document.createElement("x-foo")); diff --git a/dom/base/test/chrome/test_registerElement_content.xul b/dom/base/test/chrome/test_registerElement_content.xul index 9a918f2d7..bf00ed53d 100644 --- a/dom/base/test/chrome/test_registerElement_content.xul +++ b/dom/base/test/chrome/test_registerElement_content.xul @@ -21,19 +21,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1130028 <script type="application/javascript"><![CDATA[ /** Test for Bug 1130028 **/ - SimpleTest.waitForExplicitFinish(); + var connectedCallbackCount = 0; - var createdCallbackCount = 0; - - // Callback should be called once by element created in chrome, - // and once by element created in content. - function createdCallbackCalled() { - createdCallbackCount++; - ok(true, "Created callback called, should be called twice in test."); + // Callback should be called only once by element created in content. + function connectedCallbackCalled() { + connectedCallbackCount++; + is(connectedCallbackCount, 1, "Connected callback called, should be called once in test."); is(this.magicNumber, 42, "Callback should be able to see the custom prototype."); - if (createdCallbackCount == 2) { - SimpleTest.finish(); - } } function startTests() { @@ -45,10 +39,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1130028 var proto = Object.create(frame.contentWindow.HTMLElement.prototype); proto.magicNumber = 42; - proto.createdCallback = createdCallbackCalled; + proto.connectedCallback = connectedCallbackCalled; + frame.contentDocument.registerElement("x-bar", { prototype: proto }); + is(connectedCallbackCount, 1, "Connected callback should be called by element created in content."); - frame.contentDocument.createElement("x-bar"); + var element = frame.contentDocument.createElement("x-bar"); + is(element.magicNumber, 42, "Should be able to see the custom prototype on created element."); } ]]></script> diff --git a/dom/base/test/chrome/test_registerElement_ep.xul b/dom/base/test/chrome/test_registerElement_ep.xul index 6f1745268..b6a160c2e 100644 --- a/dom/base/test/chrome/test_registerElement_ep.xul +++ b/dom/base/test/chrome/test_registerElement_ep.xul @@ -26,8 +26,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1130028 SimpleTest.waitForExplicitFinish(); function finishTest(canSeePrototype) { - ok(true, "createdCallback called when reigsterElement was called with an extended principal."); - ok(canSeePrototype, "createdCallback should be able to see custom prototype."); + ok(true, "connectedCallback called when reigsterElement was called with an extended principal."); + ok(canSeePrototype, "connectedCallback should be able to see custom prototype."); SimpleTest.finish(); } diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index b3b804ce4..3dfd666f8 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -196,7 +196,6 @@ support-files = formReset.html invalid_accesscontrol.resource invalid_accesscontrol.resource^headers^ - mutationobserver_dialog.html orientationcommon.js script-1_bug597345.sjs script-2_bug597345.js @@ -627,9 +626,6 @@ subsuite = clipboard skip-if = toolkit == 'android' #bug 904183 [test_createHTMLDocument.html] [test_declare_stylesheet_obsolete.html] -[test_dialogArguments.html] -tags = openwindow -skip-if = toolkit == 'android' || e10s # showmodaldialog [test_document.all_iteration.html] [test_document.all_unqualified.html] [test_document_constructor.html] diff --git a/dom/base/test/mutationobserver_dialog.html b/dom/base/test/mutationobserver_dialog.html deleted file mode 100644 index 2cc815309..000000000 --- a/dom/base/test/mutationobserver_dialog.html +++ /dev/null @@ -1,62 +0,0 @@ -<html> - <head> - <title></title> - <script> - - var div = document.createElement("div"); - - var M; - if ("MozMutationObserver" in window) { - M = window.MozMutationObserver; - } else if ("WebKitMutationObserver" in window) { - M = window.WebKitMutationObserver; - } else { - M = window.MutationObserver; - } - - var didCall1 = false; - var didCall2 = false; - function testMutationObserverInDialog() { - div.innerHTML = "<span>1</span><span>2</span>"; - m = new M(function(records, observer) { - opener.is(records[0].type, "childList", "Should have got childList"); - opener.is(records[0].removedNodes.length, 2, "Should have got removedNodes"); - opener.is(records[0].addedNodes.length, 1, "Should have got addedNodes"); - observer.disconnect(); - m = null; - didCall1 = true; - }); - m.observe(div, { childList: true }); - div.innerHTML = "<span><span>foo</span></span>"; - } - - function testMutationObserverInDialog2() { - div.innerHTML = "<span>1</span><span>2</span>"; - m = new M(function(records, observer) { - opener.is(records[0].type, "childList", "Should have got childList"); - opener.is(records[0].removedNodes.length, 2, "Should have got removedNodes"); - opener.is(records[0].addedNodes.length, 1, "Should have got addedNodes"); - observer.disconnect(); - m = null; - didCall2 = true; - }); - m.observe(div, { childList: true }); - div.innerHTML = "<span><span>foo</span></span>"; - } - - window.addEventListener("load", testMutationObserverInDialog); - window.addEventListener("load", testMutationObserverInDialog2); - window.addEventListener("load", - function() { - opener.ok(didCall1, "Should have called 1st mutation callback"); - opener.ok(didCall2, "Should have called 2nd mutation callback"); - window.close(); - }); - </script> - <style> - </style> - </head> - <body> - <input type="button" onclick="window.close()" value="close"> - </body> -</html> diff --git a/dom/base/test/test_dialogArguments.html b/dom/base/test/test_dialogArguments.html deleted file mode 100644 index 70a091d00..000000000 --- a/dom/base/test/test_dialogArguments.html +++ /dev/null @@ -1,31 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>Test for Bug 1019761</title> - <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> - - <meta http-equiv="content-type" content="text/html; charset=utf-8"> -</head> -<body> -<script type="application/javascript"> - -/* - Tests whether Firefox crashes when accessing the dialogArguments property - of a modal window that has been closed. -*/ -SimpleTest.waitForExplicitFinish(); - -function openModal() { - showModalDialog("javascript:opener.winRef = window; \ - window.opener.setTimeout(\'winRef.dialogArguments;\', 0);\ - window.close();"); - - ok(true, "dialogArguments did not cause a crash."); - SimpleTest.finish(); -} - -window.onload = openModal; -</script> -</body> -</html> diff --git a/dom/base/test/test_mutationobservers.html b/dom/base/test/test_mutationobservers.html index bde07c79c..7e4c99423 100644 --- a/dom/base/test/test_mutationobservers.html +++ b/dom/base/test/test_mutationobservers.html @@ -362,7 +362,7 @@ function testChildList5() { is(records[5].previousSibling, c3, ""); is(records[5].nextSibling, c5, ""); observer.disconnect(); - then(testAdoptNode); + then(testNestedMutations); m = null; }); m.observe(div, { childList: true, subtree: true }); @@ -375,6 +375,37 @@ function testChildList5() { div.appendChild(emptyDF); // empty document shouldn't cause mutation records } +function testNestedMutations() { + div.textContent = null; + div.appendChild(document.createTextNode("foo")); + var m2WasCalled = false; + m = new M(function(records, observer) { + is(records[0].type, "characterData", "Should have got characterData"); + observer.disconnect(); + m = null; + m3 = new M(function(records, observer) { + ok(m2WasCalled, "m2 should have been called before m3!"); + is(records[0].type, "characterData", "Should have got characterData"); + observer.disconnect(); + then(testAdoptNode); + m3 = null; + }); + m3.observe(div, { characterData: true, subtree: true}); + div.firstChild.data = "foo"; + }); + m2 = new M(function(records, observer) { + m2WasCalled = true; + is(records[0].type, "characterData", "Should have got characterData"); + observer.disconnect(); + m2 = null; + }); + m2.observe(div, { characterData: true, subtree: true}); + div.appendChild(document.createTextNode("foo")); + m.observe(div, { characterData: true, subtree: true }); + + div.firstChild.data = "bar"; +} + function testAdoptNode() { var d1 = document.implementation.createHTMLDocument(null); var d2 = document.implementation.createHTMLDocument(null); @@ -484,28 +515,6 @@ function testSyncXHR() { function testSyncXHR2() { ok(callbackHandled, "Should have called the mutation callback!"); - then(testModalDialog); -} - -function testModalDialog() { - var didHandleCallback = false; - div.innerHTML = "<span>1</span><span>2</span>"; - m = new M(function(records, observer) { - is(records[0].type, "childList", "Should have got childList"); - is(records[0].removedNodes.length, 2, "Should have got removedNodes"); - is(records[0].addedNodes.length, 1, "Should have got addedNodes"); - observer.disconnect(); - m = null; - didHandleCallback = true; - }); - m.observe(div, { childList: true }); - div.innerHTML = "<span><span>foo</span></span>"; - try { - window.showModalDialog("mutationobserver_dialog.html"); - ok(didHandleCallback, "Should have called the callback while showing modal dialog!"); - } catch(e) { - todo(false, "showModalDialog not implemented on this platform"); - } then(testTakeRecords); } |