blob: 53fe9548cb31bcdd1f20cecf2e891f90c8f90581 [file] [log] [blame]
/*
* Copyright (C) 2015, 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "JSCustomElementsRegistry.h"
#include "CustomElementsRegistry.h"
#include "Document.h"
#include "HTMLNames.h"
#include "JSCustomElementInterface.h"
#include "JSDOMBinding.h"
#include "JSDOMConvert.h"
using namespace JSC;
namespace WebCore {
#if ENABLE(CUSTOM_ELEMENTS)
static JSObject* getCustomElementCallback(ExecState& state, JSObject& prototype, const Identifier& id)
{
JSValue callback = prototype.get(&state, id);
if (state.hadException())
return nullptr;
if (callback.isUndefined())
return nullptr;
if (!callback.isFunction()) {
throwTypeError(&state, ASCIILiteral("A custom element callback must be a function"));
return nullptr;
}
return callback.getObject();
}
// https://html.spec.whatwg.org/#dom-customelementsregistry-define
JSValue JSCustomElementsRegistry::define(ExecState& state)
{
if (UNLIKELY(state.argumentCount() < 2))
return state.vm().throwException(&state, createNotEnoughArgumentsError(&state));
AtomicString localName(state.uncheckedArgument(0).toString(&state)->toAtomicString(&state));
if (UNLIKELY(state.hadException()))
return jsUndefined();
JSValue constructorValue = state.uncheckedArgument(1);
if (!constructorValue.isConstructor())
return throwTypeError(&state, ASCIILiteral("The second argument must be a constructor"));
JSObject* constructor = constructorValue.getObject();
// FIXME: Throw a TypeError if constructor doesn't inherit from HTMLElement.
// https://github.com/w3c/webcomponents/issues/541
switch (Document::validateCustomElementName(localName)) {
case CustomElementNameValidationStatus::Valid:
break;
case CustomElementNameValidationStatus::ConflictsWithBuiltinNames:
return throwSyntaxError(&state, ASCIILiteral("Custom element name cannot be same as one of the builtin elements"));
case CustomElementNameValidationStatus::NoHyphen:
return throwSyntaxError(&state, ASCIILiteral("Custom element name must contain a hyphen"));
case CustomElementNameValidationStatus::ContainsUpperCase:
return throwSyntaxError(&state, ASCIILiteral("Custom element name cannot contain an upper case letter"));
}
// FIXME: Check re-entrancy here.
// https://github.com/w3c/webcomponents/issues/545
CustomElementsRegistry& registry = wrapped();
if (registry.findInterface(localName)) {
throwNotSupportedError(state, ASCIILiteral("Cannot define multiple custom elements with the same tag name"));
return jsUndefined();
}
if (registry.containsConstructor(constructor)) {
throwNotSupportedError(state, ASCIILiteral("Cannot define multiple custom elements with the same class"));
return jsUndefined();
}
auto& vm = globalObject()->vm();
JSValue prototypeValue = constructor->get(&state, vm.propertyNames->prototype);
if (state.hadException())
return jsUndefined();
if (!prototypeValue.isObject())
return throwTypeError(&state, ASCIILiteral("Custom element constructor's prototype must be an object"));
JSObject& prototypeObject = *asObject(prototypeValue);
QualifiedName name(nullAtom, localName, HTMLNames::xhtmlNamespaceURI);
auto elementInterface = JSCustomElementInterface::create(name, constructor, globalObject());
auto* connectedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "connectedCallback"));
if (state.hadException())
return jsUndefined();
if (connectedCallback)
elementInterface->setConnectedCallback(connectedCallback);
auto* disconnectedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "disconnectedCallback"));
if (state.hadException())
return jsUndefined();
if (disconnectedCallback)
elementInterface->setDisconnectedCallback(disconnectedCallback);
// FIXME: Add the support for adoptedCallback.
getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "adoptedCallback"));
if (state.hadException())
return jsUndefined();
auto* attributeChangedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "attributeChangedCallback"));
if (state.hadException())
return jsUndefined();
if (attributeChangedCallback) {
auto value = convertOptional<Vector<String>>(state, constructor->get(&state, Identifier::fromString(&state, "observedAttributes")));
if (state.hadException())
return jsUndefined();
if (value)
elementInterface->setAttributeChangedCallback(attributeChangedCallback, *value);
}
PrivateName uniquePrivateName;
globalObject()->putDirect(vm, uniquePrivateName, constructor);
registry.addElementDefinition(WTFMove(elementInterface));
// FIXME: 17. Let map be registry's upgrade candidates map.
// FIXME: 18. Upgrade a newly-defined element given map and definition.
// FIXME: 19. Resolve whenDefined promise.
return jsUndefined();
}
#endif
}