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 14 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 @@ -378,6 +378,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
core/html/forms/html_input_element.cc
core/html/forms/html_form_element.cc
core/html/forms/html_textarea_element.cc
core/html/forms/form_data.cc
core/html/forms/form_data_part.cc

# SVG files
core/svg/svg_element.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
4 changes: 4 additions & 0 deletions bridge/core/binding_object.cc
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,8 @@ bool BindingObject::IsCanvasGradient() const {
return false;
}

bool BindingObject::IsFormData() const {
return false;
}

} // namespace webf
2 changes: 1 addition & 1 deletion bridge/core/binding_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class BindingObject : public ScriptWrappable {
virtual bool IsTouchList() const;
virtual bool IsComputedCssStyleDeclaration() const;
virtual bool IsCanvasGradient() const;

virtual bool IsFormData() const;
protected:
void TrackPendingPromiseBindingContext(BindingObjectPromiseContext* binding_object_promise_context);
void FullFillPendingPromise(BindingObjectPromiseContext* binding_object_promise_context);
Expand Down
173 changes: 173 additions & 0 deletions bridge/core/html/forms/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_part.h"
#include "form_data.h"
#include <memory>
#include "bindings/qjs/atomic_string.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
22 changes: 22 additions & 0 deletions bridge/core/html/forms/form_data.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2022-present The WebF authors. All rights reserved.
*/

export interface FormData {
new(): FormData;
append(name: string, value: BlobPart, fileName?: string): void;
append(name: string, value: string, 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;

readonly forEach: JSArrayProtoMethod;
readonly keys: JSArrayProtoMethod;
readonly entries: JSArrayProtoMethod;
readonly values: JSArrayProtoMethod;
readonly [Symbol.iterator]: JSArrayProtoMethod;
}
Loading
Loading