Skip to content

Commit

Permalink
Add support for <model src> and honor <source type> attributes
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=248855
rdar://problem/103056552

Reviewed by Antoine Quint.

* LayoutTests/model-element/model-element-source-expected.txt:
* LayoutTests/model-element/model-element-source.html:
* Source/WebCore/Modules/model-element/HTMLModelElement.cpp:
(WebCore::isSupportedModelType):
(WebCore::HTMLModelElement::selectModelSource const):
(WebCore::HTMLModelElement::sourcesChanged):
(WebCore::HTMLModelElement::parseAttribute):
(WebCore::HTMLModelElement::isURLAttribute const):
(WebCore::HTMLModelElement::attributeChanged): Deleted.
* Source/WebCore/Modules/model-element/HTMLModelElement.h:
* Source/WebCore/Modules/model-element/HTMLModelElement.idl:

Canonical link: https://commits.webkit.org/257518@main
  • Loading branch information
heycam committed Dec 7, 2022
1 parent ac1c9d9 commit 7d493da
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 18 deletions.
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
1 change: 1 addition & 0 deletions Source/WebCore/Modules/model-element/HTMLModelElement.idl
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

0 comments on commit 7d493da

Please sign in to comment.