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

Implement FormData. #645

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.vscode
.history
.cache
*.log
*.apk
*.ap_
Expand Down Expand Up @@ -48,6 +49,8 @@ build/
*~
*.swp

**/vendor/**

# NDK
obj/
yarn.lock
Expand Down
3 changes: 3 additions & 0 deletions bridge/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
# Core sources
webf_bridge.cc
core/api/api.cc
core/api/form_data.cc
core/api/form_data_part.cc
core/executing_context.cc
core/script_forbidden_scope.cc
core/script_state.cc
Expand Down Expand Up @@ -412,6 +414,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
out/qjs_window.cc
out/qjs_location.cc
out/qjs_blob.cc
out/qjs_form_data.cc
out/qjs_event.cc
out/qjs_add_event_listener_options.cc
out/qjs_event_listener_options.cc
Expand Down
3 changes: 3 additions & 0 deletions bridge/bindings/qjs/binding_initializer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "qjs_animation_event.h"
#include "qjs_blob.h"
#include "qjs_form_data.h"
#include "qjs_bounding_client_rect.h"
#include "qjs_canvas_gradient.h"
#include "qjs_canvas_pattern.h"
Expand Down Expand Up @@ -200,6 +201,8 @@ void InstallBindings(ExecutingContext* context) {
QJSSVGStyleElement::Install(context);
QJSSVGLineElement::Install(context);

QJSFormData::Install(context);

// Legacy bindings, not standard.
QJSElementAttributes::Install(context);
}
Expand Down
32 changes: 32 additions & 0 deletions bridge/bindings/qjs/converter_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#ifndef BRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_
#define BRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_

#include <memory>
#include <type_traits>
#include "atomic_string.h"
#include "bindings/qjs/union_base.h"
Expand All @@ -14,6 +15,7 @@
#include "core/dom/events/event_target.h"
#include "core/dom/node_list.h"
#include "core/fileapi/blob_part.h"
#include "core/api/form_data_part.h"
#include "core/fileapi/blob_property_bag.h"
#include "core/frame/window.h"
#include "core/html/html_body_element.h"
Expand Down Expand Up @@ -384,6 +386,30 @@ struct Converter<IDLCallback> : public ConverterBase<IDLCallback> {
}
};

template <>
struct Converter<FormDataPart> : public ConverterBase<FormDataPart> {
using ImplType = FormDataPart::ImplType;
static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) {
assert(!JS_IsException(value));
return FormDataPart::Create(ctx, value, exception_state);
}

static JSValue ToValue(JSContext* ctx, ImplType data) {
if (data == nullptr)
return JS_NULL;

return data->ToQuickJS(ctx);
}

static JSValue ToValue(JSContext* ctx, FormDataPart* data) {
if (data == nullptr)
return JS_NULL;

return data->ToQuickJS(ctx);
}
};


template <>
struct Converter<BlobPart> : public ConverterBase<BlobPart> {
using ImplType = BlobPart::ImplType;
Expand All @@ -398,6 +424,12 @@ struct Converter<BlobPart> : public ConverterBase<BlobPart> {

return data->ToQuickJS(ctx);
}
static JSValue ToValue(JSContext* ctx, std::shared_ptr<BlobPart> data) {
if (data == nullptr)
return JS_NULL;

return data->ToQuickJS(ctx);
}
};

template <>
Expand Down
2 changes: 1 addition & 1 deletion bridge/bindings/qjs/exception_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace webf {

enum ErrorType { TypeError, InternalError, RangeError, ReferenceError, SyntaxError };
enum ErrorType { TypeError, InternalError, RangeError, ReferenceError, SyntaxError,ArgumentError };

// ExceptionState is a scope-like class and provides a way to store an exception.
class ExceptionState {
Expand Down
14 changes: 11 additions & 3 deletions bridge/bindings/qjs/member_installer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "member_installer.h"
#include <quickjs/quickjs.h>
#include <string>
#include "core/executing_context.h"
#include "qjs_engine_patch.h"

Expand Down Expand Up @@ -67,14 +68,21 @@ void MemberInstaller::InstallAttributes(ExecutingContext* context,
}
}
}

// Defined a placeholder name in FormData to avoid using C++ keyword **delete**.
const char* fn_form_data_delete="form_data_delete";
void MemberInstaller::InstallFunctions(ExecutingContext* context,
JSValue root,
std::initializer_list<FunctionConfig> config) {
JSContext* ctx = context->ctx();
for (auto& c : config) {
JSValue function = JS_NewCFunction(ctx, c.function, c.name, c.length);
JS_DefinePropertyValueStr(ctx, root, c.name, function, c.flag);
std::string name = c.name;

// replace the placeholder name to real one.
if(c.name==fn_form_data_delete){
name = "delete";
}
JSValue function = JS_NewCFunction(ctx, c.function, name.c_str(), c.length);
JS_DefinePropertyValueStr(ctx, root, name.c_str(), function, c.flag);
}
}

Expand Down
5 changes: 5 additions & 0 deletions bridge/bindings/qjs/wrapper_type_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ enum {
JS_CLASS_SVG_LENGTH,
JS_CLASS_SVG_ANIMATED_LENGTH,

//
JS_CLASS_FORM_DATA,

JS_CLASS_CUSTOM_CLASS_INIT_COUNT /* last entry for predefined classes */


};

// Callback when get property using index.
Expand Down
193 changes: 193 additions & 0 deletions bridge/core/api/form_data.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright (C) 2022-present The WebF authors. All rights reserved.
*/

#include "form_data.h"
#include <memory>
#include "bindings/qjs/atomic_string.h"
#include "core/api/form_data_part.h"
#include "core/executing_context.h"
#include "core/fileapi/blob_part.h"

namespace webf {
const char* className= "FormData";
FormData* FormData::Create(ExecutingContext* context, ExceptionState& exception_state) {
return MakeGarbageCollected<FormData>(context->ctx());
}

FormData::FormData(ExecutingContext* context, NativeBindingObject* native_binding_object)
: BindingObject(context->ctx(), native_binding_object) {}

NativeValue FormData::HandleCallFromDartSide(const AtomicString& method,
int32_t argc,
const NativeValue* argv,
Dart_Handle dart_object) {
return Native_NewNull();
}

bool FormData::IsFormData() const {
return true;
}

void FormData::append(const AtomicString& name,
const std::shared_ptr<BlobPart>& value,
const AtomicString& fileName,
ExceptionState& exception_state) {
// 验证参数有效性
if (name.IsEmpty()) {
exception_state.ThrowException(ctx(), ErrorType::ArgumentError, "The name parameter must not be empty.");
return;
}

// 创建 FormDataPart 对象
auto form_data_part = std::make_shared<FormDataPart>(name.ToStdString(ctx()), value, fileName.ToStdString(ctx()));

// 添加数据
_parts.push_back(form_data_part);
}

void FormData::form_data_delete(const AtomicString& name, ExceptionState& exception_state) {
// 验证参数有效性
if (name.IsEmpty()) {
exception_state.ThrowException(ctx(), ErrorType::ArgumentError, "The name parameter must not be empty.");
return;
}

// 删除数据
_parts.erase(std::remove_if(_parts.begin(), _parts.end(),
[name, this](const std::shared_ptr<FormDataPart>& part) {
return part->GetName() == name.ToStdString(ctx());
}),
_parts.end());
}

webf::BlobPart* FormData::get(const AtomicString& name, ExceptionState& exception_state) {
// 验证参数有效性
if (name.IsEmpty()) {
exception_state.ThrowException(ctx(), ErrorType::ArgumentError, "The name parameter must not be empty.");
return nullptr;
}

// 查找数据
for (const auto& part : _parts) {
if (part->GetName() == name.ToStdString(ctx())) {
return &*part->getFirst();
}
}

// 如果没有找到,则返回 nullptr
return nullptr;
}

std::vector<BlobPart::ImplType> FormData::getAll(const AtomicString& name, ExceptionState& exception_state) {
std::vector<BlobPart::ImplType> result;

// 验证参数有效性
if (name.IsEmpty()) {
exception_state.ThrowException(ctx(), ErrorType::ArgumentError, "The name parameter must not be empty.");
return result;
}

// 收集数据
for (const auto& part : _parts) {
if (part->GetName() == name.ToStdString(ctx())) {
for (const auto& value : part->GetValues()) {
result.push_back(std::make_shared<BlobPart>(value));
}
}
}

return result;
}

bool FormData::has(const AtomicString& name, ExceptionState& exception_state) {
// 验证参数有效性
if (name.IsEmpty()) {
exception_state.ThrowException(ctx(), ErrorType::ArgumentError, "The name parameter must not be empty.");
return false;
}

// 检查数据
for (const auto& part : _parts) {
if (part->GetName() == name.ToStdString(ctx())) {
return true;
}
}

return false;
}

void FormData::set(const AtomicString& name,
const std::shared_ptr<webf::BlobPart>& value,
const AtomicString& fileName,
ExceptionState& exception_state) {
// 验证参数有效性
if (name.IsEmpty()) {
exception_state.ThrowException(ctx(), ErrorType::ArgumentError, "The name parameter must not be empty.");
return;
}

// 移除已存在的相同名称的条目
_parts.erase(std::remove_if(_parts.begin(), _parts.end(),
[name, this](const std::shared_ptr<FormDataPart>& part) {
return part->GetName() == name.ToStdString(ctx());
}),
_parts.end());

// 创建 FormDataPart 对象
auto form_data_part = std::make_shared<FormDataPart>(name.ToStdString(ctx()), value, fileName.ToStdString(ctx()));

// 添加新的数据
_parts.push_back(form_data_part);
}

void FormData::forEach(const std::shared_ptr<QJSFunction>& callback,
const ScriptValue& thisArg,
ExceptionState& exception_state) {
if (!callback || !callback->IsFunction(ctx())) {
exception_state.ThrowException(ctx(), ErrorType::ArgumentError, "The callback function must be callable.");
return;
}
// callbackFn:(value: BlobPart, key: string, parent: FormData) => void
for (const auto& part : _parts) {
ScriptValue args[3];
/*value*/ args[0] = ScriptValue(ctx(), part->ToQuickJS(ctx()));
/*key*/ args[1] = ScriptValue(ctx(), AtomicString(ctx(), part->GetName()));

// TODO: which parent???
/*parent*/ args[2] = ScriptValue(ctx(), this->ToQuickJS());

// 调用回调函数
linsmod marked this conversation as resolved.
Show resolved Hide resolved
ScriptValue result = callback->Invoke(ctx(), thisArg, 3, args);
if (result.IsException()) {
exception_state.ThrowException(ctx(), result.QJSValue());
return;
}
}
}

// 实现 keys() 方法
std::vector<webf::AtomicString> FormData::keys(ExceptionState& exception_state) const {
std::vector<webf::AtomicString> keys;
for (const auto& part : _parts) {
keys.push_back(AtomicString(ctx(), part->GetName()));
}
return keys;
}

// 实现 values() 方法
std::vector<std::shared_ptr<BlobPart>> FormData::values(ExceptionState& exception_state) const {
std::vector<std::shared_ptr<BlobPart>> values;
for (const auto& part : _parts) {
for (const auto& value : part->GetValues()) {
values.push_back(std::make_shared<BlobPart>(value));
}
}
return values;
}

// 实现 entries() 方法
std::vector<std::shared_ptr<FormDataPart>> FormData::entries(ExceptionState& exception_state) const {
return std::vector<std::shared_ptr<FormDataPart>>(_parts.begin(), _parts.end());
}
} // namespace webf
21 changes: 21 additions & 0 deletions bridge/core/api/form_data.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2022-present The WebF authors. All rights reserved.
*/

// type FormDataEntryValue = File | string;
type FormDataPart={}
export interface FormData {
new():FormData;
append(name: string, value: BlobPart, fileName?: string): void;
Copy link
Member

Choose a reason for hiding this comment

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

  append(name: string, value: BlobPart, fileName?: string): void;
  append(name: string, value: string, fileName?: string): void;

Copy link
Author

Choose a reason for hiding this comment

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

BlobPart支持两者,而且内部也可以区分,似乎不必要创建两个函数?

Copy link
Member

Choose a reason for hiding this comment

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

重载需要写两个,这样生成器也会生成两个重载,来应对不同的参数传递方式

// This method name is a placeholder of **delete** method to avoid using C++ keyword
// and will be replaced to **delete** when installing in MemberInstaller::InstallFunctions.
form_data_delete(name: string): void;
Copy link
Member

Choose a reason for hiding this comment

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

unknown API

Copy link
Author

Choose a reason for hiding this comment

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

append: https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
delete: https://developer.mozilla.org/en-US/docs/Web/API/FormData/delete

"delete" is renamed to "form_data_delete" here to avoid using C++ keyword, otherwise will cause a keyword conflict in generated C++ binding code.

It will be renamed back to "delete" when register into qjs , see: MemberInstaller::InstallFunctions

get(name: string): BlobPart
getAll(name: string): BlobPart[];
has(name: string): boolean;
set(name: string, value: BlobPart, fileName?: string): void;
forEach(callbackfn: Function, thisArg?: any): void;
linsmod marked this conversation as resolved.
Show resolved Hide resolved
keys():string[]
values():BlobPart[]
entries():FormDataPart[]
}
Loading