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 13 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
173 changes: 173 additions & 0 deletions bridge/core/api/form_data.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* 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;
}

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();
}
}

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());

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());

ScriptValue result = callback->Invoke(ctx(), thisArg, 3, args);
if (result.IsException()) {
exception_state.ThrowException(ctx(), result.QJSValue());
return;
}
}
}

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;
}

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;
}

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
23 changes: 23 additions & 0 deletions bridge/core/api/form_data.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2022-present The WebF authors. All rights reserved.
*/


// This file is for generate binding code only.

type FormDataPart={} // dummy code. Real one is introduced in C++ form_data_part.h, also blob_part.h
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