Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for <model src> and honor <source type> attributes #7245

Merged
merged 1 commit into from Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions LayoutTests/model-element/model-element-source-expected.txt
Expand Up @@ -9,4 +9,16 @@ PASS Removing the <source> changes the currentSrc property.
PASS currentSrc returns the src value for the first <source> element.
PASS Removing a <source> element updates currentSrc.
PASS Adding a <source> before the current <source> updates currentSrc.
PASS The HTMLModelElement interface has a src property.
PASS HTMLModelElement src property reflects src attribute value as a resolved URL.
PASS <model> src attribute reflects HTMLModelElement src property.
PASS Changing the src attribute of a <model> changes the currentSrc property.
PASS A non-empty src attribute takes precedence over a <source> child.
PASS An empty src attribute does not take precedence over a <source> child.
PASS Removing a src attribute selects the first matching <source> child.
PASS The first <source> with a supported type is selected.
PASS Changing a <source type> to a supported type causes it to be selected.
PASS Changing a <source type> to an unsupported type causes it not to be selected.
PASS An inserted <source> with an unsupported type is not selected.
PASS An inserted <source> with a supported type is selected.

86 changes: 85 additions & 1 deletion LayoutTests/model-element/model-element-source.html
Expand Up @@ -2,9 +2,11 @@
<script src="../resources/testharnessreport.js"></script>
<script>

const makeSource = src => {
const makeSource = (src, type) => {
const source = document.createElement("source");
source.src = src;
if (type)
source.type = type;
return source;
}

Expand Down Expand Up @@ -76,6 +78,88 @@
assert_equals(model.currentSrc, secondSource.src);
}, "Adding a <source> before the current <source> updates currentSrc.");

test(() => {
assert_idl_attribute(document.createElement("model"), "src");
}, "The HTMLModelElement interface has a src property.");

test(() => {
const model = document.createElement("model");
model.setAttribute("src", "some-model.usdz");
assert_true(model.src.endsWith("/some-model.usdz"));
}, "HTMLModelElement src property reflects src attribute value as a resolved URL.");

test(() => {
const model = document.createElement("model");
model.src = "some-model.usdz";
assert_equals(model.getAttribute("src"), "some-model.usdz");
}, "<model> src attribute reflects HTMLModelElement src property.");

test(() => {
const model = document.createElement("model");
model.src = "model.usdz";
assert_equals(model.currentSrc, model.src);
}, "Changing the src attribute of a <model> changes the currentSrc property.");

test(() => {
const model = document.createElement("model");
model.appendChild(makeSource("model-child.usdz"));
model.src = "model-attr.usdz";
assert_equals(model.currentSrc, model.src);
}, "A non-empty src attribute takes precedence over a <source> child.");

test(() => {
const model = document.createElement("model");
const source = model.appendChild(makeSource("model-child.usdz"));
model.src = "";
assert_true(model.hasAttribute("src"));
assert_equals(model.currentSrc, source.src);
}, "An empty src attribute does not take precedence over a <source> child.");

test(() => {
const model = document.createElement("model");
const source = model.appendChild(makeSource("model-child.usdz"));
model.src = "model-attr.usdz";
assert_equals(model.currentSrc, model.src);
model.src = "";
assert_equals(model.currentSrc, source.src);
}, "Removing a src attribute selects the first matching <source> child.");

test(() => {
const model = document.createElement("model");
const firstSource = model.appendChild(makeSource("model-child-1.unknown", "model/unknown"));
const secondSource = model.appendChild(makeSource("model-child-2.usdz", "model/vnd.usdz+zip"));
assert_equals(model.currentSrc, secondSource.src);
}, "The first <source> with a supported type is selected.");

test(() => {
const model = document.createElement("model");
const firstSource = model.appendChild(makeSource("model-child-1.usdz", "model/unknown"));
const secondSource = model.appendChild(makeSource("model-child-2.usdz", "model/vnd.usdz+zip"));
firstSource.type = "model/vnd.usdz+zip";
assert_equals(model.currentSrc, firstSource.src);
}, "Changing a <source type> to a supported type causes it to be selected.");

test(() => {
const model = document.createElement("model");
const firstSource = model.appendChild(makeSource("model-child-1.usdz", "model/vnd.usdz+zip"));
const secondSource = model.appendChild(makeSource("model-child-2.usdz", "model/vnd.usdz+zip"));
firstSource.type = "model/unknown";
assert_equals(model.currentSrc, secondSource.src);
}, "Changing a <source type> to an unsupported type causes it not to be selected.");

test(() => {
const model = document.createElement("model");
const initialSource = model.appendChild(makeSource("model-child-1.usdz", "model/vnd.usdz+zip"));
const newSource = model.insertBefore(makeSource("model-child-2.usdz", "model/unsupported"), model.firstChild);
assert_equals(model.currentSrc, initialSource.src);
}, "An inserted <source> with an unsupported type is not selected.");

test(() => {
const model = document.createElement("model");
const initialSource = model.appendChild(makeSource("model-child-1.usdz", "model/vnd.usdz+zip"));
const newSource = model.insertBefore(makeSource("model-child-2.usdz", "model/vnd.usdz+zip"), model.firstChild);
assert_equals(model.currentSrc, newSource.src);
}, "An inserted <source> with a supported type is selected.");
</script>
</body>
</html>
56 changes: 40 additions & 16 deletions Source/WebCore/Modules/model-element/HTMLModelElement.cpp
Expand Up @@ -48,6 +48,7 @@
#include "JSHTMLModelElementCamera.h"
#include "LayoutRect.h"
#include "LayoutSize.h"
#include "MIMETypeRegistry.h"
#include "Model.h"
#include "ModelPlayer.h"
#include "ModelPlayerProvider.h"
Expand Down Expand Up @@ -100,23 +101,37 @@ RefPtr<Model> HTMLModelElement::model() const
return m_model;
}

void HTMLModelElement::sourcesChanged()
static bool isSupportedModelType(const AtomString& type)
{
if (!document().hasBrowsingContext()) {
setSourceURL(URL { });
return;
}
return type.isEmpty() || MIMETypeRegistry::isSupportedModelMIMEType(type);
}

URL HTMLModelElement::selectModelSource() const
{
// FIXME: This should probably work more like media element resource
// selection, where if a <source> element fails to load, an error event
// is dispatched to it, and we continue to try subsequent <source>s.

if (!document().hasBrowsingContext())
return { };

if (auto src = getNonEmptyURLAttribute(srcAttr); src.isValid())
return src;

for (auto& element : childrenOfType<HTMLSourceElement>(*this)) {
// FIXME: for now we use the first valid URL without looking at the mime-type.
auto url = element.getNonEmptyURLAttribute(HTMLNames::srcAttr);
if (url.isValid()) {
setSourceURL(url);
return;
}
if (!isSupportedModelType(element.attributeWithoutSynchronization(typeAttr)))
continue;

if (auto src = element.getNonEmptyURLAttribute(srcAttr); src.isValid())
return src;
}

setSourceURL(URL { });
return { };
}

void HTMLModelElement::sourcesChanged()
{
setSourceURL(selectModelSource());
}

void HTMLModelElement::setSourceURL(const URL& url)
Expand Down Expand Up @@ -366,11 +381,15 @@ bool HTMLModelElement::isInteractive() const
return hasAttributeWithoutSynchronization(HTMLNames::interactiveAttr);
}

void HTMLModelElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason reason)
void HTMLModelElement::parseAttribute(const QualifiedName& name, const AtomString& value)
{
HTMLElement::attributeChanged(name, oldValue, newValue, reason);
if (m_modelPlayer && name == HTMLNames::interactiveAttr)
m_modelPlayer->setInteractionEnabled(isInteractive());
if (name == srcAttr)
sourcesChanged();
else if (name == interactiveAttr) {
if (m_modelPlayer)
m_modelPlayer->setInteractionEnabled(isInteractive());
} else
HTMLElement::parseAttribute(name, value);
}

void HTMLModelElement::defaultEventHandler(Event& event)
Expand Down Expand Up @@ -699,6 +718,11 @@ bool HTMLModelElement::hasPresentationalHintsForAttribute(const QualifiedName& n
return HTMLElement::hasPresentationalHintsForAttribute(name);
}

bool HTMLModelElement::isURLAttribute(const Attribute& attribute) const
{
return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
}

}

#endif // ENABLE(MODEL_ELEMENT)
4 changes: 3 additions & 1 deletion Source/WebCore/Modules/model-element/HTMLModelElement.h
Expand Up @@ -120,6 +120,7 @@ class HTMLModelElement final : public HTMLElement, private CachedRawResourceClie
private:
HTMLModelElement(const QualifiedName&, Document&);

URL selectModelSource() const;
void setSourceURL(const URL&);
void modelDidChange();
void createModelPlayer();
Expand All @@ -132,7 +133,8 @@ class HTMLModelElement final : public HTMLElement, private CachedRawResourceClie

// DOM overrides.
void didMoveToNewDocument(Document& oldDocument, Document& newDocument) final;
void attributeChanged(const QualifiedName&, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason) final;
bool isURLAttribute(const Attribute&) const final;
void parseAttribute(const QualifiedName&, const AtomString&) final;

// StyledElement
bool hasPresentationalHintsForAttribute(const QualifiedName&) const final;
Expand Down
Expand Up @@ -32,6 +32,7 @@
] interface HTMLModelElement : HTMLElement {
[CEReactions=NotNeeded, Reflect] attribute unsigned long width;
[CEReactions=NotNeeded, Reflect] attribute unsigned long height;
[CEReactions=NotNeeded, Reflect, URL] attribute USVString src;
[URL] readonly attribute USVString currentSrc;

readonly attribute boolean complete;
Expand Down