Skip to content

Commit

Permalink
refactor: remove element manager (openkraken#940)
Browse files Browse the repository at this point in the history
* refactor: remove element manager

* chore: fix error

* Committing clang-format changes

* fix: reset dom

* fix: lint

* fix: bridge error

* Committing clang-format changes

* fix: document.body

* fix: ref error

* fix: reset html

* fix: specs

* chore: update plugin deps version

* chore: export defineElement api

* chore: change context is optional

* chore: export comment node

* chore: update deps plugins

* chore: remove performance overlay

* fix: img snapshot

Co-authored-by: openkraken-bot <[email protected]>
  • Loading branch information
yuanyan and openkraken-bot authored Dec 9, 2021
1 parent 6885388 commit fdc1f62
Show file tree
Hide file tree
Showing 59 changed files with 1,139 additions and 1,289 deletions.
179 changes: 158 additions & 21 deletions bridge/bindings/qjs/dom/document.cc
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,18 @@ JSValue Document::createTextNode(QjsContext* ctx, JSValue this_val, int argc, JS
JSValue textNode = JS_CallConstructor(ctx, TextNode::instance(document->m_context)->classObject, argc, argv);
return textNode;
}

JSValue Document::createDocumentFragment(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
return JS_CallConstructor(ctx, DocumentFragment::instance(document->m_context)->classObject, 0, nullptr);
}

JSValue Document::createComment(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
JSValue commentNode = JS_CallConstructor(ctx, Comment::instance(document->m_context)->classObject, argc, argv);
return commentNode;
}

JSValue Document::getElementById(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'getElementById' on 'Document': 1 argument required, but only 0 present.");
Expand Down Expand Up @@ -219,6 +222,7 @@ JSValue Document::getElementById(QjsContext* ctx, JSValue this_val, int argc, JS

return JS_NULL;
}

JSValue Document::getElementsByTagName(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx,
Expand All @@ -233,9 +237,9 @@ JSValue Document::getElementsByTagName(QjsContext* ctx, JSValue this_val, int ar

std::vector<ElementInstance*> elements;

traverseNode(document->m_documentElement, [tagName, &elements](NodeInstance* node) {
traverseNode(document, [tagName, &elements](NodeInstance* node) {
if (node->nodeType == NodeType::ELEMENT_NODE) {
auto element = static_cast<ElementInstance*>(node);
auto* element = static_cast<ElementInstance*>(node);
if (element->tagName() == tagName || tagName == "*") {
elements.emplace_back(element);
}
Expand Down Expand Up @@ -264,7 +268,7 @@ JSValue Document::getElementsByClassName(QjsContext* ctx, JSValue this_val, int
std::string className = jsValueToStdString(ctx, argv[0]);

std::vector<ElementInstance*> elements;
traverseNode(document->m_documentElement, [ctx, className, &elements](NodeInstance* node) {
traverseNode(document, [ctx, className, &elements](NodeInstance* node) {
if (node->nodeType == NodeType::ELEMENT_NODE) {
auto element = reinterpret_cast<ElementInstance*>(node);
if (element->classNames()->containsAll(className)) {
Expand Down Expand Up @@ -303,21 +307,157 @@ bool Document::isCustomElement(const std::string& tagName) {
PROP_GETTER(DocumentInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
return JS_NewString(ctx, "#document");
}

PROP_SETTER(DocumentInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
return JS_NULL;
}

// document.documentElement
PROP_GETTER(DocumentInstance, documentElement)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
ElementInstance* documentElement = document->getDocumentElement();
return documentElement == nullptr ? JS_NULL : documentElement->instanceObject;
}

PROP_SETTER(DocumentInstance, documentElement)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
return JS_NULL;
}

// document.head
PROP_GETTER(DocumentInstance, head)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
ElementInstance* documentElement = document->getDocumentElement();
int32_t len = arrayGetLength(ctx, documentElement->childNodes);
JSValue head = JS_NULL;
if (documentElement != nullptr) {
for (int i = 0; i < len; i++) {
JSValue v = JS_GetPropertyUint32(ctx, documentElement->childNodes, i);
auto* nodeInstance = static_cast<NodeInstance*>(JS_GetOpaque(v, Node::classId(v)));
if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) {
auto* elementInstance = static_cast<ElementInstance*>(nodeInstance);
if (elementInstance->tagName() == "HEAD") {
head = elementInstance->instanceObject;
break;
}
}
JS_FreeValue(ctx, v);
}

JS_FreeValue(ctx, documentElement->instanceObject);
}

return head;
}

PROP_SETTER(DocumentInstance, head)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
return JS_NULL;
}

// document.body: https://html.spec.whatwg.org/multipage/dom.html#dom-document-body-dev
PROP_GETTER(DocumentInstance, body)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
ElementInstance* documentElement = document->getDocumentElement();
JSValue body = JS_NULL;

if (documentElement != nullptr) {
int32_t len = arrayGetLength(ctx, documentElement->childNodes);
// The body element of a document is the first of the html documentElement's children that
// is either a body element or a frameset element, or null if there is no such element.
for (int i = 0; i < len; i++) {
JSValue v = JS_GetPropertyUint32(ctx, documentElement->childNodes, i);
auto* nodeInstance = static_cast<NodeInstance*>(JS_GetOpaque(v, Node::classId(v)));
if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) {
auto* elementInstance = static_cast<ElementInstance*>(nodeInstance);
if (elementInstance->tagName() == "BODY") {
body = elementInstance->instanceObject;
break;
}
}
JS_FreeValue(ctx, v);
}
JS_FreeValue(ctx, documentElement->instanceObject);
}
return body;
}

// The body property is settable, setting a new body on a document will effectively remove all
// the current children of the existing <body> element.
PROP_SETTER(DocumentInstance, body)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
ElementInstance* documentElement = document->getDocumentElement();
// If there is no document element, throw a Exception.
if (documentElement == nullptr) {
return JS_ThrowInternalError(ctx, "No document element exists");
}
JSValue result = JS_NULL;
JSValue newBody = argv[0];
// If the body element is not null, then replace the body element with the new value within the body element's parent and return.
if (JS_IsInstanceOf(ctx, newBody, Element::instance(document->m_context)->classObject)) {
auto* newElementInstance = static_cast<ElementInstance*>(JS_GetOpaque(newBody, Element::classId()));
// If the new value is not a body element, then throw a Exception.
if (newElementInstance->tagName() == "BODY") {
JSValue oldBody = JS_GetPropertyStr(ctx, document->instanceObject, "body");
if (JS_VALUE_GET_PTR(oldBody) != JS_VALUE_GET_PTR(newBody)) {
// If the new value is the same as the body element.
if (JS_IsNull(oldBody)) {
// The old body element is null, but there's a document element. Append the new value to the document element.
documentElement->internalAppendChild(newElementInstance);
} else {
// Otherwise, replace the body element with the new value within the body element's parent.
auto* oldElementInstance = static_cast<ElementInstance*>(JS_GetOpaque(oldBody, Element::classId()));
documentElement->internalReplaceChild(newElementInstance, oldElementInstance);
}
}
JS_FreeValue(ctx, oldBody);
result = JS_DupValue(ctx, newBody);
} else {
result = JS_ThrowTypeError(ctx, "The new body element must be a 'BODY' element");
}
} else {
result = JS_ThrowTypeError(ctx, "The 1st argument provided is either null, or an invalid HTMLElement");
}

JS_FreeValue(ctx, documentElement->instanceObject);
return result;
}

// document.children
PROP_GETTER(DocumentInstance, children)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
JSValue array = JS_NewArray(ctx);
JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push");

int32_t len = arrayGetLength(ctx, document->childNodes);
for (int i = 0; i < len; i++) {
JSValue v = JS_GetPropertyUint32(ctx, document->childNodes, i);
auto* instance = static_cast<NodeInstance*>(JS_GetOpaque(v, Node::classId(v)));
if (instance->nodeType == NodeType::ELEMENT_NODE) {
JSValue arguments[] = {v};
JS_Call(ctx, pushMethod, array, 1, arguments);
}
JS_FreeValue(ctx, v);
}

JS_FreeValue(ctx, pushMethod);
return array;
}

PROP_SETTER(DocumentInstance, children)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
return JS_NULL;
}

PROP_GETTER(DocumentInstance, all)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
auto all = new AllCollection(document->m_context);

traverseNode(document->m_documentElement, [&all](NodeInstance* node) {
traverseNode(document, [&all](NodeInstance* node) {
all->internalAdd(node, nullptr);
return false;
});

return all->jsObject;
}

PROP_SETTER(DocumentInstance, all)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
return JS_NULL;
}
Expand All @@ -327,6 +467,7 @@ PROP_GETTER(DocumentInstance, cookie)(QjsContext* ctx, JSValue this_val, int arg
std::string cookie = document->m_cookie->getCookie();
return JS_NewString(ctx, cookie.c_str());
}

PROP_SETTER(DocumentInstance, cookie)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* document = static_cast<DocumentInstance*>(JS_GetOpaque(this_val, Document::classId()));
std::string value = jsValueToStdString(ctx, argv[0]);
Expand Down Expand Up @@ -391,22 +532,7 @@ DocumentInstance::DocumentInstance(Document* document) : NodeInstance(document,
m_instanceMap[Document::instance(m_context)] = this;
m_eventTargetId = DOCUMENT_TARGET_ID;

JSAtom htmlTagName = JS_NewAtom(m_ctx, "HTML");
JSValue htmlTagValue = JS_AtomToValue(m_ctx, htmlTagName);
JSValue htmlArgs[] = {htmlTagValue};
JSValue documentElementValue = JS_CallConstructor(m_ctx, Element::instance(m_context)->classObject, 1, htmlArgs);
m_documentElement = static_cast<ElementInstance*>(JS_GetOpaque(documentElementValue, Element::classId()));
m_documentElement->parentNode = JS_DupValue(m_ctx, instanceObject);

JSAtom documentElementTag = JS_NewAtom(m_ctx, "documentElement");
JS_SetProperty(m_ctx, instanceObject, documentElementTag, documentElementValue);

JS_FreeAtom(m_ctx, documentElementTag);
JS_FreeAtom(m_ctx, htmlTagName);
JS_FreeValue(m_ctx, htmlTagValue);

#if FLUTTER_BACKEND
getDartMethod()->initHTML(m_context->getContextId(), m_documentElement->nativeEventTarget);
getDartMethod()->initDocument(m_context->getContextId(), nativeEventTarget);
#endif
}
Expand Down Expand Up @@ -437,8 +563,19 @@ void DocumentInstance::addElementById(JSAtom id, ElementInstance* element) {
}
}

ElementInstance* DocumentInstance::documentElement() {
return m_documentElement;
ElementInstance* DocumentInstance::getDocumentElement() {
int32_t len = arrayGetLength(m_ctx, childNodes);

for (int i = 0; i < len; i++) {
JSValue v = JS_GetPropertyUint32(m_ctx, childNodes, i);
auto* instance = static_cast<NodeInstance*>(JS_GetOpaque(v, Node::classId(v)));
if (instance->nodeType == NodeType::ELEMENT_NODE) {
return static_cast<ElementInstance*>(instance);
}
JS_FreeValue(m_ctx, v);
}

return nullptr;
}

} // namespace kraken::binding::qjs
4 changes: 2 additions & 2 deletions bridge/bindings/qjs/dom/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ class DocumentInstance : public NodeInstance {
explicit DocumentInstance(Document* document);
~DocumentInstance();
static std::unordered_map<Document*, DocumentInstance*> m_instanceMap;
ElementInstance* documentElement();
static DocumentInstance* instance(Document* document) {
if (m_instanceMap.count(document) == 0) {
m_instanceMap[document] = new DocumentInstance(document);
Expand All @@ -86,10 +85,11 @@ class DocumentInstance : public NodeInstance {
}

private:
DEFINE_HOST_CLASS_PROPERTY(3, nodeName, all, cookie);
DEFINE_HOST_CLASS_PROPERTY(7, nodeName, documentElement, body, head, children, all, cookie);

void removeElementById(JSAtom id, ElementInstance* element);
void addElementById(JSAtom id, ElementInstance* element);
ElementInstance* getDocumentElement();
std::unordered_map<JSAtom, std::vector<ElementInstance*>> m_elementMapById;
ElementInstance* m_documentElement{nullptr};
std::unique_ptr<DocumentCookie> m_cookie;
Expand Down
13 changes: 4 additions & 9 deletions bridge/bindings/qjs/dom/element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,7 @@ JSValue Element::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue
return JS_CallConstructor(ctx, Document->getElementConstructor(context, name), argc, argv);
}

ElementInstance* element;
if (name == "HTML") {
element = new ElementInstance(this, name, false);
element->m_eventTargetId = HTML_TARGET_ID;
} else {
// Fallback to default Element class
element = new ElementInstance(this, name, true);
}

auto* element = new ElementInstance(this, name, true);
return element->instanceObject;
}

Expand Down Expand Up @@ -395,6 +387,7 @@ PROP_GETTER(ElementInstance, className)(QjsContext* ctx, JSValue this_val, int a
JSAtom valueAtom = element->m_attributes->getAttribute("class");
return JS_AtomToString(ctx, valueAtom);
}

PROP_SETTER(ElementInstance, className)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
auto* element = static_cast<ElementInstance*>(JS_GetOpaque(this_val, Element::classId()));
JSAtom atom = JS_ValueToAtom(ctx, argv[0]);
Expand Down Expand Up @@ -530,6 +523,7 @@ PROP_GETTER(ElementInstance, scrollWidth)(QjsContext* ctx, JSValue this_val, int
NativeValue args[] = {Native_NewInt32(static_cast<int32_t>(ViewModuleProperty::scrollWidth))};
return element->callNativeMethods("getViewModuleProperty", 1, args);
}

PROP_SETTER(ElementInstance, scrollWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
return JS_NULL;
}
Expand All @@ -550,6 +544,7 @@ PROP_GETTER(ElementInstance, firstElementChild)(QjsContext* ctx, JSValue this_va

return JS_NULL;
}

PROP_SETTER(ElementInstance, firstElementChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
return JS_NULL;
}
Expand Down
1 change: 1 addition & 0 deletions bridge/bindings/qjs/dom/event_target.cc
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ EventTargetInstance::~EventTargetInstance() {
foundation::UICommandBuffer::instance(m_contextId)->addCommand(m_eventTargetId, UICommand::disposeEventTarget, nullptr, false);
#if FLUTTER_BACKEND
getDartMethod()->flushUICommand();
delete nativeEventTarget;
#endif
}

Expand Down
9 changes: 6 additions & 3 deletions bridge/bindings/qjs/dom/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ JSValue Node::cloneNode(QjsContext* ctx, JSValue this_val, int argc, JSValue* ar
}
return JS_NULL;
}

JSValue Node::appendChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
if (argc != 1) {
return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first argument is required.");
Expand All @@ -96,7 +97,7 @@ JSValue Node::appendChild(QjsContext* ctx, JSValue this_val, int argc, JSValue*
return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first arguments should be an Node type.");
}

if (nodeInstance->m_eventTargetId == HTML_TARGET_ID || nodeInstance == selfInstance) {
if (nodeInstance == selfInstance) {
return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': The new child element contains the parent.");
}

Expand Down Expand Up @@ -143,6 +144,7 @@ JSValue Node::removeChild(QjsContext* ctx, JSValue this_val, int argc, JSValue*
auto removedNode = selfInstance->internalRemoveChild(nodeInstance);
return JS_DupValue(ctx, removedNode->instanceObject);
}

JSValue Node::insertBefore(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
if (argc < 2) {
return JS_ThrowTypeError(ctx, "Failed to execute 'insertBefore' on 'Node': 2 arguments is required.");
Expand Down Expand Up @@ -188,6 +190,7 @@ JSValue Node::insertBefore(QjsContext* ctx, JSValue this_val, int argc, JSValue*

return JS_NULL;
}

JSValue Node::replaceChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) {
if (argc < 2) {
return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'replaceChild' on 'Node': 2 arguments required");
Expand Down Expand Up @@ -369,11 +372,11 @@ PROP_SETTER(NodeInstance, textContent)(QjsContext* ctx, JSValue this_val, int ar
}

bool NodeInstance::isConnected() {
bool _isConnected = m_eventTargetId == HTML_TARGET_ID;
bool _isConnected = this == document();
auto parent = static_cast<NodeInstance*>(JS_GetOpaque(parentNode, Node::classId(parentNode)));

while (parent != nullptr && !_isConnected) {
_isConnected = parent->m_eventTargetId == HTML_TARGET_ID;
_isConnected = parent == document();
JSValue parentParentNode = parent->parentNode;
parent = static_cast<NodeInstance*>(JS_GetOpaque(parentParentNode, Node::classId(parentParentNode)));
}
Expand Down
9 changes: 2 additions & 7 deletions bridge/bindings/qjs/module_manager_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,8 @@ namespace kraken::binding::qjs {
TEST(ModuleManager, shouldThrowErrorWhenBadJSON) {
bool static errorCalled = false;
auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) {
EXPECT_STREQ(errmsg,
"TypeError: circular reference\n"
" at __kraken_invoke_module__ (native)\n"
" at <anonymous> (internal://:616)\n"
" at Promise (native)\n"
" at invokeMethod (internal://:617)\n"
" at <eval> (vm://:12)\n");
std::string stdErrorMsg = std::string(errmsg);
EXPECT_EQ(stdErrorMsg.find("TypeError: circular reference") != std::string::npos, true);
errorCalled = true;
});
kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {};
Expand Down
Loading

0 comments on commit fdc1f62

Please sign in to comment.