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

Web APIs: File and FormData #1056

Merged
merged 12 commits into from
Nov 4, 2018
Merged

Web APIs: File and FormData #1056

merged 12 commits into from
Nov 4, 2018

Conversation

kyranet
Copy link
Contributor

@kyranet kyranet commented Oct 21, 2018

This PR adds better Blob spec compliance (objects, Uint16Array, Uint32Array tests), to pass some of WPT's tests.
This also adds File, which extends Blob, check spec here.
And FormData, which I first wanted to do (but later noticed it depended on File).

TODO:

  • Expand the File and FormData's tests.
  • Implement FetchResponse#formData:
    async formData(): Promise<domTypes.FormData> {
    I believe this goes out of this PR's scope, I'll do this in another PR once I understand how this method exactly works.

Copy link
Contributor

@kitsonk kitsonk left a comment

Choose a reason for hiding this comment

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

Some thoughts...

js/form_data.ts Outdated
import { DenoFile } from "./file";

export class FormData implements domTypes.FormData {
private data: Array<[string, domTypes.FormDataEntryValue]> = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

My personal opinion is that we should follow the convention of prefacing private members and methods with _. In TypeScript, because these are not really private, they impact the type structure. Which means that something else that has a public data member is now incompatible structurally, which is undesired. Also, as the TC39 proposal for public and private members becomes a reality, if this is made really private in the future, then there will be a need to refactor and replace the _ sigil with the private member sigil (#) and so having an existing sigil makes that refactoring easier.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also why is this not a Map?

Copy link
Contributor Author

@kyranet kyranet Oct 22, 2018

Choose a reason for hiding this comment

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

My personal opinion is that we should follow the convention of prefacing private members and methods with _. In TypeScript, because these are not really private, they impact the type structure. Which means that something else that has a public data member is now incompatible structurally, which is undesired. Also, as the TC39 proposal for public and private members becomes a reality, if this is made really private in the future, then there will be a need to refactor and replace the _ sigil with the private member sigil (#) and so having an existing sigil makes that refactoring easier.

If anything we would use a symbol instead:

deno/js/blob.ts

Lines 5 to 8 in c4bddc4

const bytesSymbol = Symbol("bytes");
export class DenoBlob implements domTypes.Blob {
private readonly [bytesSymbol]: Uint8Array;

Also why is this not a Map?

Because you can "append" multiple elements under the same name, getAll gets a list of all of them. Also because they're in insertion order:
image

I know using a Map would be awesome, it's superfast and basically what we'd define as idiomatic JavaScript/TypeScript (since append and getAll give a hint of it), but FormData#forEach would log a 1, a 3, b 2 instead of a 1, b 2, a 3 like browsers do. It's kinda sad we can't use a Map here, it would definitely boost FormData's internals by a lot, but we can't afford going out of the standard.

Copy link
Contributor

Choose a reason for hiding this comment

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

The symbol makes sense. Regarding the Map of course it could be a Map<string, domTypes.FormDataEntryValue[]> and on a .get() you would simply return value[0] and Array.from(value) for getAll() (using Array.from() to ensure to not leak the reference to real value array)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we were to go performance-wise, I believe Array#slice() (with no arguments) is much faster.

getAll(name: string): domTypes.FormDataEntryValue[] {
  const entries = this[dataSymbol].get(name);
  return entries ? entries.slice() : [];
}

But then again using the map would be against the implementation of various browsers where FormData#forEach, URLSearchParams#forEach, and others, call the callback strictly by insertion order, which a Map does not allow.

js/form_data.ts Outdated
* console.log(key);
* }
*/
keys(): IterableIterator<string> {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should produce iterators in a more idiomatic way... I have raised a PR with the way I feel we should be doing them, see #1062.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Feel free to do so, WHATWG defines a series of implementable interfaces like iterable which is essentially like this:

interface IWHATWGIterable<K, V> {
  keys(): IterableIterator<K>;
  values(): IterableIterator<V>;
  entries(): IterableIterator<[K, V]>;
  [Symbol.iterator](): IterableIterator<[K, V]>;
  forEach((value: V, key: K, parent: this) => void, thisArg?: any): void;
}

We could implement this and use a mixin to implement them, thought that'd need to expose the internal slot, which we cannot do. But if you have a better solution, I'm open to suggestions.

Copy link
Contributor

Choose a reason for hiding this comment

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

@kyranet I like the idea... I will change my PR to utilise TypeScript mixin classes. The only problem is that it would need to have whatever the internal data is as an iterator, which means you would have to further abstract your array data structure.

Copy link
Contributor

Choose a reason for hiding this comment

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

@kyranet I pushed an update to #1062 that combines everything we are talking about. Take a look.

@@ -0,0 +1,24 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
import * as domTypes from "./dom_types";
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm... file is a bit confusing with files. I wonder if this should be structured so that Web API stuff is in a sub folder where things are a bit less confusing, and Blob and Headers and fetch, etc. can go in there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, we could have js/webapi/, js/fileapi, js/denoapi, and others. But this is out of the scope of this PR. We can do this later once Event and EventTarget are merged too to prevent merge conflicts/issues with other collaborators working on this.

Copy link
Contributor

@kitsonk kitsonk left a comment

Choose a reason for hiding this comment

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

Some feedback...

Copy link
Contributor

@kitsonk kitsonk left a comment

Choose a reason for hiding this comment

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

Some feedback...

Copy link
Contributor

@kitsonk kitsonk left a comment

Choose a reason for hiding this comment

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

Some feedback...

Copy link
Contributor

@kitsonk kitsonk left a comment

Choose a reason for hiding this comment

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

Couple thoughts...

yield entry;
}
}

*keys(): IterableIterator<K> {
for (const key of (this as any)[dataSymbol].keys()) {
yield key;
for (const entry of (this as any)[dataSymbol]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would personally prefer:

Suggested change
for (const entry of (this as any)[dataSymbol]) {
for (const [key] of (this as any)[dataSymbol]) {

}
}

*values(): IterableIterator<V> {
for (const value of (this as any)[dataSymbol].values()) {
yield value;
for (const entry of (this as any)[dataSymbol]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would personally prefer:

Suggested change
for (const entry of (this as any)[dataSymbol]) {
for (const [_key, value] of (this as any)[dataSymbol]) {

Copy link
Contributor

@kitsonk kitsonk left a comment

Choose a reason for hiding this comment

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

LGTM!!!

}
}

*values(): IterableIterator<V> {
for (const entry of (this as any)[dataSymbol]) {
yield entry[1];
for (const [, value] of (this as any)[dataSymbol]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

oooh, yeah forgot you can omit values on array destructuring... 👍

@kyranet kyranet changed the title [WIP] Web APIs: File and FormData Web APIs: File and FormData Oct 27, 2018
Copy link
Member

@ry ry left a comment

Choose a reason for hiding this comment

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

LGTM - thanks!

@ry ry merged commit e93d686 into denoland:master Nov 4, 2018
@kyranet kyranet deleted the file-and-formdata branch November 4, 2018 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants