webf
webf copied to clipboard
Add FormData Support
Feature type
DOM API
The spec link.
https://kapeli.com/dash_share?docset_file=JavaScript&docset_name=JavaScript&path=developer.mozilla.org/en-US/docs/Web/API/FormData.html&platform=javascript&repo=Main&source=developer.mozilla.org/en-US/docs/Web/API/FormData
How much important for you
No response
Hi there, I'm porting an vue app to webf however http errors stop me. Seems like FormData is not implemented currently, do we have a plan to implement it or if any beta version supports FormData?
E:authClient 'FormData' is not defined at <anonymous> (<input>:177:27)
at Promise (native)
at http (<input>:218:12)
at call (native)
at process (<input>:355:27)
at authClient (<input>:135:17)
at connect (<input>:112:25)
at $httpGuard (<input>:422:24)
at process (<input>:355:72)
at listFiles (<input>:468:17)
at refresh (<input>:166:25)
at mounted (<input>:85:10)
at <anonymous> (<input>:2716:94)
at callWithErrorHandling (<input>:353:5)
at callWithAsyncErrorHandling (<input>:356:17)
at <anonymous> (<input>:2698:19)
at flushPostFlushCbs (<input>:514:7)
at flushJobs (<input>:550:5)
at flushJobs (<input>:554:7)
177 27
We do not have a plan to support this from now on
Being a sponsor for this project can enhance your desired features compared to other projects we are currently working on.
Fine, thank you for you reply. I decide to implement formData in webF by my side. Due to the poor code skill of mine on Dart ( the first meet between Dart and I ), I made it hard with much help of AI suggestion. I wish I could issue a pull request to webF if I can get some advice/help from you webF authors about how to follow your archetectures or rules.
arraybuffer.dart
import 'dart:typed_data';
class ArrayBufferData {
Uint8List buffer;
ArrayBufferData(this.buffer);
factory ArrayBufferData.fromBytes(Uint8List bytes) {
return ArrayBufferData(bytes);
}
int get length => buffer.length;
}
blob.dart
import 'dart:typed_data';
import 'dart:convert';
import 'arraybuffer.dart';
/// A simplified version of the Blob class for demonstration purposes.
class Blob {
Blob(this._data, [this.type = 'application/octet-stream']);
final Uint8List _data;
final String type;
int get size => _data.length;
Uint8List get bytes => _data;
String StringResult() {
return String.fromCharCodes(_data);
}
String Base64Result() {
return 'data:$type;base64,${base64Encode(_data)}';
}
ArrayBufferData ArrayBufferResult() {
return ArrayBufferData(_data.buffer.asUint8List());
}
Blob slice(int start, int end, [String contentType = '']) {
if (start < 0) start = 0;
if (end < 0) end = 0;
if (end > _data.length) end = _data.length;
if (start > end) start = end;
final slicedData = _data.sublist(start, end);
return Blob(slicedData, contentType);
}
}
formdata.dart
import 'dart:convert';
import 'package:webf/webf.dart';
import 'blob.dart';
/// A simple implementation of the FormData interface.
class FormData extends BaseModule {
/// Creates a new FormData instance.
FormData(ModuleManager? moduleManager) : super(moduleManager);
/// The list that holds the data.
final List<List<dynamic>> _list = [];
/// Adds a key-value pair to the FormData.
void append(String name, value) {
if (value is Blob) {
// Handle Blob type.
_list.add([name, value]);
} else {
// Handle other types.
_list.add([name, value]);
}
}
/// Returns the first value associated with the given key.
dynamic getFirst(String name) {
for (var entry in _list) {
if (entry[0] == name) {
return entry[1];
}
}
return null;
}
/// Returns all values associated with the given key.
List<dynamic> getAll(String name) {
return _list.where((entry) => entry[0] == name).map((entry) => entry[1]).toList();
}
/// Serializes the FormData into a string.
@override
String toString() {
var entries = _list.map((entry) {
if (entry[1] is String) {
return '${Uri.encodeComponent(entry[0])}=${Uri.encodeComponent(entry[1] as String)}';
} else if (entry[1] is Blob) {
// Handle Blob serialization.
return '${Uri.encodeComponent(entry[0])}=${Uri.encodeComponent(entry[1].Base64Result())}';
} else {
return '${Uri.encodeComponent(entry[0])}=${Uri.encodeComponent(jsonEncode(entry[1]))}';
}
}).join('&');
return entries;
}
@override
String invoke(String method, params, InvokeModuleCallback callback) {
switch (method) {
case 'append':
append(params[0], params[1]);
break;
case 'getFirst':
return jsonEncode(getFirst(params[0]));
case 'getAll':
return jsonEncode(getAll(params[0]));
case 'toString':
return toString();
default:
print('Failed to execute \'$method\' on \'fromData\': NoSuchMethod ');
}
return EMPTY_STRING;
}
@override
void dispose() {}
@override
String get name => 'FormData';
}
module_manager.dart: one line change.
void _defineModuleCreator() {
if (_isDefined) return;
_isDefined = true;
[...]
_defineModule((ModuleManager? moduleManager) => FormData(moduleManager));
}
After add code listed above, still say 'FormData' is not defined. I'm no idea about how a js object registered in webF.
Adding a FormData API in JavaScript must be done with C++ bindings.
The Blob JavaScript API is a similar case to the FormData API.
You need to store the underlying byte data of FormData in C++ and send it to Dart via Dart FFI.
Then, append this byte data into the networking request, as described in the GitHub repository.
我尝试添加了formData和arrayBuffer,并且编译通过。 (formData还没有添加到请求的内容里面, 因为我那个vue程序好像可以new FormData了,暂时就没有管它了)
现在在测试arrayBuffer,但是用js测试new出来的对象似乎类型不对。
ts这样写的:
import {webf} from './webf';
export class ArrayBuffer{
private id:string;
private byteLength?:number=0;
constructor(byteLength?:number){
this.byteLength=byteLength;
this.id = webf.invokeModule('ArrayBufferData', 'init',[byteLength]);
}
public slice(start:number,end:number):void{
webf.invokeModule('ArrayBufferData','slice',[this.id,start,end]);
}
public toString():string{
return webf.invokeModule('ArrayBufferData','toString',[this.id]);
}
}
测试代码这样写的:
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
错误是这样的:
TypeError: ArrayBuffer object expected
at DataView (native)
at testBlobArrayBufferDataView (assets:assets/bundle.js:34:18)
我将本地的提交生成了一个patch文件
0001-WIP-Implement-fromdata-and-arraybuffer.zip
测试的代码附加在原有的example里面。
请指导一下。
arrayBuffer 是 ecma 标准支持的对象,quickjs 也支持了,不需要单独支持
而且通过 C++ 实现的话,是不需要在 polyfill 里新增任何代码
确实没有很清晰使用了polyfill,C++,又混合dart的时候,对象实现是如何注册到qjs里面。
那如果实现FormData,必须要实现一个qjs_form_data是吗?
写一个 TypeScript Typings,比如 blob.d.ts,生成器会生成与 QuickJS 交互的胶水代码,然后实现 form_data.h 和 form_data.cc,需要继承 BindingObject,这样才可以创建一个用于在 JavaScript 环境内使用的 C++ 对象,还能在 Dart 那边同步访问,最后别忘了在这里注册:binding_initializer.cc
搞定上面一切后,JS 的全局环境就会出现你添加的类,直接在 JS 中调用即可。然后在 JS 里调用了 FormData 返回的 JS 对象可以传递到 JS 写的 Fetch API,并体现在 invokeModule 的参数内,这时候在 C++ 那边提取参数时,就能找到 FormData 创建的 JS 对象。由于它是 BindingObject,可以直接转成 Pointer 发送到 Dart 那边。
然后你需要写一个 Dart 版本的 FormData 对象,记得继承 DynamicBindingObject。
其他的一些参考,比如 CanvasGradient 对象,以及 [Dart 那边的对应实]现](https://github.com/openwebf/webf/blob/main/webf/lib/src/html/canvas/canvas_context.dart#L138)
那如果实现FormData,必须要实现一个qjs_form_data是吗?
这是生成器生成的,不需要手动写
生成器是借鉴了 Chrome 的 WebIDL 思路,使用 TypeScript Typing 定义和 TypeScript API 定制的,代码在这里:
https://github.com/openwebf/webf/tree/main/bridge/scripts/code_generator
好的。Fetch API和xmlHttpRequest,在webf中是共享的实现代码吗?好像没有看到xmlHttpRequest。
https://github.com/openwebf/webf/blob/main/bridge/polyfill/src/xhr.ts
好的。那如果添加了FormData, 估计xhr这里也要适配一下。
我尝试在formData中使用BlobParts,
// type FormDataEntryValue = File | string;
export interface FormData {
new():FormData;
append(name: string, value: BlobPart, fileName?: string): void;
del(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: (value: BlobPart, key: string, parent: FormData) => void, thisArg?: any): void;
}
但是由于它的ImplType不是BlobPart*,而是一个std_shared_ptr<BlobPart>,
class BlobPart {
public:
using ImplType = std::shared_ptr<BlobPart>;
那么默认的ConverterImpl在绑定getAll的时候就转换不过去
template <>
struct Converter<BlobPart> : public ConverterBase<BlobPart> {
using ImplType = BlobPart::ImplType;
static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) {
assert(!JS_IsException(value));
return BlobPart::Create(ctx, value, exception_state);
}
static JSValue ToValue(JSContext* ctx, BlobPart* data) {
if (data == nullptr)
return JS_NULL;
return data->ToQuickJS(ctx);
}
};
那遇到这样的情况,我认为可以修改converter来支持,也可以创建一个类似blobPart的类型且它的ImplType是其本身指针来解决,不知道这样理解是否正确,有更好的方案吗?
template <>
struct Converter<BlobPart> : public ConverterBase<BlobPart> {
using ImplType = BlobPart::ImplType;
static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) {
assert(!JS_IsException(value));
return BlobPart::Create(ctx, value, exception_state);
}
static JSValue ToValue(JSContext* ctx, BlobPart* data) {
if (data == nullptr)
return JS_NULL;
return data->ToQuickJS(ctx);
}
};
你的想法没错,这里的实现有问题,应该把 ToValue 的参数改成:
static JSValue ToValue(JSContext* ctx, std::shared_ptr<BlobPart> data) {
if (data == nullptr)
return JS_NULL;
return data->ToQuickJS(ctx);
}
回调函数在d.ts里面用什么表示,NativeTypeFunction?
declare const setInterval: (callback: Function, timeout?: double) => int64;
FormData已经实现,现在需要执行单元测试。
我这边尝试了npm run test但是测试框架好像配置有问题,简单看了一下不知道如何解决,方便看一下吗
https://github.com/openwebf/webf/issues/642 npm run test出错。libwebf.so: undefined symbol: initTestFramework
@andycall
测试需要在 macOS 平台运行。
可以加到这里 https://github.com/openwebf/webf/tree/main/integration_tests/specs/xhr
目前只是测试FormData的api,FFI我还不会,不知道怎么弄到fetchModule里面。
我想是否能在FormData上面添加一个getBytes(), 在xhr.ts里面调用一下,data就自动变成一个dart里面的Uint8List?
看了下,底层的 FFI 通信现在还只支持从 Dart 发送 Uint8List 到 C++,反过来还没实现,你先把 PR 提交一下,我在你的分支上补充这块
你先把 PR 提交一下,我在你的分支上补充这块
Pull request created: https://github.com/openwebf/webf/pull/645
@linsmod done,看下 PR,通信的例子已经补充上了