diff options
-rw-r--r-- | dom/base/CustomElementRegistry.cpp | 6 | ||||
-rw-r--r-- | dom/base/nsContentCreatorFunctions.h | 4 | ||||
-rw-r--r-- | dom/html/nsHTMLContentSink.cpp | 25 | ||||
-rw-r--r-- | dom/tests/mochitest/webcomponents/mochitest.ini | 3 | ||||
-rw-r--r-- | dom/tests/mochitest/webcomponents/test_document_register_base_queue.html | 48 | ||||
-rw-r--r-- | dom/tests/mochitest/webcomponents/test_document_register_parser.html | 4 | ||||
-rw-r--r-- | parser/html/nsHtml5TreeOperation.cpp | 250 | ||||
-rw-r--r-- | parser/html/nsHtml5TreeOperation.h | 4 |
8 files changed, 203 insertions, 141 deletions
diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp index 0f03b16bd..f17c2c7d9 100644 --- a/dom/base/CustomElementRegistry.cpp +++ b/dom/base/CustomElementRegistry.cpp @@ -1044,8 +1044,12 @@ CustomElementReactionsStack::PopAndInvokeElementQueue() // 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(); - MOZ_ASSERT(global, "Should always have a entry global here!"); InvokeReactions(elementQueue, global); } 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/html/nsHTMLContentSink.cpp b/dom/html/nsHTMLContentSink.cpp index 2827f5ff6..518a3675e 100644 --- a/dom/html/nsHTMLContentSink.cpp +++ b/dom/html/nsHTMLContentSink.cpp @@ -255,7 +255,8 @@ DoCustomElementCreate(Element** aElement, nsIDocument* aDoc, nsIAtom* aLocalName nsresult NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, - FromParser aFromParser, const nsAString* aIs) + FromParser aFromParser, const nsAString* aIs, + mozilla::dom::CustomElementDefinition* aDefinition) { *aResult = nullptr; @@ -276,8 +277,8 @@ NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& // We only handle the "synchronous custom elements flag is set" now. // For the unset case (e.g. cloning a node), see bug 1319342 for that. // Step 4. - CustomElementDefinition* definition = nullptr; - if (CustomElementRegistry::IsCustomElementEnabled()) { + CustomElementDefinition* definition = aDefinition; + if (!definition && CustomElementRegistry::IsCustomElementEnabled()) { definition = nsContentUtils::LookupCustomElementDefinition(nodeInfo->GetDocument(), nodeInfo->LocalName(), @@ -302,9 +303,20 @@ NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& bool synchronousCustomElements = aFromParser != dom::FROM_PARSER_FRAGMENT || aFromParser == dom::NOT_FROM_PARSER; // Per discussion in https://github.com/w3c/webcomponents/issues/635, - // use entry global in those places that are called from JS APIs. - nsIGlobalObject* global = GetEntryGlobal(); - MOZ_ASSERT(global); + // use entry global in those places that are called from JS APIs and use the + // node document's global object if it is called from parser. + nsIGlobalObject* global; + if (aFromParser == dom::NOT_FROM_PARSER) { + global = GetEntryGlobal(); + } else { + global = nodeInfo->GetDocument()->GetScopeObject(); + } + if (!global) { + // In browser chrome code, one may have access to a document which doesn't + // have scope object anymore. + return NS_ERROR_FAILURE; + } + AutoEntryScript aes(global, "create custom elements"); JSContext* cx = aes.cx(); ErrorResult rv; @@ -344,6 +356,7 @@ NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& // Step 6.2. NS_IF_ADDREF(*aResult = NS_NewHTMLElement(nodeInfo.forget(), aFromParser)); + (*aResult)->SetCustomElementData(new CustomElementData(definition->mType)); nsContentUtils::EnqueueUpgradeReaction(*aResult, definition); return NS_OK; } diff --git a/dom/tests/mochitest/webcomponents/mochitest.ini b/dom/tests/mochitest/webcomponents/mochitest.ini index e8f632e39..b32e9999e 100644 --- a/dom/tests/mochitest/webcomponents/mochitest.ini +++ b/dom/tests/mochitest/webcomponents/mochitest.ini @@ -10,6 +10,7 @@ support-files = [test_content_element.html] [test_custom_element_adopt_callbacks.html] [test_custom_element_callback_innerhtml.html] +skip-if = true # disabled - See bug 1390396 [test_custom_element_clone_callbacks_extended.html] [test_custom_element_htmlconstructor.html] skip-if = os == 'android' # bug 1323645 @@ -20,6 +21,7 @@ support-files = [test_custom_element_in_shadow.html] skip-if = true || stylo # disabled - See bug 1390396 and 1293844 [test_custom_element_register_invalid_callbacks.html] +[test_custom_element_throw_on_dynamic_markup_insertion.html] [test_custom_element_get.html] [test_custom_element_when_defined.html] [test_nested_content_element.html] @@ -31,7 +33,6 @@ skip-if = true || stylo # disabled - See bug 1390396 and 1293844 [test_document_adoptnode.html] [test_document_importnode.html] [test_document_register.html] -[test_document_register_base_queue.html] [test_document_register_lifecycle.html] skip-if = true # disabled - See bug 1390396 [test_document_register_parser.html] diff --git a/dom/tests/mochitest/webcomponents/test_document_register_base_queue.html b/dom/tests/mochitest/webcomponents/test_document_register_base_queue.html deleted file mode 100644 index c839857b4..000000000 --- a/dom/tests/mochitest/webcomponents/test_document_register_base_queue.html +++ /dev/null @@ -1,48 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=783129 ---> -<head> - <title>Test for document.registerElement lifecycle callback</title> - <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> -<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> -<script> -var p = Object.create(HTMLElement.prototype); - -var createdCallbackCallCount = 0; - -// By the time the base element queue is processed via the microtask, -// both x-hello elements should be in the document. -p.createdCallback = function() { - var one = document.getElementById("one"); - var two = document.getElementById("two"); - isnot(one, null, "First x-hello element should be in the tree."); - isnot(two, null, "Second x-hello element should be in the tree."); - createdCallbackCallCount++; - if (createdCallbackCallCount == 2) { - SimpleTest.finish(); - } - - if (createdCallbackCallCount > 2) { - ok(false, "Created callback called too much."); - } -}; - -p.attributeChangedCallback = function(name, oldValue, newValue) { - ok(false, "Attribute changed callback should not be called because callbacks should not be queued until created callback invoked."); -}; - -document.registerElement("x-hello", { prototype: p }); - -SimpleTest.waitForExplicitFinish(); -</script> -</head> -<body> -<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a> -<x-hello id="one"></x-hello> -<x-hello id="two"></x-hello> -<script> -</script> -</body> -</html> diff --git a/dom/tests/mochitest/webcomponents/test_document_register_parser.html b/dom/tests/mochitest/webcomponents/test_document_register_parser.html index bc4cdcbac..a4fb63139 100644 --- a/dom/tests/mochitest/webcomponents/test_document_register_parser.html +++ b/dom/tests/mochitest/webcomponents/test_document_register_parser.html @@ -11,7 +11,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=783129 var extendedButtonProto = Object.create(HTMLButtonElement.prototype); var buttonCallbackCalled = false; -extendedButtonProto.createdCallback = function() { +extendedButtonProto.connectedCallback = function() { is(buttonCallbackCalled, false, "created callback for x-button should only be called once."); is(this.tagName, "BUTTON", "Only the <button> element should be upgraded."); buttonCallbackCalled = true; @@ -21,7 +21,7 @@ document.registerElement("x-button", { prototype: extendedButtonProto, extends: var divProto = Object.create(HTMLDivElement.prototype); var divCallbackCalled = false; -divProto.createdCallback = function() { +divProto.connectedCallback = function() { is(divCallbackCalled, false, "created callback for x-div should only be called once."); is(buttonCallbackCalled, true, "crated callback should be called for x-button before x-div."); is(this.tagName, "X-DIV", "Only the <x-div> element should be upgraded."); diff --git a/parser/html/nsHtml5TreeOperation.cpp b/parser/html/nsHtml5TreeOperation.cpp index 0c5aad566..a7d3da259 100644 --- a/parser/html/nsHtml5TreeOperation.cpp +++ b/parser/html/nsHtml5TreeOperation.cpp @@ -24,6 +24,7 @@ #include "nsIFormControl.h" #include "nsIStyleSheetLinkingElement.h" #include "nsIDOMDocumentType.h" +#include "DocGroup.h" #include "nsIObserverService.h" #include "mozilla/Services.h" #include "nsIMutationObserver.h" @@ -74,6 +75,29 @@ class MOZ_STACK_CLASS nsHtml5OtherDocUpdate { nsCOMPtr<nsIDocument> mDocument; }; +/** + * Helper class to temporary break out of the document update batch. Use this + * with caution as this will cause blocked scripts to run. + */ +class MOZ_RAII mozAutoPauseContentUpdate final +{ +public: + explicit mozAutoPauseContentUpdate(nsIDocument* aDocument) + : mDocument(aDocument) + { + MOZ_ASSERT(mDocument); + mDocument->EndUpdate(UPDATE_CONTENT_MODEL); + } + + ~mozAutoPauseContentUpdate() + { + mDocument->BeginUpdate(UPDATE_CONTENT_MODEL); + } + +private: + nsCOMPtr<nsIDocument> mDocument; +}; + nsHtml5TreeOperation::nsHtml5TreeOperation() : mOpCode(eTreeOpUninitialized) { @@ -332,6 +356,41 @@ nsHtml5TreeOperation::AddAttributes(nsIContent* aNode, return NS_OK; } +void +nsHtml5TreeOperation::SetHTMLElementAttributes(dom::Element* aElement, + nsIAtom* aName, + nsHtml5HtmlAttributes* aAttributes) +{ + int32_t len = aAttributes->getLength(); + for (int32_t i = 0; i < len; i++) { + // prefix doesn't need regetting. it is always null or a static atom + // local name is never null + nsCOMPtr<nsIAtom> localName = + Reget(aAttributes->getLocalNameNoBoundsCheck(i)); + nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i); + int32_t nsuri = aAttributes->getURINoBoundsCheck(i); + + nsString value; // Not Auto, because using it to hold nsStringBuffer* + aAttributes->getValueNoBoundsCheck(i).ToString(value); + if (nsHtml5Atoms::a == aName && nsHtml5Atoms::name == localName) { + // This is an HTML5-incompliant Geckoism. + // Remove when fixing bug 582361 + NS_ConvertUTF16toUTF8 cname(value); + NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting())); + aElement->SetAttr(nsuri, + localName, + prefix, + uv, + false); + } else { + aElement->SetAttr(nsuri, + localName, + prefix, + value, + false); + } + } + } nsIContent* nsHtml5TreeOperation::CreateHTMLElement( @@ -351,106 +410,133 @@ nsHtml5TreeOperation::CreateHTMLElement( RefPtr<dom::NodeInfo> nodeInfo = aNodeInfoManager->GetNodeInfo( aName, nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE); NS_ASSERTION(nodeInfo, "Got null nodeinfo."); - nsCOMPtr<dom::Element> newElement = aCreator(nodeInfo.forget(), aFromParser); - MOZ_ASSERT(newElement, "Element creation created null pointer."); + dom::Element* newContent = nullptr; + nsIDocument* document = nodeInfo->GetDocument(); + bool willExecuteScript = false; + bool isCustomElement = false; + nsString isValue; + dom::CustomElementDefinition* definition = nullptr; + + // Avoid overhead by checking if custom elements pref is enabled or not. + if (nsContentUtils::IsCustomElementsEnabled()) { + if (aAttributes) { + nsHtml5String is = aAttributes->getValue(nsHtml5AttributeName::ATTR_IS); + if (is) { + is.ToString(isValue); + } + } - dom::Element* newContent = newElement; - aBuilder->HoldElement(newElement.forget()); + isCustomElement = (aCreator == NS_NewCustomElement || !isValue.IsEmpty()); + if (isCustomElement && aFromParser != dom::FROM_PARSER_FRAGMENT) { + definition = nsContentUtils::LookupCustomElementDefinition(document, + nodeInfo->LocalName(), nodeInfo->NamespaceID(), + (isValue.IsEmpty() ? nullptr : &isValue)); - if (aCreator == NS_NewCustomElement) { - // Not inlining the call below into NS_NewCustomElement itself, because - // in the near future, the code here will need to break out of an update - // batch here. - nsContentUtils::SetupCustomElement(newContent); + if (definition) { + willExecuteScript = true; + } + } } - if (MOZ_UNLIKELY(aName == nsHtml5Atoms::style || aName == nsHtml5Atoms::link)) { - nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(newContent)); - if (ssle) { - ssle->InitStyleLinkElement(false); - ssle->SetEnableUpdates(false); + if (willExecuteScript) { // This will cause custom element constructors to run + AutoSetThrowOnDynamicMarkupInsertionCounter + throwOnDynamicMarkupInsertionCounter(document); + mozAutoPauseContentUpdate autoPauseContentUpdate(document); + { + nsAutoMicroTask mt; } - } else if (MOZ_UNLIKELY(isKeygen)) { - // Adapted from CNavDTD - nsresult rv; - nsCOMPtr<nsIFormProcessor> theFormProcessor = - do_GetService(kFormProcessorCID, &rv); - if (NS_FAILED(rv)) { + dom::AutoCEReaction + autoCEReaction(document->GetDocGroup()->CustomElementReactionsStack()); + + nsCOMPtr<dom::Element> newElement; + NS_NewHTMLElement(getter_AddRefs(newElement), nodeInfo.forget(), + aFromParser, (isValue.IsEmpty() ? nullptr : &isValue), + definition); + + MOZ_ASSERT(newElement, "Element creation created null pointer."); + newContent = newElement; + aBuilder->HoldElement(newElement.forget()); + + if (MOZ_UNLIKELY(aName == nsHtml5Atoms::style || aName == nsHtml5Atoms::link)) { + nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(newContent)); + if (ssle) { + ssle->InitStyleLinkElement(false); + ssle->SetEnableUpdates(false); + } + } + + if (!aAttributes) { return newContent; } - nsTArray<nsString> theContent; - nsAutoString theAttribute; - - (void) theFormProcessor->ProvideContent(NS_LITERAL_STRING("select"), - theContent, - theAttribute); - - newContent->SetAttr(kNameSpaceID_None, - nsGkAtoms::moztype, - nullptr, - theAttribute, - false); - - RefPtr<dom::NodeInfo> optionNodeInfo = - aNodeInfoManager->GetNodeInfo(nsHtml5Atoms::option, - nullptr, - kNameSpaceID_XHTML, - nsIDOMNode::ELEMENT_NODE); - - for (uint32_t i = 0; i < theContent.Length(); ++i) { - RefPtr<dom::NodeInfo> ni = optionNodeInfo; - nsCOMPtr<dom::Element> optionElt = - NS_NewHTMLOptionElement(ni.forget(), aFromParser); - RefPtr<nsTextNode> optionText = new nsTextNode(aNodeInfoManager); - (void) optionText->SetText(theContent[i], false); - optionElt->AppendChildTo(optionText, false); - newContent->AppendChildTo(optionElt, false); + SetHTMLElementAttributes(newContent, aName, aAttributes); + } else { + nsCOMPtr<dom::Element> newElement; + + if (isCustomElement) { + NS_NewHTMLElement(getter_AddRefs(newElement), nodeInfo.forget(), + aFromParser, (isValue.IsEmpty() ? nullptr : &isValue), + definition); + } else { + newElement = aCreator(nodeInfo.forget(), aFromParser); } - newContent->DoneAddingChildren(false); - } - if (!aAttributes) { - return newContent; - } + MOZ_ASSERT(newElement, "Element creation created null pointer."); - int32_t len = aAttributes->getLength(); - for (int32_t i = 0; i < len; i++) { - // prefix doesn't need regetting. it is always null or a static atom - // local name is never null - nsCOMPtr<nsIAtom> localName = - Reget(aAttributes->getLocalNameNoBoundsCheck(i)); - nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i); - int32_t nsuri = aAttributes->getURINoBoundsCheck(i); + newContent = newElement; + aBuilder->HoldElement(newElement.forget()); - nsString value; // Not Auto, because using it to hold nsStringBuffer* - aAttributes->getValueNoBoundsCheck(i).ToString(value); - if (nsHtml5Atoms::a == aName && nsHtml5Atoms::name == localName) { - // This is an HTML5-incompliant Geckoism. - // Remove when fixing bug 582361 - NS_ConvertUTF16toUTF8 cname(value); - NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting())); - newContent->SetAttr(nsuri, - localName, - prefix, - uv, - false); - } else { - newContent->SetAttr(nsuri, - localName, - prefix, - value, + if (MOZ_UNLIKELY(aName == nsHtml5Atoms::style || aName == nsHtml5Atoms::link)) { + nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(newContent)); + if (ssle) { + ssle->InitStyleLinkElement(false); + ssle->SetEnableUpdates(false); + } + } else if (MOZ_UNLIKELY(isKeygen)) { + // Adapted from CNavDTD + nsresult rv; + nsCOMPtr<nsIFormProcessor> theFormProcessor = + do_GetService(kFormProcessorCID, &rv); + if (NS_FAILED(rv)) { + return newContent; + } + + nsTArray<nsString> theContent; + nsAutoString theAttribute; + + (void) theFormProcessor->ProvideContent(NS_LITERAL_STRING("select"), + theContent, + theAttribute); + + newContent->SetAttr(kNameSpaceID_None, + nsGkAtoms::moztype, + nullptr, + theAttribute, false); - // Custom element setup may be needed if there is an "is" attribute. - if (nsContentUtils::IsWebComponentsEnabled() && - kNameSpaceID_None == nsuri && - !prefix && nsGkAtoms::is == localName) { - nsContentUtils::SetupCustomElement(newContent, &value); + RefPtr<dom::NodeInfo> optionNodeInfo = aNodeInfoManager->GetNodeInfo( + nsHtml5Atoms::option, nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE); + + for (uint32_t i = 0; i < theContent.Length(); ++i) { + RefPtr<dom::NodeInfo> ni = optionNodeInfo; + nsCOMPtr<dom::Element> optionElt = + NS_NewHTMLOptionElement(ni.forget(), aFromParser); + RefPtr<nsTextNode> optionText = new nsTextNode(aNodeInfoManager); + (void) optionText->SetText(theContent[i], false); + optionElt->AppendChildTo(optionText, false); + newContent->AppendChildTo(optionElt, false); } + newContent->DoneAddingChildren(false); } + + if (!aAttributes) { + return newContent; + } + + SetHTMLElementAttributes(newContent, aName, aAttributes); } + return newContent; } diff --git a/parser/html/nsHtml5TreeOperation.h b/parser/html/nsHtml5TreeOperation.h index 0cdb08e2e..db6cc6397 100644 --- a/parser/html/nsHtml5TreeOperation.h +++ b/parser/html/nsHtml5TreeOperation.h @@ -143,6 +143,10 @@ class nsHtml5TreeOperation { nsIContent* aTable, nsHtml5DocumentBuilder* aBuilder); + static void SetHTMLElementAttributes(mozilla::dom::Element* aElement, + nsIAtom* aName, + nsHtml5HtmlAttributes* aAttributes); + static nsresult AddAttributes(nsIContent* aNode, nsHtml5HtmlAttributes* aAttributes, nsHtml5DocumentBuilder* aBuilder); |