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

Feat/support querySelector and querySelectorAll #672

Merged
merged 33 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
59ece31
feat: add querySelect polyfill.
answershuto Sep 10, 2021
3b9c49d
test: add querySelector test.
answershuto Sep 10, 2021
f29bcb0
test: add querySelectorAll test.
answershuto Sep 10, 2021
189692f
fix: delete invalid code.
answershuto Sep 10, 2021
366ef03
chore: remove console.log
answershuto Sep 10, 2021
d0e8649
test: modify id of test.
answershuto Sep 10, 2021
b8f63e7
chore: modify type.
answershuto Sep 10, 2021
313c39c
Merge branch 'main' into feat/support_querySelector
answershuto Sep 13, 2021
4a6458a
feat: add querySelector.ts.
answershuto Sep 13, 2021
f10eb73
chore: modify comments.
answershuto Sep 13, 2021
3730949
chore: delete useless util function.
answershuto Sep 14, 2021
24bbdff
feat: add new file to index .
answershuto Sep 14, 2021
3982398
feat: modify querySelector.
answershuto Sep 14, 2021
e968047
feat: add getElementsByClassName.
answershuto Sep 15, 2021
6c1b38b
chore: delete comments.
answershuto Sep 15, 2021
fdba750
feat: className property should be save in attributes.
answershuto Sep 16, 2021
0d08b9e
test: add test for getElementsByClassName
answershuto Sep 16, 2021
a71c4c0
test: add test for querySelector to find className.
answershuto Sep 16, 2021
3e8b490
fix: getElementsByTagName should work with *.
answershuto Sep 16, 2021
888445c
fix: className shoud be class when set property of element.
answershuto Sep 16, 2021
82c54a2
test: add test for getElementsByTagName of *.
answershuto Sep 16, 2021
0100c1c
chore: add new line.
answershuto Sep 16, 2021
afdce2a
test: add querySelectorAll for query attrs.
answershuto Sep 16, 2021
f3a52a2
test: add test for query href of querySelectorAll
answershuto Sep 16, 2021
a7fc280
test: add test for querySelector when query href.
answershuto Sep 16, 2021
d23590b
test: add test for absolute path of querySelector.
answershuto Sep 16, 2021
287d9ba
fix: misjudged the URL to be class.
answershuto Sep 16, 2021
eb355a2
fix: modify select class.
answershuto Sep 16, 2021
b2c5b8d
feat: modify querySelector.ts to query-selector.ts
answershuto Sep 22, 2021
34522ac
chore: delete semicolon.
answershuto Sep 22, 2021
affec47
chore: modify the format.
answershuto Sep 22, 2021
a1f9413
chore: modify the format.
answershuto Sep 23, 2021
8ecd064
test: modify query-select to a separate file.
answershuto Sep 23, 2021
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
38 changes: 36 additions & 2 deletions bridge/bindings/jsc/DOM/document.cc
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,45 @@ JSValueRef JSDocument::getElementsByTagName(JSContextRef ctx, JSObjectRef functi
std::transform(tagName.begin(), tagName.end(), tagName.begin(), ::toupper);

std::vector<ElementInstance *> elements;

traverseNode(document->documentElement, [tagName, &elements](NodeInstance *node) {
if (node->nodeType == NodeType::ELEMENT_NODE) {
auto element = reinterpret_cast<ElementInstance *>(node);
if (element->tagName() == tagName) {
if (element->tagName() == tagName || tagName == "*") {
elements.emplace_back(element);
}
}

return false;
});

JSValueRef elementArguments[elements.size()];

for (int i = 0; i < elements.size(); i++) {
elementArguments[i] = elements[i]->object;
}

return JSObjectMakeArray(ctx, elements.size(), elementArguments, exception);
}

JSValueRef JSDocument::getElementsByClassName(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argumentCount, const JSValueRef *arguments, JSValueRef *exception) {
if (argumentCount < 1) {
throwJSError(ctx,
"Uncaught TypeError: Failed to execute 'getElementsByClassName' on 'Document': 1 argument required, "
"but only 0 present.",
exception);
return nullptr;
}

auto document = reinterpret_cast<DocumentInstance *>(JSObjectGetPrivate(thisObject));
JSStringRef classNameStringRef = JSValueToStringCopy(ctx, arguments[0], exception);
std::string className = JSStringToStdString(classNameStringRef);
std::vector<ElementInstance *> elements;

traverseNode(document->documentElement, [ctx, exception, className, &elements](NodeInstance *node) {
if (node->nodeType == NodeType::ELEMENT_NODE) {
auto element = reinterpret_cast<ElementInstance *>(node);
if(element->classNames().containsAll(className)) {
elements.emplace_back(element);
}
}
Expand Down
19 changes: 19 additions & 0 deletions bridge/bindings/jsc/DOM/element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ JSValueRef ElementInstance::getProperty(std::string &name, JSValueRef *exception
JSElement::ElementProperty &property = propertyMap[name];

switch (property) {
case JSElement::ElementProperty::className: {
std::string str = "class";
JSValueRef classRef = (*m_attributes)->getProperty(str, exception);
return classRef;
break;
}
case JSElement::ElementProperty::nodeName:
case JSElement::ElementProperty::tagName: {
return JSValueMakeString(_hostClass->ctx, JSStringCreateWithUTF8CString(tagName().c_str()));
Expand Down Expand Up @@ -322,6 +328,11 @@ bool ElementInstance::setProperty(std::string &name, JSValueRef value, JSValueRe
auto &property = propertyMap[name];

switch (property) {
case JSElement::ElementProperty::className: {
std::string str = "class";
(*m_attributes)->setProperty(str, value, exception);
break;
}
case JSElement::ElementProperty::style:
case JSElement::ElementProperty::attributes:
return false;
Expand Down Expand Up @@ -756,6 +767,14 @@ void ElementInstance::setAttributes(JSHostObjectHolder<JSElementAttributes> &att
m_attributes = JSHostObjectHolder<JSElementAttributes>(attributes);
}

SpaceSplitString ElementInstance::classNames() {
std::string str = "className";
JSValueRef classValueRef = getProperty(str, nullptr);
JSStringRef classStringRef = JSValueToStringCopy(ctx, classValueRef, nullptr);
std::string strClass = JSStringToStdString(classStringRef);
return SpaceSplitString(strClass);
}

void ElementInstance::internalSetTextContent(JSStringRef content, JSValueRef *exception) {
auto node = firstChild();
while (node != nullptr) {
Expand Down
73 changes: 70 additions & 3 deletions bridge/include/kraken_bridge_jsc.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,68 @@ class MouseEventInstance;
struct NativePopStateEvent;
class PopStateEventInstance;

class SpaceSplitString {
public:
SpaceSplitString() = default;
SpaceSplitString(std::string& string) {
set(string);
}

void set(std::string &string) {
size_t pos = 0;
std::string token;
std::string s = string;
while ((pos = s.find(m_delimiter)) != std::string::npos) {
token = s.substr(0, pos);
m_szData.push_back(token);
s.erase(0, pos + m_delimiter.length());
}
m_szData.push_back(s);
}

bool contains(std::string& string) {
for (std::string s : m_szData) {
if (s == string) {
return true;
}
}

return false;
}

bool containsAll(std::string string) {
std::vector<std::string> szData;
size_t pos = 0;
std::string token;
std::string s = string;

while ((pos = s.find(m_delimiter)) != std::string::npos) {
token = s.substr(0, pos);
szData.push_back(token);
s.erase(0, pos + m_delimiter.length());
}
szData.push_back(s);

bool flag = true;
for(std::string str : szData) {
bool isContains = false;
for(std::string data : m_szData) {
if (data == str) {
isContains = true;
break;
}
}
flag &= isContains;
}

return flag;
}

private:
std::string m_delimiter = " ";
std::vector<std::string> m_szData;
};

class JSContext {
public:
static std::vector<JSStaticFunction> globalFunctions;
Expand Down Expand Up @@ -661,6 +723,9 @@ class JSDocument : public JSNode {
static JSValueRef getElementsByTagName(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception);

static JSValueRef getElementsByClassName(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception);

private:
protected:
JSDocument() = delete;
Expand All @@ -674,6 +739,7 @@ class JSDocument : public JSNode {
JSFunctionHolder m_createComment{context, prototypeObject, this, "createComment", createComment};
JSFunctionHolder m_getElementById{context, prototypeObject, this, "getElementById", getElementById};
JSFunctionHolder m_getElementsByTagName{context, prototypeObject, this, "getElementsByTagName", getElementsByTagName};
JSFunctionHolder m_getElementsByClassName{context, prototypeObject, this, "getElementsByClassName", getElementsByClassName};
};

class DocumentCookie {
Expand All @@ -697,7 +763,7 @@ struct NativeDocument {
class DocumentInstance : public NodeInstance {
public:
DEFINE_OBJECT_PROPERTY(Document, 4, nodeName, all, cookie, documentElement);
DEFINE_PROTOTYPE_OBJECT_PROPERTY(Document, 6, createElement, createTextNode, createComment, getElementById,
DEFINE_PROTOTYPE_OBJECT_PROPERTY(Document, 7, createElement, createTextNode, createComment, getElementById, getElementsByClassName,
getElementsByTagName, createEvent);

static DocumentInstance *instance(JSContext *context);
Expand Down Expand Up @@ -813,9 +879,9 @@ using ElementCreator = ElementInstance *(*)(JSContext *context);

class KRAKEN_EXPORT JSElement : public JSNode {
public:
DEFINE_OBJECT_PROPERTY(Element, 17, style, attributes, nodeName, tagName, offsetLeft, offsetTop, offsetWidth,
DEFINE_OBJECT_PROPERTY(Element, 18, style, attributes, nodeName, tagName, offsetLeft, offsetTop, offsetWidth,
offsetHeight, clientWidth, clientHeight, clientTop, clientLeft, scrollTop, scrollLeft,
scrollHeight, scrollWidth, children);
scrollHeight, scrollWidth, children, className);

DEFINE_PROTOTYPE_OBJECT_PROPERTY(Element, 10, getBoundingClientRect, getAttribute, setAttribute, hasAttribute,
removeAttribute, toBlob, click, scroll, scrollBy, scrollTo);
Expand Down Expand Up @@ -890,6 +956,7 @@ class KRAKEN_EXPORT ElementInstance : public NodeInstance {
JSHostClassHolder& getStyle();
void setStyle(JSHostClassHolder& style);
void setAttributes(JSHostObjectHolder<JSElementAttributes>& attributes);
SpaceSplitString classNames();

NativeElement *nativeElement{nullptr};

Expand Down
1 change: 1 addition & 0 deletions bridge/polyfill/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'es6-promise/dist/es6-promise.auto';
import './dom';
import './query-selector';
import { console } from './console';
import { WebSocket } from './websocket';
import { fetch, Request, Response, Headers } from './fetch';
Expand Down
175 changes: 175 additions & 0 deletions bridge/polyfill/src/query-selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
function fetchSelector(str: string, regex: RegExp) {
return {
selectors: str.match(regex) || [],
ruleStr: str.replace(regex, ' ')
};
}

function getElementsBySelector(selector: string): Array<Element | null | HTMLElement> {
let context = document;
let temp, tempElements: Array<Element> = [], elements: Array<Element> = [];
selector = selector.trim();

// If selector starts with *, find all elements.
if (selector.charAt(0) === '*') {
let temps: HTMLCollectionOf<Element> = context.getElementsByTagName('*');
tempElements = Array.from(temps);
}

// Classes. e.g. .row.
let classes: Array<string> = [];
selector = selector.split(' ').map(item => {
if (item && item.charAt(0) === '.') {
temp = fetchSelector(selector, /\.[\w-_]+/g);
classes = classes.concat(temp.selectors)
return temp.ruleStr;
}
return item;
}).join(' ');

// Ids. e.g. #mail-title.
temp = fetchSelector(selector, /#[\w-_]+/g);
let id = temp.selectors ? temp.selectors[0] : null;
selector = temp.ruleStr;

// Attributes. e.g. [rel=external].
temp = fetchSelector(selector, /\[.+?\]/g);
Copy link
Contributor

@temper357 temper357 Sep 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

只支持 equal,正则是不是限定下 /[.+=.+]/g

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

讨论了下这个正则没问题,去掉TODO。

let attributes = temp.selectors;
selector = temp.ruleStr;

// Elements. e.g. header, div.
temp = fetchSelector(selector, /\w+/g);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

讨论了下这个正则没问题。

let els = temp.selectors;
selector = temp.ruleStr;

// Get by id.
// Id is supposed to be unique.
// More need to attach other selectors.
if (id) {
id = id.substring(1);
return [document.getElementById(id) || null];
}

// Get by Elements.
if (els.length !== 0) {
let temps: HTMLCollectionOf<Element> = context.getElementsByTagName(els[0]);
tempElements = tempElements.concat(Array.from(temps));
}

// Get by class name.
for (let i = 0, l = classes.length; i !== l; ++i) {
let className = classes[i].substring(1);
let temps: HTMLCollectionOf<Element> = context.getElementsByClassName(className);
let arrTemps: Array<Element> = Array.from(temps);
if (tempElements.length === 0) {
// If no temp elements yet, push into tempElements directly.
tempElements = tempElements.concat(arrTemps);
}
else {
// Otherwise, find intersection.
let prevs: Array<Element> = [];
prevs = prevs.concat(tempElements);
tempElements = [];

for (let index = 0; index < arrTemps.length; index++) {
let t = arrTemps[index];
if (prevs.indexOf(t) !== -1) {
tempElements = tempElements.concat([t]);
}
}
}
}

// Get by attributes.
if (attributes.length !== 0) {
let attrs = {};
for (let i = 0; i < attributes.length; i++) {
let attribute = attributes[i];
attribute = attribute.substring(1, attribute.length - 1);
let parts: Array<string> = (attribute.split('=')).map(item => item.trim());
if (parts[1]) {
parts[1] = parts[1].substring(1, parts[1].length - 1);
}
attrs[parts[0]] = parts[1];
}
let prevs: Array<Element> = [];
prevs = prevs.concat(tempElements);
tempElements = [];

for (let i = 0, l = prevs.length; i !== l; ++i) {
let t = prevs[i];
let shouldAdd = true;
for (let key in attrs) {
let lastChar = key.charAt(key.length - 1);
if (/[\^\*\$]$/.test(key)) {
key = key.substring(0, key.length - 1);
}
let tempAttr = t.getAttribute(key) || '';
// Case: [href*=/en].
if (lastChar === '*' && tempAttr.indexOf(attrs[key + lastChar]) === -1) {
shouldAdd = false;
break;
}
// Case: [href^=/en].
else if (lastChar === '^' && tempAttr.indexOf(attrs[key + lastChar]) !== 0) {
shouldAdd = false;
break;
}
// Case: [href$=/en].
else if (lastChar === '$' &&
(tempAttr.lastIndexOf(attrs[key + lastChar]) === -1
? false
: tempAttr.lastIndexOf(attrs[key + lastChar]))
!==
tempAttr.length - attrs[key + lastChar].length) {
shouldAdd = false;
break;
}
// Case: [href=/en].
else if (/[\$\*\^]/.test(lastChar) === false && tempAttr !== attrs[key]) {
shouldAdd = false;
break;
}

}

if (shouldAdd) {
tempElements = tempElements.concat([t]);
}
}
}

elements = elements.concat(tempElements);
return elements;
}

document.querySelectorAll = function <E extends Element = Element>(selector: string): NodeListOf<E> {
if (typeof selector !== 'string') {
throw new TypeError('document.querySelectorAll: Invalid selector type. ' +
'Expect: string. Found: ' + typeof selector + '.');
}
let elements: Array<E> = [];

// Split `selector` into rules by `,`.
let rules: Array<string> = selector.split(',').map(item => item.trim());

// Iterate through each rule.
// For the sake of performance, use for-loop here rather than forEach.
for (let i = 0, l = rules.length; i !== l; ++i) {
let rule = rules[i];

// TODO: support ' ' and '>'.
elements = elements.concat(getElementsBySelector.call(this, rule));
}

return (elements as any) as NodeListOf<E>;
};

document.querySelector = function (selector: string): Element | null {
if (typeof selector !== 'string') {
throw new TypeError('document.querySelector: Invalid selector type. ' +
'Expect: string. Found: ' + typeof selector + '.');
}
let elements = this.querySelectorAll(selector);
return elements.length > 0 ? elements[0] : null;
}