/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
2604150425000
src/form-data-wrapper.js
300 строк
9 KB
Shihua Zheng
commit (#39097)
06 июн 2023, 02:37
Не верифицирован
06 июн 2023, 02:37
649e4ac
Код
Авторство
О чём код?
import {iterateCursor} from '#core/dom'; import {getFormAsObject, getSubmitButtonUsed} from '#core/dom/form'; import {map} from '#core/types/object'; import {Services} from '#service'; /** * Create a form data wrapper. The wrapper is necessary to provide a common * API for FormData objects on all browsers. For example, not all browsers * support the FormData#entries or FormData#delete functions. * * @param {!Window} win * @param {!HTMLFormElement=} opt_form * @return {!FormDataWrapperInterface} */ export function createFormDataWrapper(win, opt_form) { const platform = Services.platformFor(win); if (platform.isIos() && platform.getMajorVersion() == 11) { return new Ios11NativeFormDataWrapper(opt_form); } else if (FormData.prototype.entries && FormData.prototype.delete) { return new NativeFormDataWrapper(opt_form); } else { return new PolyfillFormDataWrapper(opt_form); } } /** * Check if the given object is a FormDataWrapper instance * @param {*} o * @return {boolean} True if the object is a FormDataWrapper instance. */ export function isFormDataWrapper(o) { // instanceof doesn't work as expected, so we detect with duck-typing. return !!o && typeof o.getFormData == 'function'; } /** * A polyfill wrapper for a `FormData` object. * * If there's no native `FormData#entries`, chances are there are no native * methods to read the content of the `FormData` after construction, so the * only way to implement `entries` in this class is to capture the fields in * the form passed to the constructor (and the arguments passed to the * `append` method). * * For more details on this, see http://mdn.io/FormData. * * @implements {FormDataWrapperInterface} * @visibleForTesting */ export class PolyfillFormDataWrapper { /** @override */ constructor(opt_form = undefined) { /** @private @const {!{[key: string]: !Array<string>}} */ this.fieldValues_ = opt_form ? getFormAsObject(opt_form) : map(); } /** * @param {string} name * @param {string|!File} value * @param {string=} opt_filename * @override */ append(name, value, opt_filename) { // Coercion to string is required to match // the native FormData.append behavior const nameString = String(name); this.fieldValues_[nameString] = this.fieldValues_[nameString] || []; this.fieldValues_[nameString].push(String(value)); } /** @override */ delete(name) { delete this.fieldValues_[name]; } /** @override */ entries() { const fieldEntries = []; Object.keys(this.fieldValues_).forEach((name) => { const values = this.fieldValues_[name]; values.forEach((value) => fieldEntries.push([name, value])); }); // Generator functions are not supported by the current Babel configuration, // so we must manually implement the iterator interface. let nextIndex = 0; return /** @type {!Iterator<!Array<string>>} */ ({ next() { return nextIndex < fieldEntries.length ? {value: fieldEntries[nextIndex++], done: false} : {value: undefined, done: true}; }, }); } /** @override */ getFormData() { const formData = new FormData(); Object.keys(this.fieldValues_).forEach((name) => { const values = this.fieldValues_[name]; values.forEach((value) => formData.append(name, value)); }); return formData; } } /** * Wrap the native `FormData` implementation. * * NOTE: This differs from the standard `FormData` constructor. This constructor * includes a submit button if it was used to submit the `opt_form`, where * the native `FormData` constructor does not include the submit button used to * submit the form. * {@link https://xhr.spec.whatwg.org/#dom-formdata} * @implements {FormDataWrapperInterface} */ class NativeFormDataWrapper { /** @override */ constructor(opt_form) { /** @private @const {!FormData} */ this.formData_ = new FormData(opt_form); this.maybeIncludeSubmitButton_(opt_form); } /** * If a submit button is focused (because it was used to submit the form), * or was the first submit button present, add its name and value to the * `FormData`, since publishers expect the submit button to be present. * @param {!HTMLFormElement=} opt_form * @private */ maybeIncludeSubmitButton_(opt_form) { // If a form is not passed to the constructor, // we are not in a submitting code path. if (!opt_form) { return; } const button = getSubmitButtonUsed(opt_form); if (button && button.name) { this.append(button.name, button.value); } } /** * @param {string} name * @param {string|!File} value * @param {string=} opt_filename * @override */ append(name, value, opt_filename) { this.formData_.append(name, value); } /** @override */ delete(name) { this.formData_.delete(name); } /** @override */ entries() { return this.formData_.entries(); } /** @override */ getFormData() { return this.formData_; } } /** * iOS 11 has a bug when submitting empty file inputs. * This works around the bug by replacing the empty files with Blob objects. */ class Ios11NativeFormDataWrapper extends NativeFormDataWrapper { /** @override */ constructor(opt_form) { super(opt_form); if (opt_form) { iterateCursor(opt_form.elements, (input) => { if (input.type == 'file' && input.files.length == 0) { this.formData_.delete(input.name); this.formData_.append(input.name, new Blob([]), ''); } }); } } /** * @param {string} name * @param {string|!File} value * @param {string=} opt_filename * @override */ append(name, value, opt_filename) { // Safari 11 breaks on submitting empty File values. if (value && typeof value == 'object' && isEmptyFile(value)) { this.formData_.append(name, new Blob([]), opt_filename || ''); } else { this.formData_.append(name, value); } } } /** * A wrapper for a native `FormData` object that allows the retrieval of entries * in the form data after construction even on browsers that don't natively * support `FormData.prototype.entries`. * * @interface * Subclassing `FormData` doesn't work in this case as the transpiler * generates code that calls the super constructor directly using * `Function.prototype.call`. WebKit (Safari) doesn't allow this and * enforces that constructors be called with the `new` operator. */ class FormDataWrapperInterface { /** * Creates a new wrapper for a `FormData` object. * * If there's no native `FormData#entries`, chances are there are no native * methods to read the content of the `FormData` after construction, so the * only way to implement `entries` in this class is to capture the fields in * the form passed to the constructor (and the arguments passed to the * `append` method). * * This constructor should also add the submitter element as defined in the * HTML spec for Form Submission Algorithm, but is not defined by the standard * when using the `FormData` constructor directly. * * For more details on this, see http://mdn.io/FormData. * * @param {!HTMLFormElement=} opt_form An HTML `<form>` element — when * specified, the `FormData` object will be populated with the form's * current keys/values using the name property of each element for the * keys and their submitted value for the values. It will also encode file * input content. */ constructor(opt_form) {} /** * Appends a new value onto an existing key inside a `FormData` object, or * adds the key if it does not already exist. * * Appending a `File` object is not yet supported and the `filename` * parameter is ignored for this wrapper. * * For more details on this, see http://mdn.io/FormData/append. * * TODO(cvializ): Update file support * * @param {string} unusedName The name of the field whose data is contained in * `value`. * @param {string|!File} unusedValue The field's value. * @param {string=} opt_filename The filename to use if the value is a file. */ append(unusedName, unusedValue, opt_filename) {} /** * Remove the given value from the FormData. * * For more details on this, see http://mdn.io/FormData/delete. * * @param {string} unusedName The name of the field to remove from the FormData. */ delete(unusedName) {} /** * Returns an iterator of all key/value pairs contained in this object. * * For more details on this, see http://mdn.io/FormData/entries. * * @return {!Iterator<!Array<string|!File>>} */ entries() {} /** * Returns the wrapped native `FormData` object. * * @return {!FormData} */ getFormData() {} } /** * Check if the given file is an empty file, which is the result of submitting * an empty `<input type="file">`. These cause errors when submitting forms * in Safari 11. * * @param {!File} file * @return {boolean} */ function isEmptyFile(file) { return file.name == '' && file.size == 0; }