Bug 1378079 - Part 3: Complete the steps related to custom elements in "create an element for a token".

With fixup for missing header due to unified builds.

Tag UXP Issue #1344
This commit is contained in:
Gaming4JC 2020-01-19 21:22:39 -05:00 committed by Roy Tam
commit 5d26d32825
8 changed files with 203 additions and 141 deletions

View file

@ -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);
}

View file

@ -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.

View file

@ -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;
}

View file

@ -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]

View file

@ -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>

View file

@ -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.");

View file

@ -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;
dom::Element* newContent = newElement;
aBuilder->HoldElement(newElement.forget());
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 (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;
// 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);
}
}
nsTArray<nsString> theContent;
nsAutoString theAttribute;
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));
(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);
}
newContent->DoneAddingChildren(false);
}
if (!aAttributes) {
return newContent;
}
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()));
newContent->SetAttr(nsuri,
localName,
prefix,
uv,
false);
} else {
newContent->SetAttr(nsuri,
localName,
prefix,
value,
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);
if (definition) {
willExecuteScript = true;
}
}
}
if (willExecuteScript) { // This will cause custom element constructors to run
AutoSetThrowOnDynamicMarkupInsertionCounter
throwOnDynamicMarkupInsertionCounter(document);
mozAutoPauseContentUpdate autoPauseContentUpdate(document);
{
nsAutoMicroTask mt;
}
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;
}
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);
}
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);
}
} 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);
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;
}

View file

@ -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);