openwebf / webf

Build flutter apps with HTML/CSS and JavaScript.
https://openwebf.com/
Apache License 2.0
1.66k stars 121 forks source link

Implement FormData. #645

Open linsmod opened 3 months ago

linsmod commented 3 months ago

In this pull request, FormData Web API and it's qjs binding is implmented.

For prototype, see below:

type FormDataPart={}
export interface FormData {
    new():FormData;
    append(name: string, value: BlobPart, fileName?: string): void;
    // 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;
    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;
    keys():string[]
    values():BlobPart[]
    entries():FormDataPart[]
}

Todo: Making FetchModule support FormData body.

Todo: Should using fileName parameter in append/set implementation, we do NOT using it currently.

andycall commented 3 months ago

@linsmod

image

The binding from C++ to Dart with FormData class finished

linsmod commented 3 months ago

The binding from C++ to Dart with FormData class finished

Got! Awesome job!

andycall commented 3 months ago

The implementation of Chrome's FormData support is an good reference.

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/forms/form_data.h

linsmod commented 3 months ago

绑定到Dart的代码,是否可以通过直接传递entries的引用/指针来简化?因为FormData已经在C++层面实现了,如果再通过dart实现就重复了。

我就想投机取巧一下: 比如C++一边createBindingObject传递entries_:


FormData::FormData(JSContext* ctx) : BindingObject(ctx) {
  GetExecutingContext()->dartMethodPtr()->createBindingObject(GetExecutingContext()->isDedicated(),
                                                              GetExecutingContext()->contextId(), bindingObject(),
                                                              CreateBindingObjectType::kCreateFormData, &entries_, 0);
}

 private:
  std::vector<std::shared_ptr<FormDataEntry>> entries_;

Dart一边在某种构造函数内把C++传递的entries_指针取出来, 伪代码如下:

class FormData extends DynamicBindingObject {
 List<FormDataEntry> entries = [];
  FormData(BindingContext context, List<dynamic> domMatrixInit): super(context){
    entries=context.args;
}

关于创建绑定,还有一个疑问。我看到有一个创建绑定的初始化函数,但是没有看到被调用方的具体实现,只有测试代码中有一个空函数, 从Dart创建NativeBindingObject不是必要的吗?: void TEST_CreateBindingObject(double context_id, void native_binding_object, int32_t type, void args, int32_t argc) {}

andycall commented 3 months ago

绑定到Dart的代码,是否可以通过直接传递entries的引用/指针来简化?因为FormData已经在C++层面实现了,如果再通过dart实现就重复了。

我就想投机取巧一下: 比如C++一边createBindingObject传递entries_:

FormData::FormData(JSContext* ctx) : BindingObject(ctx) {
  GetExecutingContext()->dartMethodPtr()->createBindingObject(GetExecutingContext()->isDedicated(),
                                                              GetExecutingContext()->contextId(), bindingObject(),
                                                              CreateBindingObjectType::kCreateFormData, &entries_, 0);
}

 private:
  std::vector<std::shared_ptr<FormDataEntry>> entries_;

Dart一边在某种构造函数内把C++传递的entries_指针取出来, 伪代码如下:

class FormData extends DynamicBindingObject {
 List<FormDataEntry> entries = [];
  FormData(BindingContext context, List<dynamic> domMatrixInit): super(context){
    entries=context.args;
}

关于创建绑定,还有一个疑问。我看到有一个创建绑定的初始化函数,但是没有看到被调用方的具体实现,只有测试代码中有一个空函数, 从Dart创建NativeBindingObject不是必要的吗?: void TEST_CreateBindingObject(double context_id, void native_binding_object, int32_t type, void args, int32_t argc) {}

这么搞的话,问题可多了。跨线程问题,跨 VM 内存管理,Dart FFI 本身的性能问题,内存回收问题。

别瞎想了,再写一遍吧

linsmod commented 3 months ago

js 里面 new FormData()并添加数据, 在 dart 里面成了空的Map。

linsmod commented 3 months ago

readonly forEach: JSArrayProtoMethod; 被改成这样了之后我就不会了,没有找到任何具体实现例子。

我理解如果要实现JSArrayProtoMethod是缺少一个Iterator的模板的,如果有那个模板,那么代码生成那里改一下,让生成的代码去调用目标的Converter::ToIterator,是不是就行了。

因为我现在想在js那边直接把FormData转成普通JS对象,也就是dart里面的Map,为什么想这么做?因为js传递的FormData在fetch.dart里面就变成了一个空Map,即使js那边添加了数据,在Dart那边看也是空的,那不如将计就计直接用JS对象{}。如果要转{}就需要遍历内容,但因为现在改成了readonly forEach: JSArrayProtoMethod; 我在js那边forEach就取不到内容,空空如也。

(PS:注意到你们似乎在移植blink,那个能搞完,就省很多实现Web标准的功夫了)

andycall commented 3 months ago

估计哪里搞错了,预期 new FormData 传到 dart 是会变成昨天搞的 FormData Dart 对象的,我来看看为啥

andycall commented 3 months ago

readonly forEach: JSArrayProtoMethod; 被改成这样了之后我就不会了,没有找到任何具体实现例子。

我理解如果要实现JSArrayProtoMethod是缺少一个Iterator的模板的,如果有那个模板,那么代码生成那里改一下,让生成的代码去调用目标的Converter::ToIterator,是不是就行了。

因为我现在想在js那边直接把FormData转成普通JS对象,也就是dart里面的Map,为什么想这么做?因为js传递的FormData在fetch.dart里面就变成了一个空Map,即使js那边添加了数据,在Dart那边看也是空的,那不如将计就计直接用JS对象{}。如果要转{}就需要遍历内容,但因为现在改成了readonly forEach: JSArrayProtoMethod; 我在js那边forEach就取不到内容,空空如也。

(PS:注意到你们似乎在移植blink,那个能搞完,就省很多实现Web标准的功夫了)

你说的没错,是得单独写个 iterator 才能完整实现这个。readonly forEach: JSArrayProtoMethod; 依赖了 quickjs 自身的特定,类数组的对象能 work,但是对于 FormData 就不凑效了

andycall commented 3 months ago

@linsmod 重写了 FormData,并重新实现了 Iterator 功能,现在 Foreach 和返回 interator 的函数都 work 了。

估计哪里搞错了,预期 new FormData 传到 dart 是会变成昨天搞的 FormData Dart 对象的,我来看看为啥

现在把 formData 传给 fetch API 已经能在 C++ 那边收到了,接下来就是把这个 FormData 当成指针传递给 Dart。

绑定到Dart的代码,是否可以通过直接传递entries的引用/指针来简化?因为FormData已经在C++层面实现了,如果再通过dart实现就重复了。

这个我想了一下,是可以通过一种简单的方式实现的,Dart FormData 只需要实现一个功能 —— 反向通过 Dart FFI 把 FormData 的 bytes 读回 Dart 环境,然后再这个 bytes 传给 http 模块,这样就搞定了。

所以接下来的任务是:

  1. 将 FormData 转成 NativeValue,然后发送到 Dart 那边,这里需要调整 invokeModule 这个函数
  2. 实现 Dart --> C++ FormData 的通道,实现 HandleCallFromDartSide 函数即可。
  3. 实现 FormData 的 encoding 逻辑,需要将 FormData 的数据结构编码成 MultiPart byte 格式,这里可以参考 blink 的代码:1, 2
  4. 由于 Dart 和 C++ 是运行在两个线程内,从 Dart 里通过 FFI 读取 bytes,再回到 Dart 属于跨线程调用逻辑,需要处理同步问题

@linsmod 接下来就交给你了,我得去搞别人的事情了,加油。

linsmod commented 3 months ago

出差了,得过段时间。

linsmod commented 3 months ago

编译不过,好像是改动了这里原因:

  NativeValue* invokeModule(bool is_dedicated,
                            void* callback_context,
                            double context_id,
                            int64_t profile_link_id,
                            SharedNativeString* moduleName,
                            SharedNativeString* method,
                            NativeValue* params,
                            uint32_t argc,
                            AsyncModuleCallback callback);
dart_methods.h:143:16: note:   candidate expects 9 arguments, 8 provided
/home/wulin/webf_copy/bridge/core/frame/module_manager.cc:186:52: error: no matching function for call to ‘webf::DartMethodPointer::invokeModule(bool, std::nullptr_t, double, int64_t, std::unique_ptr<webf::SharedNativeString>::pointer, std::unique_ptr<webf::SharedNativeString>::pointer, webf::NativeValue*, webf::NativeValue* (&)(void*, double, const char*, webf::NativeValue*, Dart_Handle, webf::InvokeModuleResultCallback))’
  186 |     result = context->dartMethodPtr()->invokeModule(
      |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
  187 |         context->isDedicated(), nullptr, context->contextId(), context->dartIsolateContext()->profiler()->link_id(),
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  188 |         module_name_string.get(), method_name_string.get(), &params, handleInvokeModuleUnexpectedCallback);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /home/wulin/webf_copy/bridge/core/dart_isolate_context.h:11,
                 from /home/wulin/webf_copy/bridge/core/executing_context.h:26,
                 from /home/wulin/webf_copy/bridge/bindings/qjs/qjs_interface_bridge.h:9,
                 from /home/wulin/webf_copy/bridge/bindings/qjs/generated_code_helper.h:11,
                 from /home/wulin/webf_copy/bridge/out/qjs_module_manager_options.h:9,
                 from /home/wulin/webf_copy/bridge/core/frame/module_manager.h:12,
                 from /home/wulin/webf_copy/bridge/core/frame/module_manager.cc:5:
andycall commented 3 months ago

编译不过,好像是改动了这里原因:

修好了

@linsmod 可以留个联系方式吗,我开个会议给你讲讲后面怎么搞。 如果可以的话,可以把微信二维码发我邮箱一下

dongtiangche@outlook.com