7071 lines
239 KiB
JavaScript
7071 lines
239 KiB
JavaScript
/**
|
|
* @license React
|
|
* react-dom-server.node.development.js
|
|
*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
if (process.env.NODE_ENV !== "production") {
|
|
(function() {
|
|
'use strict';
|
|
|
|
var React = require('react');
|
|
var util = require('util');
|
|
|
|
var ReactVersion = '18.3.1';
|
|
|
|
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
|
|
// by calls to these methods by a Babel plugin.
|
|
//
|
|
// In PROD (or in packages without access to React internals),
|
|
// they are left as they are instead.
|
|
|
|
function warn(format) {
|
|
{
|
|
{
|
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
args[_key - 1] = arguments[_key];
|
|
}
|
|
|
|
printWarning('warn', format, args);
|
|
}
|
|
}
|
|
}
|
|
function error(format) {
|
|
{
|
|
{
|
|
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
args[_key2 - 1] = arguments[_key2];
|
|
}
|
|
|
|
printWarning('error', format, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
function printWarning(level, format, args) {
|
|
// When changing this logic, you might want to also
|
|
// update consoleWithStackDev.www.js as well.
|
|
{
|
|
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
|
var stack = ReactDebugCurrentFrame.getStackAddendum();
|
|
|
|
if (stack !== '') {
|
|
format += '%s';
|
|
args = args.concat([stack]);
|
|
} // eslint-disable-next-line react-internal/safe-string-coercion
|
|
|
|
|
|
var argsWithFormat = args.map(function (item) {
|
|
return String(item);
|
|
}); // Careful: RN currently depends on this prefix
|
|
|
|
argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it
|
|
// breaks IE9: https://github.com/facebook/react/issues/13610
|
|
// eslint-disable-next-line react-internal/no-production-logging
|
|
|
|
Function.prototype.apply.call(console[level], console, argsWithFormat);
|
|
}
|
|
}
|
|
|
|
function scheduleWork(callback) {
|
|
setImmediate(callback);
|
|
}
|
|
function flushBuffered(destination) {
|
|
// If we don't have any more data to send right now.
|
|
// Flush whatever is in the buffer to the wire.
|
|
if (typeof destination.flush === 'function') {
|
|
// By convention the Zlib streams provide a flush function for this purpose.
|
|
// For Express, compression middleware adds this method.
|
|
destination.flush();
|
|
}
|
|
}
|
|
var VIEW_SIZE = 2048;
|
|
var currentView = null;
|
|
var writtenBytes = 0;
|
|
var destinationHasCapacity = true;
|
|
function beginWriting(destination) {
|
|
currentView = new Uint8Array(VIEW_SIZE);
|
|
writtenBytes = 0;
|
|
destinationHasCapacity = true;
|
|
}
|
|
|
|
function writeStringChunk(destination, stringChunk) {
|
|
if (stringChunk.length === 0) {
|
|
return;
|
|
} // maximum possible view needed to encode entire string
|
|
|
|
|
|
if (stringChunk.length * 3 > VIEW_SIZE) {
|
|
if (writtenBytes > 0) {
|
|
writeToDestination(destination, currentView.subarray(0, writtenBytes));
|
|
currentView = new Uint8Array(VIEW_SIZE);
|
|
writtenBytes = 0;
|
|
}
|
|
|
|
writeToDestination(destination, textEncoder.encode(stringChunk));
|
|
return;
|
|
}
|
|
|
|
var target = currentView;
|
|
|
|
if (writtenBytes > 0) {
|
|
target = currentView.subarray(writtenBytes);
|
|
}
|
|
|
|
var _textEncoder$encodeIn = textEncoder.encodeInto(stringChunk, target),
|
|
read = _textEncoder$encodeIn.read,
|
|
written = _textEncoder$encodeIn.written;
|
|
|
|
writtenBytes += written;
|
|
|
|
if (read < stringChunk.length) {
|
|
writeToDestination(destination, currentView);
|
|
currentView = new Uint8Array(VIEW_SIZE);
|
|
writtenBytes = textEncoder.encodeInto(stringChunk.slice(read), currentView).written;
|
|
}
|
|
|
|
if (writtenBytes === VIEW_SIZE) {
|
|
writeToDestination(destination, currentView);
|
|
currentView = new Uint8Array(VIEW_SIZE);
|
|
writtenBytes = 0;
|
|
}
|
|
}
|
|
|
|
function writeViewChunk(destination, chunk) {
|
|
if (chunk.byteLength === 0) {
|
|
return;
|
|
}
|
|
|
|
if (chunk.byteLength > VIEW_SIZE) {
|
|
// this chunk may overflow a single view which implies it was not
|
|
// one that is cached by the streaming renderer. We will enqueu
|
|
// it directly and expect it is not re-used
|
|
if (writtenBytes > 0) {
|
|
writeToDestination(destination, currentView.subarray(0, writtenBytes));
|
|
currentView = new Uint8Array(VIEW_SIZE);
|
|
writtenBytes = 0;
|
|
}
|
|
|
|
writeToDestination(destination, chunk);
|
|
return;
|
|
}
|
|
|
|
var bytesToWrite = chunk;
|
|
var allowableBytes = currentView.length - writtenBytes;
|
|
|
|
if (allowableBytes < bytesToWrite.byteLength) {
|
|
// this chunk would overflow the current view. We enqueue a full view
|
|
// and start a new view with the remaining chunk
|
|
if (allowableBytes === 0) {
|
|
// the current view is already full, send it
|
|
writeToDestination(destination, currentView);
|
|
} else {
|
|
// fill up the current view and apply the remaining chunk bytes
|
|
// to a new view.
|
|
currentView.set(bytesToWrite.subarray(0, allowableBytes), writtenBytes);
|
|
writtenBytes += allowableBytes;
|
|
writeToDestination(destination, currentView);
|
|
bytesToWrite = bytesToWrite.subarray(allowableBytes);
|
|
}
|
|
|
|
currentView = new Uint8Array(VIEW_SIZE);
|
|
writtenBytes = 0;
|
|
}
|
|
|
|
currentView.set(bytesToWrite, writtenBytes);
|
|
writtenBytes += bytesToWrite.byteLength;
|
|
|
|
if (writtenBytes === VIEW_SIZE) {
|
|
writeToDestination(destination, currentView);
|
|
currentView = new Uint8Array(VIEW_SIZE);
|
|
writtenBytes = 0;
|
|
}
|
|
}
|
|
|
|
function writeChunk(destination, chunk) {
|
|
if (typeof chunk === 'string') {
|
|
writeStringChunk(destination, chunk);
|
|
} else {
|
|
writeViewChunk(destination, chunk);
|
|
}
|
|
}
|
|
|
|
function writeToDestination(destination, view) {
|
|
var currentHasCapacity = destination.write(view);
|
|
destinationHasCapacity = destinationHasCapacity && currentHasCapacity;
|
|
}
|
|
|
|
function writeChunkAndReturn(destination, chunk) {
|
|
writeChunk(destination, chunk);
|
|
return destinationHasCapacity;
|
|
}
|
|
function completeWriting(destination) {
|
|
if (currentView && writtenBytes > 0) {
|
|
destination.write(currentView.subarray(0, writtenBytes));
|
|
}
|
|
|
|
currentView = null;
|
|
writtenBytes = 0;
|
|
destinationHasCapacity = true;
|
|
}
|
|
function close(destination) {
|
|
destination.end();
|
|
}
|
|
var textEncoder = new util.TextEncoder();
|
|
function stringToChunk(content) {
|
|
return content;
|
|
}
|
|
function stringToPrecomputedChunk(content) {
|
|
return textEncoder.encode(content);
|
|
}
|
|
function closeWithError(destination, error) {
|
|
// $FlowFixMe: This is an Error object or the destination accepts other types.
|
|
destination.destroy(error);
|
|
}
|
|
|
|
/*
|
|
* The `'' + value` pattern (used in in perf-sensitive code) throws for Symbol
|
|
* and Temporal.* types. See https://github.com/facebook/react/pull/22064.
|
|
*
|
|
* The functions in this module will throw an easier-to-understand,
|
|
* easier-to-debug exception with a clear errors message message explaining the
|
|
* problem. (Instead of a confusing exception thrown inside the implementation
|
|
* of the `value` object).
|
|
*/
|
|
// $FlowFixMe only called in DEV, so void return is not possible.
|
|
function typeName(value) {
|
|
{
|
|
// toStringTag is needed for namespaced types like Temporal.Instant
|
|
var hasToStringTag = typeof Symbol === 'function' && Symbol.toStringTag;
|
|
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || 'Object';
|
|
return type;
|
|
}
|
|
} // $FlowFixMe only called in DEV, so void return is not possible.
|
|
|
|
|
|
function willCoercionThrow(value) {
|
|
{
|
|
try {
|
|
testStringCoercion(value);
|
|
return false;
|
|
} catch (e) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function testStringCoercion(value) {
|
|
// If you ended up here by following an exception call stack, here's what's
|
|
// happened: you supplied an object or symbol value to React (as a prop, key,
|
|
// DOM attribute, CSS property, string ref, etc.) and when React tried to
|
|
// coerce it to a string using `'' + value`, an exception was thrown.
|
|
//
|
|
// The most common types that will cause this exception are `Symbol` instances
|
|
// and Temporal objects like `Temporal.Instant`. But any object that has a
|
|
// `valueOf` or `[Symbol.toPrimitive]` method that throws will also cause this
|
|
// exception. (Library authors do this to prevent users from using built-in
|
|
// numeric operators like `+` or comparison operators like `>=` because custom
|
|
// methods are needed to perform accurate arithmetic or comparison.)
|
|
//
|
|
// To fix the problem, coerce this object or symbol value to a string before
|
|
// passing it to React. The most reliable way is usually `String(value)`.
|
|
//
|
|
// To find which value is throwing, check the browser or debugger console.
|
|
// Before this exception was thrown, there should be `console.error` output
|
|
// that shows the type (Symbol, Temporal.PlainDate, etc.) that caused the
|
|
// problem and how that type was used: key, atrribute, input value prop, etc.
|
|
// In most cases, this console output also shows the component and its
|
|
// ancestor components where the exception happened.
|
|
//
|
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
|
return '' + value;
|
|
}
|
|
|
|
function checkAttributeStringCoercion(value, attributeName) {
|
|
{
|
|
if (willCoercionThrow(value)) {
|
|
error('The provided `%s` attribute is an unsupported type %s.' + ' This value must be coerced to a string before before using it here.', attributeName, typeName(value));
|
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
|
|
}
|
|
}
|
|
}
|
|
function checkCSSPropertyStringCoercion(value, propName) {
|
|
{
|
|
if (willCoercionThrow(value)) {
|
|
error('The provided `%s` CSS property is an unsupported type %s.' + ' This value must be coerced to a string before before using it here.', propName, typeName(value));
|
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
|
|
}
|
|
}
|
|
}
|
|
function checkHtmlStringCoercion(value) {
|
|
{
|
|
if (willCoercionThrow(value)) {
|
|
error('The provided HTML markup uses a value of unsupported type %s.' + ' This value must be coerced to a string before before using it here.', typeName(value));
|
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
|
|
}
|
|
}
|
|
}
|
|
|
|
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
|
|
// A reserved attribute.
|
|
// It is handled by React separately and shouldn't be written to the DOM.
|
|
var RESERVED = 0; // A simple string attribute.
|
|
// Attributes that aren't in the filter are presumed to have this type.
|
|
|
|
var STRING = 1; // A string attribute that accepts booleans in React. In HTML, these are called
|
|
// "enumerated" attributes with "true" and "false" as possible values.
|
|
// When true, it should be set to a "true" string.
|
|
// When false, it should be set to a "false" string.
|
|
|
|
var BOOLEANISH_STRING = 2; // A real boolean attribute.
|
|
// When true, it should be present (set either to an empty string or its name).
|
|
// When false, it should be omitted.
|
|
|
|
var BOOLEAN = 3; // An attribute that can be used as a flag as well as with a value.
|
|
// When true, it should be present (set either to an empty string or its name).
|
|
// When false, it should be omitted.
|
|
// For any other value, should be present with that value.
|
|
|
|
var OVERLOADED_BOOLEAN = 4; // An attribute that must be numeric or parse as a numeric.
|
|
// When falsy, it should be removed.
|
|
|
|
var NUMERIC = 5; // An attribute that must be positive numeric or parse as a positive numeric.
|
|
// When falsy, it should be removed.
|
|
|
|
var POSITIVE_NUMERIC = 6;
|
|
|
|
/* eslint-disable max-len */
|
|
var ATTRIBUTE_NAME_START_CHAR = ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
|
|
/* eslint-enable max-len */
|
|
|
|
var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + "\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
|
|
var VALID_ATTRIBUTE_NAME_REGEX = new RegExp('^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$');
|
|
var illegalAttributeNameCache = {};
|
|
var validatedAttributeNameCache = {};
|
|
function isAttributeNameSafe(attributeName) {
|
|
if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) {
|
|
return true;
|
|
}
|
|
|
|
if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) {
|
|
return false;
|
|
}
|
|
|
|
if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {
|
|
validatedAttributeNameCache[attributeName] = true;
|
|
return true;
|
|
}
|
|
|
|
illegalAttributeNameCache[attributeName] = true;
|
|
|
|
{
|
|
error('Invalid attribute name: `%s`', attributeName);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
function shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag) {
|
|
if (propertyInfo !== null && propertyInfo.type === RESERVED) {
|
|
return false;
|
|
}
|
|
|
|
switch (typeof value) {
|
|
case 'function': // $FlowIssue symbol is perfectly valid here
|
|
|
|
case 'symbol':
|
|
// eslint-disable-line
|
|
return true;
|
|
|
|
case 'boolean':
|
|
{
|
|
if (isCustomComponentTag) {
|
|
return false;
|
|
}
|
|
|
|
if (propertyInfo !== null) {
|
|
return !propertyInfo.acceptsBooleans;
|
|
} else {
|
|
var prefix = name.toLowerCase().slice(0, 5);
|
|
return prefix !== 'data-' && prefix !== 'aria-';
|
|
}
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
function getPropertyInfo(name) {
|
|
return properties.hasOwnProperty(name) ? properties[name] : null;
|
|
}
|
|
|
|
function PropertyInfoRecord(name, type, mustUseProperty, attributeName, attributeNamespace, sanitizeURL, removeEmptyString) {
|
|
this.acceptsBooleans = type === BOOLEANISH_STRING || type === BOOLEAN || type === OVERLOADED_BOOLEAN;
|
|
this.attributeName = attributeName;
|
|
this.attributeNamespace = attributeNamespace;
|
|
this.mustUseProperty = mustUseProperty;
|
|
this.propertyName = name;
|
|
this.type = type;
|
|
this.sanitizeURL = sanitizeURL;
|
|
this.removeEmptyString = removeEmptyString;
|
|
} // When adding attributes to this list, be sure to also add them to
|
|
// the `possibleStandardNames` module to ensure casing and incorrect
|
|
// name warnings.
|
|
|
|
|
|
var properties = {}; // These props are reserved by React. They shouldn't be written to the DOM.
|
|
|
|
var reservedProps = ['children', 'dangerouslySetInnerHTML', // TODO: This prevents the assignment of defaultValue to regular
|
|
// elements (not just inputs). Now that ReactDOMInput assigns to the
|
|
// defaultValue property -- do we need this?
|
|
'defaultValue', 'defaultChecked', 'innerHTML', 'suppressContentEditableWarning', 'suppressHydrationWarning', 'style'];
|
|
|
|
reservedProps.forEach(function (name) {
|
|
properties[name] = new PropertyInfoRecord(name, RESERVED, false, // mustUseProperty
|
|
name, // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // A few React string attributes have a different name.
|
|
// This is a mapping from React prop names to the attribute names.
|
|
|
|
[['acceptCharset', 'accept-charset'], ['className', 'class'], ['htmlFor', 'for'], ['httpEquiv', 'http-equiv']].forEach(function (_ref) {
|
|
var name = _ref[0],
|
|
attributeName = _ref[1];
|
|
properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty
|
|
attributeName, // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // These are "enumerated" HTML attributes that accept "true" and "false".
|
|
// In React, we let users pass `true` and `false` even though technically
|
|
// these aren't boolean attributes (they are coerced to strings).
|
|
|
|
['contentEditable', 'draggable', 'spellCheck', 'value'].forEach(function (name) {
|
|
properties[name] = new PropertyInfoRecord(name, BOOLEANISH_STRING, false, // mustUseProperty
|
|
name.toLowerCase(), // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // These are "enumerated" SVG attributes that accept "true" and "false".
|
|
// In React, we let users pass `true` and `false` even though technically
|
|
// these aren't boolean attributes (they are coerced to strings).
|
|
// Since these are SVG attributes, their attribute names are case-sensitive.
|
|
|
|
['autoReverse', 'externalResourcesRequired', 'focusable', 'preserveAlpha'].forEach(function (name) {
|
|
properties[name] = new PropertyInfoRecord(name, BOOLEANISH_STRING, false, // mustUseProperty
|
|
name, // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // These are HTML boolean attributes.
|
|
|
|
['allowFullScreen', 'async', // Note: there is a special case that prevents it from being written to the DOM
|
|
// on the client side because the browsers are inconsistent. Instead we call focus().
|
|
'autoFocus', 'autoPlay', 'controls', 'default', 'defer', 'disabled', 'disablePictureInPicture', 'disableRemotePlayback', 'formNoValidate', 'hidden', 'loop', 'noModule', 'noValidate', 'open', 'playsInline', 'readOnly', 'required', 'reversed', 'scoped', 'seamless', // Microdata
|
|
'itemScope'].forEach(function (name) {
|
|
properties[name] = new PropertyInfoRecord(name, BOOLEAN, false, // mustUseProperty
|
|
name.toLowerCase(), // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // These are the few React props that we set as DOM properties
|
|
// rather than attributes. These are all booleans.
|
|
|
|
['checked', // Note: `option.selected` is not updated if `select.multiple` is
|
|
// disabled with `removeAttribute`. We have special logic for handling this.
|
|
'multiple', 'muted', 'selected' // NOTE: if you add a camelCased prop to this list,
|
|
// you'll need to set attributeName to name.toLowerCase()
|
|
// instead in the assignment below.
|
|
].forEach(function (name) {
|
|
properties[name] = new PropertyInfoRecord(name, BOOLEAN, true, // mustUseProperty
|
|
name, // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // These are HTML attributes that are "overloaded booleans": they behave like
|
|
// booleans, but can also accept a string value.
|
|
|
|
['capture', 'download' // NOTE: if you add a camelCased prop to this list,
|
|
// you'll need to set attributeName to name.toLowerCase()
|
|
// instead in the assignment below.
|
|
].forEach(function (name) {
|
|
properties[name] = new PropertyInfoRecord(name, OVERLOADED_BOOLEAN, false, // mustUseProperty
|
|
name, // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // These are HTML attributes that must be positive numbers.
|
|
|
|
['cols', 'rows', 'size', 'span' // NOTE: if you add a camelCased prop to this list,
|
|
// you'll need to set attributeName to name.toLowerCase()
|
|
// instead in the assignment below.
|
|
].forEach(function (name) {
|
|
properties[name] = new PropertyInfoRecord(name, POSITIVE_NUMERIC, false, // mustUseProperty
|
|
name, // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // These are HTML attributes that must be numbers.
|
|
|
|
['rowSpan', 'start'].forEach(function (name) {
|
|
properties[name] = new PropertyInfoRecord(name, NUMERIC, false, // mustUseProperty
|
|
name.toLowerCase(), // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
});
|
|
var CAMELIZE = /[\-\:]([a-z])/g;
|
|
|
|
var capitalize = function (token) {
|
|
return token[1].toUpperCase();
|
|
}; // This is a list of all SVG attributes that need special casing, namespacing,
|
|
// or boolean value assignment. Regular attributes that just accept strings
|
|
// and have the same names are omitted, just like in the HTML attribute filter.
|
|
// Some of these attributes can be hard to find. This list was created by
|
|
// scraping the MDN documentation.
|
|
|
|
|
|
['accent-height', 'alignment-baseline', 'arabic-form', 'baseline-shift', 'cap-height', 'clip-path', 'clip-rule', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'dominant-baseline', 'enable-background', 'fill-opacity', 'fill-rule', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-name', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'horiz-adv-x', 'horiz-origin-x', 'image-rendering', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'overline-position', 'overline-thickness', 'paint-order', 'panose-1', 'pointer-events', 'rendering-intent', 'shape-rendering', 'stop-color', 'stop-opacity', 'strikethrough-position', 'strikethrough-thickness', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'underline-position', 'underline-thickness', 'unicode-bidi', 'unicode-range', 'units-per-em', 'v-alphabetic', 'v-hanging', 'v-ideographic', 'v-mathematical', 'vector-effect', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'word-spacing', 'writing-mode', 'xmlns:xlink', 'x-height' // NOTE: if you add a camelCased prop to this list,
|
|
// you'll need to set attributeName to name.toLowerCase()
|
|
// instead in the assignment below.
|
|
].forEach(function (attributeName) {
|
|
var name = attributeName.replace(CAMELIZE, capitalize);
|
|
properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty
|
|
attributeName, null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // String SVG attributes with the xlink namespace.
|
|
|
|
['xlink:actuate', 'xlink:arcrole', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type' // NOTE: if you add a camelCased prop to this list,
|
|
// you'll need to set attributeName to name.toLowerCase()
|
|
// instead in the assignment below.
|
|
].forEach(function (attributeName) {
|
|
var name = attributeName.replace(CAMELIZE, capitalize);
|
|
properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty
|
|
attributeName, 'http://www.w3.org/1999/xlink', false, // sanitizeURL
|
|
false);
|
|
}); // String SVG attributes with the xml namespace.
|
|
|
|
['xml:base', 'xml:lang', 'xml:space' // NOTE: if you add a camelCased prop to this list,
|
|
// you'll need to set attributeName to name.toLowerCase()
|
|
// instead in the assignment below.
|
|
].forEach(function (attributeName) {
|
|
var name = attributeName.replace(CAMELIZE, capitalize);
|
|
properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty
|
|
attributeName, 'http://www.w3.org/XML/1998/namespace', false, // sanitizeURL
|
|
false);
|
|
}); // These attribute exists both in HTML and SVG.
|
|
// The attribute name is case-sensitive in SVG so we can't just use
|
|
// the React name like we do for attributes that exist only in HTML.
|
|
|
|
['tabIndex', 'crossOrigin'].forEach(function (attributeName) {
|
|
properties[attributeName] = new PropertyInfoRecord(attributeName, STRING, false, // mustUseProperty
|
|
attributeName.toLowerCase(), // attributeName
|
|
null, // attributeNamespace
|
|
false, // sanitizeURL
|
|
false);
|
|
}); // These attributes accept URLs. These must not allow javascript: URLS.
|
|
// These will also need to accept Trusted Types object in the future.
|
|
|
|
var xlinkHref = 'xlinkHref';
|
|
properties[xlinkHref] = new PropertyInfoRecord('xlinkHref', STRING, false, // mustUseProperty
|
|
'xlink:href', 'http://www.w3.org/1999/xlink', true, // sanitizeURL
|
|
false);
|
|
['src', 'href', 'action', 'formAction'].forEach(function (attributeName) {
|
|
properties[attributeName] = new PropertyInfoRecord(attributeName, STRING, false, // mustUseProperty
|
|
attributeName.toLowerCase(), // attributeName
|
|
null, // attributeNamespace
|
|
true, // sanitizeURL
|
|
true);
|
|
});
|
|
|
|
/**
|
|
* CSS properties which accept numbers but are not in units of "px".
|
|
*/
|
|
var isUnitlessNumber = {
|
|
animationIterationCount: true,
|
|
aspectRatio: true,
|
|
borderImageOutset: true,
|
|
borderImageSlice: true,
|
|
borderImageWidth: true,
|
|
boxFlex: true,
|
|
boxFlexGroup: true,
|
|
boxOrdinalGroup: true,
|
|
columnCount: true,
|
|
columns: true,
|
|
flex: true,
|
|
flexGrow: true,
|
|
flexPositive: true,
|
|
flexShrink: true,
|
|
flexNegative: true,
|
|
flexOrder: true,
|
|
gridArea: true,
|
|
gridRow: true,
|
|
gridRowEnd: true,
|
|
gridRowSpan: true,
|
|
gridRowStart: true,
|
|
gridColumn: true,
|
|
gridColumnEnd: true,
|
|
gridColumnSpan: true,
|
|
gridColumnStart: true,
|
|
fontWeight: true,
|
|
lineClamp: true,
|
|
lineHeight: true,
|
|
opacity: true,
|
|
order: true,
|
|
orphans: true,
|
|
tabSize: true,
|
|
widows: true,
|
|
zIndex: true,
|
|
zoom: true,
|
|
// SVG-related properties
|
|
fillOpacity: true,
|
|
floodOpacity: true,
|
|
stopOpacity: true,
|
|
strokeDasharray: true,
|
|
strokeDashoffset: true,
|
|
strokeMiterlimit: true,
|
|
strokeOpacity: true,
|
|
strokeWidth: true
|
|
};
|
|
/**
|
|
* @param {string} prefix vendor-specific prefix, eg: Webkit
|
|
* @param {string} key style name, eg: transitionDuration
|
|
* @return {string} style name prefixed with `prefix`, properly camelCased, eg:
|
|
* WebkitTransitionDuration
|
|
*/
|
|
|
|
function prefixKey(prefix, key) {
|
|
return prefix + key.charAt(0).toUpperCase() + key.substring(1);
|
|
}
|
|
/**
|
|
* Support style names that may come passed in prefixed by adding permutations
|
|
* of vendor prefixes.
|
|
*/
|
|
|
|
|
|
var prefixes = ['Webkit', 'ms', 'Moz', 'O']; // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
|
|
// infinite loop, because it iterates over the newly added props too.
|
|
|
|
Object.keys(isUnitlessNumber).forEach(function (prop) {
|
|
prefixes.forEach(function (prefix) {
|
|
isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
|
|
});
|
|
});
|
|
|
|
var hasReadOnlyValue = {
|
|
button: true,
|
|
checkbox: true,
|
|
image: true,
|
|
hidden: true,
|
|
radio: true,
|
|
reset: true,
|
|
submit: true
|
|
};
|
|
function checkControlledValueProps(tagName, props) {
|
|
{
|
|
if (!(hasReadOnlyValue[props.type] || props.onChange || props.onInput || props.readOnly || props.disabled || props.value == null)) {
|
|
error('You provided a `value` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultValue`. Otherwise, ' + 'set either `onChange` or `readOnly`.');
|
|
}
|
|
|
|
if (!(props.onChange || props.readOnly || props.disabled || props.checked == null)) {
|
|
error('You provided a `checked` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultChecked`. Otherwise, ' + 'set either `onChange` or `readOnly`.');
|
|
}
|
|
}
|
|
}
|
|
|
|
function isCustomComponent(tagName, props) {
|
|
if (tagName.indexOf('-') === -1) {
|
|
return typeof props.is === 'string';
|
|
}
|
|
|
|
switch (tagName) {
|
|
// These are reserved SVG and MathML elements.
|
|
// We don't mind this list too much because we expect it to never grow.
|
|
// The alternative is to track the namespace in a few places which is convoluted.
|
|
// https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts
|
|
case 'annotation-xml':
|
|
case 'color-profile':
|
|
case 'font-face':
|
|
case 'font-face-src':
|
|
case 'font-face-uri':
|
|
case 'font-face-format':
|
|
case 'font-face-name':
|
|
case 'missing-glyph':
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
var ariaProperties = {
|
|
'aria-current': 0,
|
|
// state
|
|
'aria-description': 0,
|
|
'aria-details': 0,
|
|
'aria-disabled': 0,
|
|
// state
|
|
'aria-hidden': 0,
|
|
// state
|
|
'aria-invalid': 0,
|
|
// state
|
|
'aria-keyshortcuts': 0,
|
|
'aria-label': 0,
|
|
'aria-roledescription': 0,
|
|
// Widget Attributes
|
|
'aria-autocomplete': 0,
|
|
'aria-checked': 0,
|
|
'aria-expanded': 0,
|
|
'aria-haspopup': 0,
|
|
'aria-level': 0,
|
|
'aria-modal': 0,
|
|
'aria-multiline': 0,
|
|
'aria-multiselectable': 0,
|
|
'aria-orientation': 0,
|
|
'aria-placeholder': 0,
|
|
'aria-pressed': 0,
|
|
'aria-readonly': 0,
|
|
'aria-required': 0,
|
|
'aria-selected': 0,
|
|
'aria-sort': 0,
|
|
'aria-valuemax': 0,
|
|
'aria-valuemin': 0,
|
|
'aria-valuenow': 0,
|
|
'aria-valuetext': 0,
|
|
// Live Region Attributes
|
|
'aria-atomic': 0,
|
|
'aria-busy': 0,
|
|
'aria-live': 0,
|
|
'aria-relevant': 0,
|
|
// Drag-and-Drop Attributes
|
|
'aria-dropeffect': 0,
|
|
'aria-grabbed': 0,
|
|
// Relationship Attributes
|
|
'aria-activedescendant': 0,
|
|
'aria-colcount': 0,
|
|
'aria-colindex': 0,
|
|
'aria-colspan': 0,
|
|
'aria-controls': 0,
|
|
'aria-describedby': 0,
|
|
'aria-errormessage': 0,
|
|
'aria-flowto': 0,
|
|
'aria-labelledby': 0,
|
|
'aria-owns': 0,
|
|
'aria-posinset': 0,
|
|
'aria-rowcount': 0,
|
|
'aria-rowindex': 0,
|
|
'aria-rowspan': 0,
|
|
'aria-setsize': 0
|
|
};
|
|
|
|
var warnedProperties = {};
|
|
var rARIA = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$');
|
|
var rARIACamel = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$');
|
|
|
|
function validateProperty(tagName, name) {
|
|
{
|
|
if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) {
|
|
return true;
|
|
}
|
|
|
|
if (rARIACamel.test(name)) {
|
|
var ariaName = 'aria-' + name.slice(4).toLowerCase();
|
|
var correctName = ariaProperties.hasOwnProperty(ariaName) ? ariaName : null; // If this is an aria-* attribute, but is not listed in the known DOM
|
|
// DOM properties, then it is an invalid aria-* attribute.
|
|
|
|
if (correctName == null) {
|
|
error('Invalid ARIA attribute `%s`. ARIA attributes follow the pattern aria-* and must be lowercase.', name);
|
|
|
|
warnedProperties[name] = true;
|
|
return true;
|
|
} // aria-* attributes should be lowercase; suggest the lowercase version.
|
|
|
|
|
|
if (name !== correctName) {
|
|
error('Invalid ARIA attribute `%s`. Did you mean `%s`?', name, correctName);
|
|
|
|
warnedProperties[name] = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (rARIA.test(name)) {
|
|
var lowerCasedName = name.toLowerCase();
|
|
var standardName = ariaProperties.hasOwnProperty(lowerCasedName) ? lowerCasedName : null; // If this is an aria-* attribute, but is not listed in the known DOM
|
|
// DOM properties, then it is an invalid aria-* attribute.
|
|
|
|
if (standardName == null) {
|
|
warnedProperties[name] = true;
|
|
return false;
|
|
} // aria-* attributes should be lowercase; suggest the lowercase version.
|
|
|
|
|
|
if (name !== standardName) {
|
|
error('Unknown ARIA attribute `%s`. Did you mean `%s`?', name, standardName);
|
|
|
|
warnedProperties[name] = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function warnInvalidARIAProps(type, props) {
|
|
{
|
|
var invalidProps = [];
|
|
|
|
for (var key in props) {
|
|
var isValid = validateProperty(type, key);
|
|
|
|
if (!isValid) {
|
|
invalidProps.push(key);
|
|
}
|
|
}
|
|
|
|
var unknownPropString = invalidProps.map(function (prop) {
|
|
return '`' + prop + '`';
|
|
}).join(', ');
|
|
|
|
if (invalidProps.length === 1) {
|
|
error('Invalid aria prop %s on <%s> tag. ' + 'For details, see https://reactjs.org/link/invalid-aria-props', unknownPropString, type);
|
|
} else if (invalidProps.length > 1) {
|
|
error('Invalid aria props %s on <%s> tag. ' + 'For details, see https://reactjs.org/link/invalid-aria-props', unknownPropString, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateProperties(type, props) {
|
|
if (isCustomComponent(type, props)) {
|
|
return;
|
|
}
|
|
|
|
warnInvalidARIAProps(type, props);
|
|
}
|
|
|
|
var didWarnValueNull = false;
|
|
function validateProperties$1(type, props) {
|
|
{
|
|
if (type !== 'input' && type !== 'textarea' && type !== 'select') {
|
|
return;
|
|
}
|
|
|
|
if (props != null && props.value === null && !didWarnValueNull) {
|
|
didWarnValueNull = true;
|
|
|
|
if (type === 'select' && props.multiple) {
|
|
error('`value` prop on `%s` should not be null. ' + 'Consider using an empty array when `multiple` is set to `true` ' + 'to clear the component or `undefined` for uncontrolled components.', type);
|
|
} else {
|
|
error('`value` prop on `%s` should not be null. ' + 'Consider using an empty string to clear the component or `undefined` ' + 'for uncontrolled components.', type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// When adding attributes to the HTML or SVG allowed attribute list, be sure to
|
|
// also add them to this module to ensure casing and incorrect name
|
|
// warnings.
|
|
var possibleStandardNames = {
|
|
// HTML
|
|
accept: 'accept',
|
|
acceptcharset: 'acceptCharset',
|
|
'accept-charset': 'acceptCharset',
|
|
accesskey: 'accessKey',
|
|
action: 'action',
|
|
allowfullscreen: 'allowFullScreen',
|
|
alt: 'alt',
|
|
as: 'as',
|
|
async: 'async',
|
|
autocapitalize: 'autoCapitalize',
|
|
autocomplete: 'autoComplete',
|
|
autocorrect: 'autoCorrect',
|
|
autofocus: 'autoFocus',
|
|
autoplay: 'autoPlay',
|
|
autosave: 'autoSave',
|
|
capture: 'capture',
|
|
cellpadding: 'cellPadding',
|
|
cellspacing: 'cellSpacing',
|
|
challenge: 'challenge',
|
|
charset: 'charSet',
|
|
checked: 'checked',
|
|
children: 'children',
|
|
cite: 'cite',
|
|
class: 'className',
|
|
classid: 'classID',
|
|
classname: 'className',
|
|
cols: 'cols',
|
|
colspan: 'colSpan',
|
|
content: 'content',
|
|
contenteditable: 'contentEditable',
|
|
contextmenu: 'contextMenu',
|
|
controls: 'controls',
|
|
controlslist: 'controlsList',
|
|
coords: 'coords',
|
|
crossorigin: 'crossOrigin',
|
|
dangerouslysetinnerhtml: 'dangerouslySetInnerHTML',
|
|
data: 'data',
|
|
datetime: 'dateTime',
|
|
default: 'default',
|
|
defaultchecked: 'defaultChecked',
|
|
defaultvalue: 'defaultValue',
|
|
defer: 'defer',
|
|
dir: 'dir',
|
|
disabled: 'disabled',
|
|
disablepictureinpicture: 'disablePictureInPicture',
|
|
disableremoteplayback: 'disableRemotePlayback',
|
|
download: 'download',
|
|
draggable: 'draggable',
|
|
enctype: 'encType',
|
|
enterkeyhint: 'enterKeyHint',
|
|
for: 'htmlFor',
|
|
form: 'form',
|
|
formmethod: 'formMethod',
|
|
formaction: 'formAction',
|
|
formenctype: 'formEncType',
|
|
formnovalidate: 'formNoValidate',
|
|
formtarget: 'formTarget',
|
|
frameborder: 'frameBorder',
|
|
headers: 'headers',
|
|
height: 'height',
|
|
hidden: 'hidden',
|
|
high: 'high',
|
|
href: 'href',
|
|
hreflang: 'hrefLang',
|
|
htmlfor: 'htmlFor',
|
|
httpequiv: 'httpEquiv',
|
|
'http-equiv': 'httpEquiv',
|
|
icon: 'icon',
|
|
id: 'id',
|
|
imagesizes: 'imageSizes',
|
|
imagesrcset: 'imageSrcSet',
|
|
innerhtml: 'innerHTML',
|
|
inputmode: 'inputMode',
|
|
integrity: 'integrity',
|
|
is: 'is',
|
|
itemid: 'itemID',
|
|
itemprop: 'itemProp',
|
|
itemref: 'itemRef',
|
|
itemscope: 'itemScope',
|
|
itemtype: 'itemType',
|
|
keyparams: 'keyParams',
|
|
keytype: 'keyType',
|
|
kind: 'kind',
|
|
label: 'label',
|
|
lang: 'lang',
|
|
list: 'list',
|
|
loop: 'loop',
|
|
low: 'low',
|
|
manifest: 'manifest',
|
|
marginwidth: 'marginWidth',
|
|
marginheight: 'marginHeight',
|
|
max: 'max',
|
|
maxlength: 'maxLength',
|
|
media: 'media',
|
|
mediagroup: 'mediaGroup',
|
|
method: 'method',
|
|
min: 'min',
|
|
minlength: 'minLength',
|
|
multiple: 'multiple',
|
|
muted: 'muted',
|
|
name: 'name',
|
|
nomodule: 'noModule',
|
|
nonce: 'nonce',
|
|
novalidate: 'noValidate',
|
|
open: 'open',
|
|
optimum: 'optimum',
|
|
pattern: 'pattern',
|
|
placeholder: 'placeholder',
|
|
playsinline: 'playsInline',
|
|
poster: 'poster',
|
|
preload: 'preload',
|
|
profile: 'profile',
|
|
radiogroup: 'radioGroup',
|
|
readonly: 'readOnly',
|
|
referrerpolicy: 'referrerPolicy',
|
|
rel: 'rel',
|
|
required: 'required',
|
|
reversed: 'reversed',
|
|
role: 'role',
|
|
rows: 'rows',
|
|
rowspan: 'rowSpan',
|
|
sandbox: 'sandbox',
|
|
scope: 'scope',
|
|
scoped: 'scoped',
|
|
scrolling: 'scrolling',
|
|
seamless: 'seamless',
|
|
selected: 'selected',
|
|
shape: 'shape',
|
|
size: 'size',
|
|
sizes: 'sizes',
|
|
span: 'span',
|
|
spellcheck: 'spellCheck',
|
|
src: 'src',
|
|
srcdoc: 'srcDoc',
|
|
srclang: 'srcLang',
|
|
srcset: 'srcSet',
|
|
start: 'start',
|
|
step: 'step',
|
|
style: 'style',
|
|
summary: 'summary',
|
|
tabindex: 'tabIndex',
|
|
target: 'target',
|
|
title: 'title',
|
|
type: 'type',
|
|
usemap: 'useMap',
|
|
value: 'value',
|
|
width: 'width',
|
|
wmode: 'wmode',
|
|
wrap: 'wrap',
|
|
// SVG
|
|
about: 'about',
|
|
accentheight: 'accentHeight',
|
|
'accent-height': 'accentHeight',
|
|
accumulate: 'accumulate',
|
|
additive: 'additive',
|
|
alignmentbaseline: 'alignmentBaseline',
|
|
'alignment-baseline': 'alignmentBaseline',
|
|
allowreorder: 'allowReorder',
|
|
alphabetic: 'alphabetic',
|
|
amplitude: 'amplitude',
|
|
arabicform: 'arabicForm',
|
|
'arabic-form': 'arabicForm',
|
|
ascent: 'ascent',
|
|
attributename: 'attributeName',
|
|
attributetype: 'attributeType',
|
|
autoreverse: 'autoReverse',
|
|
azimuth: 'azimuth',
|
|
basefrequency: 'baseFrequency',
|
|
baselineshift: 'baselineShift',
|
|
'baseline-shift': 'baselineShift',
|
|
baseprofile: 'baseProfile',
|
|
bbox: 'bbox',
|
|
begin: 'begin',
|
|
bias: 'bias',
|
|
by: 'by',
|
|
calcmode: 'calcMode',
|
|
capheight: 'capHeight',
|
|
'cap-height': 'capHeight',
|
|
clip: 'clip',
|
|
clippath: 'clipPath',
|
|
'clip-path': 'clipPath',
|
|
clippathunits: 'clipPathUnits',
|
|
cliprule: 'clipRule',
|
|
'clip-rule': 'clipRule',
|
|
color: 'color',
|
|
colorinterpolation: 'colorInterpolation',
|
|
'color-interpolation': 'colorInterpolation',
|
|
colorinterpolationfilters: 'colorInterpolationFilters',
|
|
'color-interpolation-filters': 'colorInterpolationFilters',
|
|
colorprofile: 'colorProfile',
|
|
'color-profile': 'colorProfile',
|
|
colorrendering: 'colorRendering',
|
|
'color-rendering': 'colorRendering',
|
|
contentscripttype: 'contentScriptType',
|
|
contentstyletype: 'contentStyleType',
|
|
cursor: 'cursor',
|
|
cx: 'cx',
|
|
cy: 'cy',
|
|
d: 'd',
|
|
datatype: 'datatype',
|
|
decelerate: 'decelerate',
|
|
descent: 'descent',
|
|
diffuseconstant: 'diffuseConstant',
|
|
direction: 'direction',
|
|
display: 'display',
|
|
divisor: 'divisor',
|
|
dominantbaseline: 'dominantBaseline',
|
|
'dominant-baseline': 'dominantBaseline',
|
|
dur: 'dur',
|
|
dx: 'dx',
|
|
dy: 'dy',
|
|
edgemode: 'edgeMode',
|
|
elevation: 'elevation',
|
|
enablebackground: 'enableBackground',
|
|
'enable-background': 'enableBackground',
|
|
end: 'end',
|
|
exponent: 'exponent',
|
|
externalresourcesrequired: 'externalResourcesRequired',
|
|
fill: 'fill',
|
|
fillopacity: 'fillOpacity',
|
|
'fill-opacity': 'fillOpacity',
|
|
fillrule: 'fillRule',
|
|
'fill-rule': 'fillRule',
|
|
filter: 'filter',
|
|
filterres: 'filterRes',
|
|
filterunits: 'filterUnits',
|
|
floodopacity: 'floodOpacity',
|
|
'flood-opacity': 'floodOpacity',
|
|
floodcolor: 'floodColor',
|
|
'flood-color': 'floodColor',
|
|
focusable: 'focusable',
|
|
fontfamily: 'fontFamily',
|
|
'font-family': 'fontFamily',
|
|
fontsize: 'fontSize',
|
|
'font-size': 'fontSize',
|
|
fontsizeadjust: 'fontSizeAdjust',
|
|
'font-size-adjust': 'fontSizeAdjust',
|
|
fontstretch: 'fontStretch',
|
|
'font-stretch': 'fontStretch',
|
|
fontstyle: 'fontStyle',
|
|
'font-style': 'fontStyle',
|
|
fontvariant: 'fontVariant',
|
|
'font-variant': 'fontVariant',
|
|
fontweight: 'fontWeight',
|
|
'font-weight': 'fontWeight',
|
|
format: 'format',
|
|
from: 'from',
|
|
fx: 'fx',
|
|
fy: 'fy',
|
|
g1: 'g1',
|
|
g2: 'g2',
|
|
glyphname: 'glyphName',
|
|
'glyph-name': 'glyphName',
|
|
glyphorientationhorizontal: 'glyphOrientationHorizontal',
|
|
'glyph-orientation-horizontal': 'glyphOrientationHorizontal',
|
|
glyphorientationvertical: 'glyphOrientationVertical',
|
|
'glyph-orientation-vertical': 'glyphOrientationVertical',
|
|
glyphref: 'glyphRef',
|
|
gradienttransform: 'gradientTransform',
|
|
gradientunits: 'gradientUnits',
|
|
hanging: 'hanging',
|
|
horizadvx: 'horizAdvX',
|
|
'horiz-adv-x': 'horizAdvX',
|
|
horizoriginx: 'horizOriginX',
|
|
'horiz-origin-x': 'horizOriginX',
|
|
ideographic: 'ideographic',
|
|
imagerendering: 'imageRendering',
|
|
'image-rendering': 'imageRendering',
|
|
in2: 'in2',
|
|
in: 'in',
|
|
inlist: 'inlist',
|
|
intercept: 'intercept',
|
|
k1: 'k1',
|
|
k2: 'k2',
|
|
k3: 'k3',
|
|
k4: 'k4',
|
|
k: 'k',
|
|
kernelmatrix: 'kernelMatrix',
|
|
kernelunitlength: 'kernelUnitLength',
|
|
kerning: 'kerning',
|
|
keypoints: 'keyPoints',
|
|
keysplines: 'keySplines',
|
|
keytimes: 'keyTimes',
|
|
lengthadjust: 'lengthAdjust',
|
|
letterspacing: 'letterSpacing',
|
|
'letter-spacing': 'letterSpacing',
|
|
lightingcolor: 'lightingColor',
|
|
'lighting-color': 'lightingColor',
|
|
limitingconeangle: 'limitingConeAngle',
|
|
local: 'local',
|
|
markerend: 'markerEnd',
|
|
'marker-end': 'markerEnd',
|
|
markerheight: 'markerHeight',
|
|
markermid: 'markerMid',
|
|
'marker-mid': 'markerMid',
|
|
markerstart: 'markerStart',
|
|
'marker-start': 'markerStart',
|
|
markerunits: 'markerUnits',
|
|
markerwidth: 'markerWidth',
|
|
mask: 'mask',
|
|
maskcontentunits: 'maskContentUnits',
|
|
maskunits: 'maskUnits',
|
|
mathematical: 'mathematical',
|
|
mode: 'mode',
|
|
numoctaves: 'numOctaves',
|
|
offset: 'offset',
|
|
opacity: 'opacity',
|
|
operator: 'operator',
|
|
order: 'order',
|
|
orient: 'orient',
|
|
orientation: 'orientation',
|
|
origin: 'origin',
|
|
overflow: 'overflow',
|
|
overlineposition: 'overlinePosition',
|
|
'overline-position': 'overlinePosition',
|
|
overlinethickness: 'overlineThickness',
|
|
'overline-thickness': 'overlineThickness',
|
|
paintorder: 'paintOrder',
|
|
'paint-order': 'paintOrder',
|
|
panose1: 'panose1',
|
|
'panose-1': 'panose1',
|
|
pathlength: 'pathLength',
|
|
patterncontentunits: 'patternContentUnits',
|
|
patterntransform: 'patternTransform',
|
|
patternunits: 'patternUnits',
|
|
pointerevents: 'pointerEvents',
|
|
'pointer-events': 'pointerEvents',
|
|
points: 'points',
|
|
pointsatx: 'pointsAtX',
|
|
pointsaty: 'pointsAtY',
|
|
pointsatz: 'pointsAtZ',
|
|
prefix: 'prefix',
|
|
preservealpha: 'preserveAlpha',
|
|
preserveaspectratio: 'preserveAspectRatio',
|
|
primitiveunits: 'primitiveUnits',
|
|
property: 'property',
|
|
r: 'r',
|
|
radius: 'radius',
|
|
refx: 'refX',
|
|
refy: 'refY',
|
|
renderingintent: 'renderingIntent',
|
|
'rendering-intent': 'renderingIntent',
|
|
repeatcount: 'repeatCount',
|
|
repeatdur: 'repeatDur',
|
|
requiredextensions: 'requiredExtensions',
|
|
requiredfeatures: 'requiredFeatures',
|
|
resource: 'resource',
|
|
restart: 'restart',
|
|
result: 'result',
|
|
results: 'results',
|
|
rotate: 'rotate',
|
|
rx: 'rx',
|
|
ry: 'ry',
|
|
scale: 'scale',
|
|
security: 'security',
|
|
seed: 'seed',
|
|
shaperendering: 'shapeRendering',
|
|
'shape-rendering': 'shapeRendering',
|
|
slope: 'slope',
|
|
spacing: 'spacing',
|
|
specularconstant: 'specularConstant',
|
|
specularexponent: 'specularExponent',
|
|
speed: 'speed',
|
|
spreadmethod: 'spreadMethod',
|
|
startoffset: 'startOffset',
|
|
stddeviation: 'stdDeviation',
|
|
stemh: 'stemh',
|
|
stemv: 'stemv',
|
|
stitchtiles: 'stitchTiles',
|
|
stopcolor: 'stopColor',
|
|
'stop-color': 'stopColor',
|
|
stopopacity: 'stopOpacity',
|
|
'stop-opacity': 'stopOpacity',
|
|
strikethroughposition: 'strikethroughPosition',
|
|
'strikethrough-position': 'strikethroughPosition',
|
|
strikethroughthickness: 'strikethroughThickness',
|
|
'strikethrough-thickness': 'strikethroughThickness',
|
|
string: 'string',
|
|
stroke: 'stroke',
|
|
strokedasharray: 'strokeDasharray',
|
|
'stroke-dasharray': 'strokeDasharray',
|
|
strokedashoffset: 'strokeDashoffset',
|
|
'stroke-dashoffset': 'strokeDashoffset',
|
|
strokelinecap: 'strokeLinecap',
|
|
'stroke-linecap': 'strokeLinecap',
|
|
strokelinejoin: 'strokeLinejoin',
|
|
'stroke-linejoin': 'strokeLinejoin',
|
|
strokemiterlimit: 'strokeMiterlimit',
|
|
'stroke-miterlimit': 'strokeMiterlimit',
|
|
strokewidth: 'strokeWidth',
|
|
'stroke-width': 'strokeWidth',
|
|
strokeopacity: 'strokeOpacity',
|
|
'stroke-opacity': 'strokeOpacity',
|
|
suppresscontenteditablewarning: 'suppressContentEditableWarning',
|
|
suppresshydrationwarning: 'suppressHydrationWarning',
|
|
surfacescale: 'surfaceScale',
|
|
systemlanguage: 'systemLanguage',
|
|
tablevalues: 'tableValues',
|
|
targetx: 'targetX',
|
|
targety: 'targetY',
|
|
textanchor: 'textAnchor',
|
|
'text-anchor': 'textAnchor',
|
|
textdecoration: 'textDecoration',
|
|
'text-decoration': 'textDecoration',
|
|
textlength: 'textLength',
|
|
textrendering: 'textRendering',
|
|
'text-rendering': 'textRendering',
|
|
to: 'to',
|
|
transform: 'transform',
|
|
typeof: 'typeof',
|
|
u1: 'u1',
|
|
u2: 'u2',
|
|
underlineposition: 'underlinePosition',
|
|
'underline-position': 'underlinePosition',
|
|
underlinethickness: 'underlineThickness',
|
|
'underline-thickness': 'underlineThickness',
|
|
unicode: 'unicode',
|
|
unicodebidi: 'unicodeBidi',
|
|
'unicode-bidi': 'unicodeBidi',
|
|
unicoderange: 'unicodeRange',
|
|
'unicode-range': 'unicodeRange',
|
|
unitsperem: 'unitsPerEm',
|
|
'units-per-em': 'unitsPerEm',
|
|
unselectable: 'unselectable',
|
|
valphabetic: 'vAlphabetic',
|
|
'v-alphabetic': 'vAlphabetic',
|
|
values: 'values',
|
|
vectoreffect: 'vectorEffect',
|
|
'vector-effect': 'vectorEffect',
|
|
version: 'version',
|
|
vertadvy: 'vertAdvY',
|
|
'vert-adv-y': 'vertAdvY',
|
|
vertoriginx: 'vertOriginX',
|
|
'vert-origin-x': 'vertOriginX',
|
|
vertoriginy: 'vertOriginY',
|
|
'vert-origin-y': 'vertOriginY',
|
|
vhanging: 'vHanging',
|
|
'v-hanging': 'vHanging',
|
|
videographic: 'vIdeographic',
|
|
'v-ideographic': 'vIdeographic',
|
|
viewbox: 'viewBox',
|
|
viewtarget: 'viewTarget',
|
|
visibility: 'visibility',
|
|
vmathematical: 'vMathematical',
|
|
'v-mathematical': 'vMathematical',
|
|
vocab: 'vocab',
|
|
widths: 'widths',
|
|
wordspacing: 'wordSpacing',
|
|
'word-spacing': 'wordSpacing',
|
|
writingmode: 'writingMode',
|
|
'writing-mode': 'writingMode',
|
|
x1: 'x1',
|
|
x2: 'x2',
|
|
x: 'x',
|
|
xchannelselector: 'xChannelSelector',
|
|
xheight: 'xHeight',
|
|
'x-height': 'xHeight',
|
|
xlinkactuate: 'xlinkActuate',
|
|
'xlink:actuate': 'xlinkActuate',
|
|
xlinkarcrole: 'xlinkArcrole',
|
|
'xlink:arcrole': 'xlinkArcrole',
|
|
xlinkhref: 'xlinkHref',
|
|
'xlink:href': 'xlinkHref',
|
|
xlinkrole: 'xlinkRole',
|
|
'xlink:role': 'xlinkRole',
|
|
xlinkshow: 'xlinkShow',
|
|
'xlink:show': 'xlinkShow',
|
|
xlinktitle: 'xlinkTitle',
|
|
'xlink:title': 'xlinkTitle',
|
|
xlinktype: 'xlinkType',
|
|
'xlink:type': 'xlinkType',
|
|
xmlbase: 'xmlBase',
|
|
'xml:base': 'xmlBase',
|
|
xmllang: 'xmlLang',
|
|
'xml:lang': 'xmlLang',
|
|
xmlns: 'xmlns',
|
|
'xml:space': 'xmlSpace',
|
|
xmlnsxlink: 'xmlnsXlink',
|
|
'xmlns:xlink': 'xmlnsXlink',
|
|
xmlspace: 'xmlSpace',
|
|
y1: 'y1',
|
|
y2: 'y2',
|
|
y: 'y',
|
|
ychannelselector: 'yChannelSelector',
|
|
z: 'z',
|
|
zoomandpan: 'zoomAndPan'
|
|
};
|
|
|
|
var validateProperty$1 = function () {};
|
|
|
|
{
|
|
var warnedProperties$1 = {};
|
|
var EVENT_NAME_REGEX = /^on./;
|
|
var INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/;
|
|
var rARIA$1 = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$');
|
|
var rARIACamel$1 = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$');
|
|
|
|
validateProperty$1 = function (tagName, name, value, eventRegistry) {
|
|
if (hasOwnProperty.call(warnedProperties$1, name) && warnedProperties$1[name]) {
|
|
return true;
|
|
}
|
|
|
|
var lowerCasedName = name.toLowerCase();
|
|
|
|
if (lowerCasedName === 'onfocusin' || lowerCasedName === 'onfocusout') {
|
|
error('React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' + 'All React events are normalized to bubble, so onFocusIn and onFocusOut ' + 'are not needed/supported by React.');
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
} // We can't rely on the event system being injected on the server.
|
|
|
|
|
|
if (eventRegistry != null) {
|
|
var registrationNameDependencies = eventRegistry.registrationNameDependencies,
|
|
possibleRegistrationNames = eventRegistry.possibleRegistrationNames;
|
|
|
|
if (registrationNameDependencies.hasOwnProperty(name)) {
|
|
return true;
|
|
}
|
|
|
|
var registrationName = possibleRegistrationNames.hasOwnProperty(lowerCasedName) ? possibleRegistrationNames[lowerCasedName] : null;
|
|
|
|
if (registrationName != null) {
|
|
error('Invalid event handler property `%s`. Did you mean `%s`?', name, registrationName);
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
|
|
if (EVENT_NAME_REGEX.test(name)) {
|
|
error('Unknown event handler property `%s`. It will be ignored.', name);
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
} else if (EVENT_NAME_REGEX.test(name)) {
|
|
// If no event plugins have been injected, we are in a server environment.
|
|
// So we can't tell if the event name is correct for sure, but we can filter
|
|
// out known bad ones like `onclick`. We can't suggest a specific replacement though.
|
|
if (INVALID_EVENT_NAME_REGEX.test(name)) {
|
|
error('Invalid event handler property `%s`. ' + 'React events use the camelCase naming convention, for example `onClick`.', name);
|
|
}
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
} // Let the ARIA attribute hook validate ARIA attributes
|
|
|
|
|
|
if (rARIA$1.test(name) || rARIACamel$1.test(name)) {
|
|
return true;
|
|
}
|
|
|
|
if (lowerCasedName === 'innerhtml') {
|
|
error('Directly setting property `innerHTML` is not permitted. ' + 'For more information, lookup documentation on `dangerouslySetInnerHTML`.');
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
|
|
if (lowerCasedName === 'aria') {
|
|
error('The `aria` attribute is reserved for future use in React. ' + 'Pass individual `aria-` attributes instead.');
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
|
|
if (lowerCasedName === 'is' && value !== null && value !== undefined && typeof value !== 'string') {
|
|
error('Received a `%s` for a string attribute `is`. If this is expected, cast ' + 'the value to a string.', typeof value);
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
|
|
if (typeof value === 'number' && isNaN(value)) {
|
|
error('Received NaN for the `%s` attribute. If this is expected, cast ' + 'the value to a string.', name);
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
|
|
var propertyInfo = getPropertyInfo(name);
|
|
var isReserved = propertyInfo !== null && propertyInfo.type === RESERVED; // Known attributes should match the casing specified in the property config.
|
|
|
|
if (possibleStandardNames.hasOwnProperty(lowerCasedName)) {
|
|
var standardName = possibleStandardNames[lowerCasedName];
|
|
|
|
if (standardName !== name) {
|
|
error('Invalid DOM property `%s`. Did you mean `%s`?', name, standardName);
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
} else if (!isReserved && name !== lowerCasedName) {
|
|
// Unknown attributes should have lowercase casing since that's how they
|
|
// will be cased anyway with server rendering.
|
|
error('React does not recognize the `%s` prop on a DOM element. If you ' + 'intentionally want it to appear in the DOM as a custom ' + 'attribute, spell it as lowercase `%s` instead. ' + 'If you accidentally passed it from a parent component, remove ' + 'it from the DOM element.', name, lowerCasedName);
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
|
|
if (typeof value === 'boolean' && shouldRemoveAttributeWithWarning(name, value, propertyInfo, false)) {
|
|
if (value) {
|
|
error('Received `%s` for a non-boolean attribute `%s`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + '%s="%s" or %s={value.toString()}.', value, name, name, value, name);
|
|
} else {
|
|
error('Received `%s` for a non-boolean attribute `%s`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + '%s="%s" or %s={value.toString()}.\n\n' + 'If you used to conditionally omit it with %s={condition && value}, ' + 'pass %s={condition ? value : undefined} instead.', value, name, name, value, name, name, name);
|
|
}
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
} // Now that we've validated casing, do not validate
|
|
// data types for reserved props
|
|
|
|
|
|
if (isReserved) {
|
|
return true;
|
|
} // Warn when a known attribute is a bad type
|
|
|
|
|
|
if (shouldRemoveAttributeWithWarning(name, value, propertyInfo, false)) {
|
|
warnedProperties$1[name] = true;
|
|
return false;
|
|
} // Warn when passing the strings 'false' or 'true' into a boolean prop
|
|
|
|
|
|
if ((value === 'false' || value === 'true') && propertyInfo !== null && propertyInfo.type === BOOLEAN) {
|
|
error('Received the string `%s` for the boolean attribute `%s`. ' + '%s ' + 'Did you mean %s={%s}?', value, name, value === 'false' ? 'The browser will interpret it as a truthy value.' : 'Although this works, it will not work as expected if you pass the string "false".', name, value);
|
|
|
|
warnedProperties$1[name] = true;
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
}
|
|
|
|
var warnUnknownProperties = function (type, props, eventRegistry) {
|
|
{
|
|
var unknownProps = [];
|
|
|
|
for (var key in props) {
|
|
var isValid = validateProperty$1(type, key, props[key], eventRegistry);
|
|
|
|
if (!isValid) {
|
|
unknownProps.push(key);
|
|
}
|
|
}
|
|
|
|
var unknownPropString = unknownProps.map(function (prop) {
|
|
return '`' + prop + '`';
|
|
}).join(', ');
|
|
|
|
if (unknownProps.length === 1) {
|
|
error('Invalid value for prop %s on <%s> tag. Either remove it from the element, ' + 'or pass a string or number value to keep it in the DOM. ' + 'For details, see https://reactjs.org/link/attribute-behavior ', unknownPropString, type);
|
|
} else if (unknownProps.length > 1) {
|
|
error('Invalid values for props %s on <%s> tag. Either remove them from the element, ' + 'or pass a string or number value to keep them in the DOM. ' + 'For details, see https://reactjs.org/link/attribute-behavior ', unknownPropString, type);
|
|
}
|
|
}
|
|
};
|
|
|
|
function validateProperties$2(type, props, eventRegistry) {
|
|
if (isCustomComponent(type, props)) {
|
|
return;
|
|
}
|
|
|
|
warnUnknownProperties(type, props, eventRegistry);
|
|
}
|
|
|
|
var warnValidStyle = function () {};
|
|
|
|
{
|
|
// 'msTransform' is correct, but the other prefixes should be capitalized
|
|
var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
|
|
var msPattern = /^-ms-/;
|
|
var hyphenPattern = /-(.)/g; // style values shouldn't contain a semicolon
|
|
|
|
var badStyleValueWithSemicolonPattern = /;\s*$/;
|
|
var warnedStyleNames = {};
|
|
var warnedStyleValues = {};
|
|
var warnedForNaNValue = false;
|
|
var warnedForInfinityValue = false;
|
|
|
|
var camelize = function (string) {
|
|
return string.replace(hyphenPattern, function (_, character) {
|
|
return character.toUpperCase();
|
|
});
|
|
};
|
|
|
|
var warnHyphenatedStyleName = function (name) {
|
|
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
|
|
return;
|
|
}
|
|
|
|
warnedStyleNames[name] = true;
|
|
|
|
error('Unsupported style property %s. Did you mean %s?', name, // As Andi Smith suggests
|
|
// (http://www.andismith.com/blog/2012/02/modernizr-prefixed/), an `-ms` prefix
|
|
// is converted to lowercase `ms`.
|
|
camelize(name.replace(msPattern, 'ms-')));
|
|
};
|
|
|
|
var warnBadVendoredStyleName = function (name) {
|
|
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
|
|
return;
|
|
}
|
|
|
|
warnedStyleNames[name] = true;
|
|
|
|
error('Unsupported vendor-prefixed style property %s. Did you mean %s?', name, name.charAt(0).toUpperCase() + name.slice(1));
|
|
};
|
|
|
|
var warnStyleValueWithSemicolon = function (name, value) {
|
|
if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
|
|
return;
|
|
}
|
|
|
|
warnedStyleValues[value] = true;
|
|
|
|
error("Style property values shouldn't contain a semicolon. " + 'Try "%s: %s" instead.', name, value.replace(badStyleValueWithSemicolonPattern, ''));
|
|
};
|
|
|
|
var warnStyleValueIsNaN = function (name, value) {
|
|
if (warnedForNaNValue) {
|
|
return;
|
|
}
|
|
|
|
warnedForNaNValue = true;
|
|
|
|
error('`NaN` is an invalid value for the `%s` css style property.', name);
|
|
};
|
|
|
|
var warnStyleValueIsInfinity = function (name, value) {
|
|
if (warnedForInfinityValue) {
|
|
return;
|
|
}
|
|
|
|
warnedForInfinityValue = true;
|
|
|
|
error('`Infinity` is an invalid value for the `%s` css style property.', name);
|
|
};
|
|
|
|
warnValidStyle = function (name, value) {
|
|
if (name.indexOf('-') > -1) {
|
|
warnHyphenatedStyleName(name);
|
|
} else if (badVendoredStyleNamePattern.test(name)) {
|
|
warnBadVendoredStyleName(name);
|
|
} else if (badStyleValueWithSemicolonPattern.test(value)) {
|
|
warnStyleValueWithSemicolon(name, value);
|
|
}
|
|
|
|
if (typeof value === 'number') {
|
|
if (isNaN(value)) {
|
|
warnStyleValueIsNaN(name, value);
|
|
} else if (!isFinite(value)) {
|
|
warnStyleValueIsInfinity(name, value);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
var warnValidStyle$1 = warnValidStyle;
|
|
|
|
// code copied and modified from escape-html
|
|
var matchHtmlRegExp = /["'&<>]/;
|
|
/**
|
|
* Escapes special characters and HTML entities in a given html string.
|
|
*
|
|
* @param {string} string HTML string to escape for later insertion
|
|
* @return {string}
|
|
* @public
|
|
*/
|
|
|
|
function escapeHtml(string) {
|
|
{
|
|
checkHtmlStringCoercion(string);
|
|
}
|
|
|
|
var str = '' + string;
|
|
var match = matchHtmlRegExp.exec(str);
|
|
|
|
if (!match) {
|
|
return str;
|
|
}
|
|
|
|
var escape;
|
|
var html = '';
|
|
var index;
|
|
var lastIndex = 0;
|
|
|
|
for (index = match.index; index < str.length; index++) {
|
|
switch (str.charCodeAt(index)) {
|
|
case 34:
|
|
// "
|
|
escape = '"';
|
|
break;
|
|
|
|
case 38:
|
|
// &
|
|
escape = '&';
|
|
break;
|
|
|
|
case 39:
|
|
// '
|
|
escape = '''; // modified from escape-html; used to be '''
|
|
|
|
break;
|
|
|
|
case 60:
|
|
// <
|
|
escape = '<';
|
|
break;
|
|
|
|
case 62:
|
|
// >
|
|
escape = '>';
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
if (lastIndex !== index) {
|
|
html += str.substring(lastIndex, index);
|
|
}
|
|
|
|
lastIndex = index + 1;
|
|
html += escape;
|
|
}
|
|
|
|
return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
|
|
} // end code copied and modified from escape-html
|
|
|
|
/**
|
|
* Escapes text to prevent scripting attacks.
|
|
*
|
|
* @param {*} text Text value to escape.
|
|
* @return {string} An escaped string.
|
|
*/
|
|
|
|
|
|
function escapeTextForBrowser(text) {
|
|
if (typeof text === 'boolean' || typeof text === 'number') {
|
|
// this shortcircuit helps perf for types that we know will never have
|
|
// special characters, especially given that this function is used often
|
|
// for numeric dom ids.
|
|
return '' + text;
|
|
}
|
|
|
|
return escapeHtml(text);
|
|
}
|
|
|
|
var uppercasePattern = /([A-Z])/g;
|
|
var msPattern$1 = /^ms-/;
|
|
/**
|
|
* Hyphenates a camelcased CSS property name, for example:
|
|
*
|
|
* > hyphenateStyleName('backgroundColor')
|
|
* < "background-color"
|
|
* > hyphenateStyleName('MozTransition')
|
|
* < "-moz-transition"
|
|
* > hyphenateStyleName('msTransition')
|
|
* < "-ms-transition"
|
|
*
|
|
* As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix
|
|
* is converted to `-ms-`.
|
|
*/
|
|
|
|
function hyphenateStyleName(name) {
|
|
return name.replace(uppercasePattern, '-$1').toLowerCase().replace(msPattern$1, '-ms-');
|
|
}
|
|
|
|
// and any newline or tab are filtered out as if they're not part of the URL.
|
|
// https://url.spec.whatwg.org/#url-parsing
|
|
// Tab or newline are defined as \r\n\t:
|
|
// https://infra.spec.whatwg.org/#ascii-tab-or-newline
|
|
// A C0 control is a code point in the range \u0000 NULL to \u001F
|
|
// INFORMATION SEPARATOR ONE, inclusive:
|
|
// https://infra.spec.whatwg.org/#c0-control-or-space
|
|
|
|
/* eslint-disable max-len */
|
|
|
|
var isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i;
|
|
var didWarn = false;
|
|
|
|
function sanitizeURL(url) {
|
|
{
|
|
if (!didWarn && isJavaScriptProtocol.test(url)) {
|
|
didWarn = true;
|
|
|
|
error('A future version of React will block javascript: URLs as a security precaution. ' + 'Use event handlers instead if you can. If you need to generate unsafe HTML try ' + 'using dangerouslySetInnerHTML instead. React was passed %s.', JSON.stringify(url));
|
|
}
|
|
}
|
|
}
|
|
|
|
var isArrayImpl = Array.isArray; // eslint-disable-next-line no-redeclare
|
|
|
|
function isArray(a) {
|
|
return isArrayImpl(a);
|
|
}
|
|
|
|
var startInlineScript = stringToPrecomputedChunk('<script>');
|
|
var endInlineScript = stringToPrecomputedChunk('</script>');
|
|
var startScriptSrc = stringToPrecomputedChunk('<script src="');
|
|
var startModuleSrc = stringToPrecomputedChunk('<script type="module" src="');
|
|
var endAsyncScript = stringToPrecomputedChunk('" async=""></script>');
|
|
/**
|
|
* This escaping function is designed to work with bootstrapScriptContent only.
|
|
* because we know we are escaping the entire script. We can avoid for instance
|
|
* escaping html comment string sequences that are valid javascript as well because
|
|
* if there are no sebsequent <script sequences the html parser will never enter
|
|
* script data double escaped state (see: https://www.w3.org/TR/html53/syntax.html#script-data-double-escaped-state)
|
|
*
|
|
* While untrusted script content should be made safe before using this api it will
|
|
* ensure that the script cannot be early terminated or never terminated state
|
|
*/
|
|
|
|
function escapeBootstrapScriptContent(scriptText) {
|
|
{
|
|
checkHtmlStringCoercion(scriptText);
|
|
}
|
|
|
|
return ('' + scriptText).replace(scriptRegex, scriptReplacer);
|
|
}
|
|
|
|
var scriptRegex = /(<\/|<)(s)(cript)/gi;
|
|
|
|
var scriptReplacer = function (match, prefix, s, suffix) {
|
|
return "" + prefix + (s === 's' ? "\\u0073" : "\\u0053") + suffix;
|
|
}; // Allows us to keep track of what we've already written so we can refer back to it.
|
|
|
|
|
|
function createResponseState(identifierPrefix, nonce, bootstrapScriptContent, bootstrapScripts, bootstrapModules) {
|
|
var idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;
|
|
var inlineScriptWithNonce = nonce === undefined ? startInlineScript : stringToPrecomputedChunk('<script nonce="' + escapeTextForBrowser(nonce) + '">');
|
|
var bootstrapChunks = [];
|
|
|
|
if (bootstrapScriptContent !== undefined) {
|
|
bootstrapChunks.push(inlineScriptWithNonce, stringToChunk(escapeBootstrapScriptContent(bootstrapScriptContent)), endInlineScript);
|
|
}
|
|
|
|
if (bootstrapScripts !== undefined) {
|
|
for (var i = 0; i < bootstrapScripts.length; i++) {
|
|
bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(bootstrapScripts[i])), endAsyncScript);
|
|
}
|
|
}
|
|
|
|
if (bootstrapModules !== undefined) {
|
|
for (var _i = 0; _i < bootstrapModules.length; _i++) {
|
|
bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(bootstrapModules[_i])), endAsyncScript);
|
|
}
|
|
}
|
|
|
|
return {
|
|
bootstrapChunks: bootstrapChunks,
|
|
startInlineScript: inlineScriptWithNonce,
|
|
placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'),
|
|
segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),
|
|
boundaryPrefix: idPrefix + 'B:',
|
|
idPrefix: idPrefix,
|
|
nextSuspenseID: 0,
|
|
sentCompleteSegmentFunction: false,
|
|
sentCompleteBoundaryFunction: false,
|
|
sentClientRenderFunction: false
|
|
};
|
|
} // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion
|
|
// modes. We only include the variants as they matter for the sake of our purposes.
|
|
// We don't actually provide the namespace therefore we use constants instead of the string.
|
|
|
|
var ROOT_HTML_MODE = 0; // Used for the root most element tag.
|
|
|
|
var HTML_MODE = 1;
|
|
var SVG_MODE = 2;
|
|
var MATHML_MODE = 3;
|
|
var HTML_TABLE_MODE = 4;
|
|
var HTML_TABLE_BODY_MODE = 5;
|
|
var HTML_TABLE_ROW_MODE = 6;
|
|
var HTML_COLGROUP_MODE = 7; // We have a greater than HTML_TABLE_MODE check elsewhere. If you add more cases here, make sure it
|
|
// still makes sense
|
|
|
|
function createFormatContext(insertionMode, selectedValue) {
|
|
return {
|
|
insertionMode: insertionMode,
|
|
selectedValue: selectedValue
|
|
};
|
|
}
|
|
|
|
function createRootFormatContext(namespaceURI) {
|
|
var insertionMode = namespaceURI === 'http://www.w3.org/2000/svg' ? SVG_MODE : namespaceURI === 'http://www.w3.org/1998/Math/MathML' ? MATHML_MODE : ROOT_HTML_MODE;
|
|
return createFormatContext(insertionMode, null);
|
|
}
|
|
function getChildFormatContext(parentContext, type, props) {
|
|
switch (type) {
|
|
case 'select':
|
|
return createFormatContext(HTML_MODE, props.value != null ? props.value : props.defaultValue);
|
|
|
|
case 'svg':
|
|
return createFormatContext(SVG_MODE, null);
|
|
|
|
case 'math':
|
|
return createFormatContext(MATHML_MODE, null);
|
|
|
|
case 'foreignObject':
|
|
return createFormatContext(HTML_MODE, null);
|
|
// Table parents are special in that their children can only be created at all if they're
|
|
// wrapped in a table parent. So we need to encode that we're entering this mode.
|
|
|
|
case 'table':
|
|
return createFormatContext(HTML_TABLE_MODE, null);
|
|
|
|
case 'thead':
|
|
case 'tbody':
|
|
case 'tfoot':
|
|
return createFormatContext(HTML_TABLE_BODY_MODE, null);
|
|
|
|
case 'colgroup':
|
|
return createFormatContext(HTML_COLGROUP_MODE, null);
|
|
|
|
case 'tr':
|
|
return createFormatContext(HTML_TABLE_ROW_MODE, null);
|
|
}
|
|
|
|
if (parentContext.insertionMode >= HTML_TABLE_MODE) {
|
|
// Whatever tag this was, it wasn't a table parent or other special parent, so we must have
|
|
// entered plain HTML again.
|
|
return createFormatContext(HTML_MODE, null);
|
|
}
|
|
|
|
if (parentContext.insertionMode === ROOT_HTML_MODE) {
|
|
// We've emitted the root and is now in plain HTML mode.
|
|
return createFormatContext(HTML_MODE, null);
|
|
}
|
|
|
|
return parentContext;
|
|
}
|
|
var UNINITIALIZED_SUSPENSE_BOUNDARY_ID = null;
|
|
function assignSuspenseBoundaryID(responseState) {
|
|
var generatedID = responseState.nextSuspenseID++;
|
|
return stringToPrecomputedChunk(responseState.boundaryPrefix + generatedID.toString(16));
|
|
}
|
|
function makeId(responseState, treeId, localId) {
|
|
var idPrefix = responseState.idPrefix;
|
|
var id = ':' + idPrefix + 'R' + treeId; // Unless this is the first id at this level, append a number at the end
|
|
// that represents the position of this useId hook among all the useId
|
|
// hooks for this fiber.
|
|
|
|
if (localId > 0) {
|
|
id += 'H' + localId.toString(32);
|
|
}
|
|
|
|
return id + ':';
|
|
}
|
|
|
|
function encodeHTMLTextNode(text) {
|
|
return escapeTextForBrowser(text);
|
|
}
|
|
|
|
var textSeparator = stringToPrecomputedChunk('<!-- -->');
|
|
function pushTextInstance(target, text, responseState, textEmbedded) {
|
|
if (text === '') {
|
|
// Empty text doesn't have a DOM node representation and the hydration is aware of this.
|
|
return textEmbedded;
|
|
}
|
|
|
|
if (textEmbedded) {
|
|
target.push(textSeparator);
|
|
}
|
|
|
|
target.push(stringToChunk(encodeHTMLTextNode(text)));
|
|
return true;
|
|
} // Called when Fizz is done with a Segment. Currently the only purpose is to conditionally
|
|
// emit a text separator when we don't know for sure it is safe to omit
|
|
|
|
function pushSegmentFinale(target, responseState, lastPushedText, textEmbedded) {
|
|
if (lastPushedText && textEmbedded) {
|
|
target.push(textSeparator);
|
|
}
|
|
}
|
|
var styleNameCache = new Map();
|
|
|
|
function processStyleName(styleName) {
|
|
var chunk = styleNameCache.get(styleName);
|
|
|
|
if (chunk !== undefined) {
|
|
return chunk;
|
|
}
|
|
|
|
var result = stringToPrecomputedChunk(escapeTextForBrowser(hyphenateStyleName(styleName)));
|
|
styleNameCache.set(styleName, result);
|
|
return result;
|
|
}
|
|
|
|
var styleAttributeStart = stringToPrecomputedChunk(' style="');
|
|
var styleAssign = stringToPrecomputedChunk(':');
|
|
var styleSeparator = stringToPrecomputedChunk(';');
|
|
|
|
function pushStyle(target, responseState, style) {
|
|
if (typeof style !== 'object') {
|
|
throw new Error('The `style` prop expects a mapping from style properties to values, ' + "not a string. For example, style={{marginRight: spacing + 'em'}} when " + 'using JSX.');
|
|
}
|
|
|
|
var isFirst = true;
|
|
|
|
for (var styleName in style) {
|
|
if (!hasOwnProperty.call(style, styleName)) {
|
|
continue;
|
|
} // If you provide unsafe user data here they can inject arbitrary CSS
|
|
// which may be problematic (I couldn't repro this):
|
|
// https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
|
|
// http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
|
|
// This is not an XSS hole but instead a potential CSS injection issue
|
|
// which has lead to a greater discussion about how we're going to
|
|
// trust URLs moving forward. See #2115901
|
|
|
|
|
|
var styleValue = style[styleName];
|
|
|
|
if (styleValue == null || typeof styleValue === 'boolean' || styleValue === '') {
|
|
// TODO: We used to set empty string as a style with an empty value. Does that ever make sense?
|
|
continue;
|
|
}
|
|
|
|
var nameChunk = void 0;
|
|
var valueChunk = void 0;
|
|
var isCustomProperty = styleName.indexOf('--') === 0;
|
|
|
|
if (isCustomProperty) {
|
|
nameChunk = stringToChunk(escapeTextForBrowser(styleName));
|
|
|
|
{
|
|
checkCSSPropertyStringCoercion(styleValue, styleName);
|
|
}
|
|
|
|
valueChunk = stringToChunk(escapeTextForBrowser(('' + styleValue).trim()));
|
|
} else {
|
|
{
|
|
warnValidStyle$1(styleName, styleValue);
|
|
}
|
|
|
|
nameChunk = processStyleName(styleName);
|
|
|
|
if (typeof styleValue === 'number') {
|
|
if (styleValue !== 0 && !hasOwnProperty.call(isUnitlessNumber, styleName)) {
|
|
valueChunk = stringToChunk(styleValue + 'px'); // Presumes implicit 'px' suffix for unitless numbers
|
|
} else {
|
|
valueChunk = stringToChunk('' + styleValue);
|
|
}
|
|
} else {
|
|
{
|
|
checkCSSPropertyStringCoercion(styleValue, styleName);
|
|
}
|
|
|
|
valueChunk = stringToChunk(escapeTextForBrowser(('' + styleValue).trim()));
|
|
}
|
|
}
|
|
|
|
if (isFirst) {
|
|
isFirst = false; // If it's first, we don't need any separators prefixed.
|
|
|
|
target.push(styleAttributeStart, nameChunk, styleAssign, valueChunk);
|
|
} else {
|
|
target.push(styleSeparator, nameChunk, styleAssign, valueChunk);
|
|
}
|
|
}
|
|
|
|
if (!isFirst) {
|
|
target.push(attributeEnd);
|
|
}
|
|
}
|
|
|
|
var attributeSeparator = stringToPrecomputedChunk(' ');
|
|
var attributeAssign = stringToPrecomputedChunk('="');
|
|
var attributeEnd = stringToPrecomputedChunk('"');
|
|
var attributeEmptyString = stringToPrecomputedChunk('=""');
|
|
|
|
function pushAttribute(target, responseState, name, value) {
|
|
switch (name) {
|
|
case 'style':
|
|
{
|
|
pushStyle(target, responseState, value);
|
|
return;
|
|
}
|
|
|
|
case 'defaultValue':
|
|
case 'defaultChecked': // These shouldn't be set as attributes on generic HTML elements.
|
|
|
|
case 'innerHTML': // Must use dangerouslySetInnerHTML instead.
|
|
|
|
case 'suppressContentEditableWarning':
|
|
case 'suppressHydrationWarning':
|
|
// Ignored. These are built-in to React on the client.
|
|
return;
|
|
}
|
|
|
|
if ( // shouldIgnoreAttribute
|
|
// We have already filtered out null/undefined and reserved words.
|
|
name.length > 2 && (name[0] === 'o' || name[0] === 'O') && (name[1] === 'n' || name[1] === 'N')) {
|
|
return;
|
|
}
|
|
|
|
var propertyInfo = getPropertyInfo(name);
|
|
|
|
if (propertyInfo !== null) {
|
|
// shouldRemoveAttribute
|
|
switch (typeof value) {
|
|
case 'function': // $FlowIssue symbol is perfectly valid here
|
|
|
|
case 'symbol':
|
|
// eslint-disable-line
|
|
return;
|
|
|
|
case 'boolean':
|
|
{
|
|
if (!propertyInfo.acceptsBooleans) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
var attributeName = propertyInfo.attributeName;
|
|
var attributeNameChunk = stringToChunk(attributeName); // TODO: If it's known we can cache the chunk.
|
|
|
|
switch (propertyInfo.type) {
|
|
case BOOLEAN:
|
|
if (value) {
|
|
target.push(attributeSeparator, attributeNameChunk, attributeEmptyString);
|
|
}
|
|
|
|
return;
|
|
|
|
case OVERLOADED_BOOLEAN:
|
|
if (value === true) {
|
|
target.push(attributeSeparator, attributeNameChunk, attributeEmptyString);
|
|
} else if (value === false) ; else {
|
|
target.push(attributeSeparator, attributeNameChunk, attributeAssign, stringToChunk(escapeTextForBrowser(value)), attributeEnd);
|
|
}
|
|
|
|
return;
|
|
|
|
case NUMERIC:
|
|
if (!isNaN(value)) {
|
|
target.push(attributeSeparator, attributeNameChunk, attributeAssign, stringToChunk(escapeTextForBrowser(value)), attributeEnd);
|
|
}
|
|
|
|
break;
|
|
|
|
case POSITIVE_NUMERIC:
|
|
if (!isNaN(value) && value >= 1) {
|
|
target.push(attributeSeparator, attributeNameChunk, attributeAssign, stringToChunk(escapeTextForBrowser(value)), attributeEnd);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
if (propertyInfo.sanitizeURL) {
|
|
{
|
|
checkAttributeStringCoercion(value, attributeName);
|
|
}
|
|
|
|
value = '' + value;
|
|
sanitizeURL(value);
|
|
}
|
|
|
|
target.push(attributeSeparator, attributeNameChunk, attributeAssign, stringToChunk(escapeTextForBrowser(value)), attributeEnd);
|
|
}
|
|
} else if (isAttributeNameSafe(name)) {
|
|
// shouldRemoveAttribute
|
|
switch (typeof value) {
|
|
case 'function': // $FlowIssue symbol is perfectly valid here
|
|
|
|
case 'symbol':
|
|
// eslint-disable-line
|
|
return;
|
|
|
|
case 'boolean':
|
|
{
|
|
var prefix = name.toLowerCase().slice(0, 5);
|
|
|
|
if (prefix !== 'data-' && prefix !== 'aria-') {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.push(attributeSeparator, stringToChunk(name), attributeAssign, stringToChunk(escapeTextForBrowser(value)), attributeEnd);
|
|
}
|
|
}
|
|
|
|
var endOfStartTag = stringToPrecomputedChunk('>');
|
|
var endOfStartTagSelfClosing = stringToPrecomputedChunk('/>');
|
|
|
|
function pushInnerHTML(target, innerHTML, children) {
|
|
if (innerHTML != null) {
|
|
if (children != null) {
|
|
throw new Error('Can only set one of `children` or `props.dangerouslySetInnerHTML`.');
|
|
}
|
|
|
|
if (typeof innerHTML !== 'object' || !('__html' in innerHTML)) {
|
|
throw new Error('`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' + 'Please visit https://reactjs.org/link/dangerously-set-inner-html ' + 'for more information.');
|
|
}
|
|
|
|
var html = innerHTML.__html;
|
|
|
|
if (html !== null && html !== undefined) {
|
|
{
|
|
checkHtmlStringCoercion(html);
|
|
}
|
|
|
|
target.push(stringToChunk('' + html));
|
|
}
|
|
}
|
|
} // TODO: Move these to ResponseState so that we warn for every request.
|
|
// It would help debugging in stateful servers (e.g. service worker).
|
|
|
|
|
|
var didWarnDefaultInputValue = false;
|
|
var didWarnDefaultChecked = false;
|
|
var didWarnDefaultSelectValue = false;
|
|
var didWarnDefaultTextareaValue = false;
|
|
var didWarnInvalidOptionChildren = false;
|
|
var didWarnInvalidOptionInnerHTML = false;
|
|
var didWarnSelectedSetOnOption = false;
|
|
|
|
function checkSelectProp(props, propName) {
|
|
{
|
|
var value = props[propName];
|
|
|
|
if (value != null) {
|
|
var array = isArray(value);
|
|
|
|
if (props.multiple && !array) {
|
|
error('The `%s` prop supplied to <select> must be an array if ' + '`multiple` is true.', propName);
|
|
} else if (!props.multiple && array) {
|
|
error('The `%s` prop supplied to <select> must be a scalar ' + 'value if `multiple` is false.', propName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function pushStartSelect(target, props, responseState) {
|
|
{
|
|
checkControlledValueProps('select', props);
|
|
checkSelectProp(props, 'value');
|
|
checkSelectProp(props, 'defaultValue');
|
|
|
|
if (props.value !== undefined && props.defaultValue !== undefined && !didWarnDefaultSelectValue) {
|
|
error('Select elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled select ' + 'element and remove one of these props. More info: ' + 'https://reactjs.org/link/controlled-components');
|
|
|
|
didWarnDefaultSelectValue = true;
|
|
}
|
|
}
|
|
|
|
target.push(startChunkForTag('select'));
|
|
var children = null;
|
|
var innerHTML = null;
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
children = propValue;
|
|
break;
|
|
|
|
case 'dangerouslySetInnerHTML':
|
|
// TODO: This doesn't really make sense for select since it can't use the controlled
|
|
// value in the innerHTML.
|
|
innerHTML = propValue;
|
|
break;
|
|
|
|
case 'defaultValue':
|
|
case 'value':
|
|
// These are set on the Context instead and applied to the nested options.
|
|
break;
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.push(endOfStartTag);
|
|
pushInnerHTML(target, innerHTML, children);
|
|
return children;
|
|
}
|
|
|
|
function flattenOptionChildren(children) {
|
|
var content = ''; // Flatten children and warn if they aren't strings or numbers;
|
|
// invalid types are ignored.
|
|
|
|
React.Children.forEach(children, function (child) {
|
|
if (child == null) {
|
|
return;
|
|
}
|
|
|
|
content += child;
|
|
|
|
{
|
|
if (!didWarnInvalidOptionChildren && typeof child !== 'string' && typeof child !== 'number') {
|
|
didWarnInvalidOptionChildren = true;
|
|
|
|
error('Cannot infer the option value of complex children. ' + 'Pass a `value` prop or use a plain string as children to <option>.');
|
|
}
|
|
}
|
|
});
|
|
return content;
|
|
}
|
|
|
|
var selectedMarkerAttribute = stringToPrecomputedChunk(' selected=""');
|
|
|
|
function pushStartOption(target, props, responseState, formatContext) {
|
|
var selectedValue = formatContext.selectedValue;
|
|
target.push(startChunkForTag('option'));
|
|
var children = null;
|
|
var value = null;
|
|
var selected = null;
|
|
var innerHTML = null;
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
children = propValue;
|
|
break;
|
|
|
|
case 'selected':
|
|
// ignore
|
|
selected = propValue;
|
|
|
|
{
|
|
// TODO: Remove support for `selected` in <option>.
|
|
if (!didWarnSelectedSetOnOption) {
|
|
error('Use the `defaultValue` or `value` props on <select> instead of ' + 'setting `selected` on <option>.');
|
|
|
|
didWarnSelectedSetOnOption = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case 'dangerouslySetInnerHTML':
|
|
innerHTML = propValue;
|
|
break;
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case 'value':
|
|
value = propValue;
|
|
// We intentionally fallthrough to also set the attribute on the node.
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selectedValue != null) {
|
|
var stringValue;
|
|
|
|
if (value !== null) {
|
|
{
|
|
checkAttributeStringCoercion(value, 'value');
|
|
}
|
|
|
|
stringValue = '' + value;
|
|
} else {
|
|
{
|
|
if (innerHTML !== null) {
|
|
if (!didWarnInvalidOptionInnerHTML) {
|
|
didWarnInvalidOptionInnerHTML = true;
|
|
|
|
error('Pass a `value` prop if you set dangerouslyInnerHTML so React knows ' + 'which value should be selected.');
|
|
}
|
|
}
|
|
}
|
|
|
|
stringValue = flattenOptionChildren(children);
|
|
}
|
|
|
|
if (isArray(selectedValue)) {
|
|
// multiple
|
|
for (var i = 0; i < selectedValue.length; i++) {
|
|
{
|
|
checkAttributeStringCoercion(selectedValue[i], 'value');
|
|
}
|
|
|
|
var v = '' + selectedValue[i];
|
|
|
|
if (v === stringValue) {
|
|
target.push(selectedMarkerAttribute);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
{
|
|
checkAttributeStringCoercion(selectedValue, 'select.value');
|
|
}
|
|
|
|
if ('' + selectedValue === stringValue) {
|
|
target.push(selectedMarkerAttribute);
|
|
}
|
|
}
|
|
} else if (selected) {
|
|
target.push(selectedMarkerAttribute);
|
|
}
|
|
|
|
target.push(endOfStartTag);
|
|
pushInnerHTML(target, innerHTML, children);
|
|
return children;
|
|
}
|
|
|
|
function pushInput(target, props, responseState) {
|
|
{
|
|
checkControlledValueProps('input', props);
|
|
|
|
if (props.checked !== undefined && props.defaultChecked !== undefined && !didWarnDefaultChecked) {
|
|
error('%s contains an input of type %s with both checked and defaultChecked props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the checked prop, or the defaultChecked prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://reactjs.org/link/controlled-components', 'A component', props.type);
|
|
|
|
didWarnDefaultChecked = true;
|
|
}
|
|
|
|
if (props.value !== undefined && props.defaultValue !== undefined && !didWarnDefaultInputValue) {
|
|
error('%s contains an input of type %s with both value and defaultValue props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://reactjs.org/link/controlled-components', 'A component', props.type);
|
|
|
|
didWarnDefaultInputValue = true;
|
|
}
|
|
}
|
|
|
|
target.push(startChunkForTag('input'));
|
|
var value = null;
|
|
var defaultValue = null;
|
|
var checked = null;
|
|
var defaultChecked = null;
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
case 'dangerouslySetInnerHTML':
|
|
throw new Error('input' + " is a self-closing tag and must neither have `children` nor " + 'use `dangerouslySetInnerHTML`.');
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case 'defaultChecked':
|
|
defaultChecked = propValue;
|
|
break;
|
|
|
|
case 'defaultValue':
|
|
defaultValue = propValue;
|
|
break;
|
|
|
|
case 'checked':
|
|
checked = propValue;
|
|
break;
|
|
|
|
case 'value':
|
|
value = propValue;
|
|
break;
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (checked !== null) {
|
|
pushAttribute(target, responseState, 'checked', checked);
|
|
} else if (defaultChecked !== null) {
|
|
pushAttribute(target, responseState, 'checked', defaultChecked);
|
|
}
|
|
|
|
if (value !== null) {
|
|
pushAttribute(target, responseState, 'value', value);
|
|
} else if (defaultValue !== null) {
|
|
pushAttribute(target, responseState, 'value', defaultValue);
|
|
}
|
|
|
|
target.push(endOfStartTagSelfClosing);
|
|
return null;
|
|
}
|
|
|
|
function pushStartTextArea(target, props, responseState) {
|
|
{
|
|
checkControlledValueProps('textarea', props);
|
|
|
|
if (props.value !== undefined && props.defaultValue !== undefined && !didWarnDefaultTextareaValue) {
|
|
error('Textarea elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled textarea ' + 'and remove one of these props. More info: ' + 'https://reactjs.org/link/controlled-components');
|
|
|
|
didWarnDefaultTextareaValue = true;
|
|
}
|
|
}
|
|
|
|
target.push(startChunkForTag('textarea'));
|
|
var value = null;
|
|
var defaultValue = null;
|
|
var children = null;
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
children = propValue;
|
|
break;
|
|
|
|
case 'value':
|
|
value = propValue;
|
|
break;
|
|
|
|
case 'defaultValue':
|
|
defaultValue = propValue;
|
|
break;
|
|
|
|
case 'dangerouslySetInnerHTML':
|
|
throw new Error('`dangerouslySetInnerHTML` does not make sense on <textarea>.');
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (value === null && defaultValue !== null) {
|
|
value = defaultValue;
|
|
}
|
|
|
|
target.push(endOfStartTag); // TODO (yungsters): Remove support for children content in <textarea>.
|
|
|
|
if (children != null) {
|
|
{
|
|
error('Use the `defaultValue` or `value` props instead of setting ' + 'children on <textarea>.');
|
|
}
|
|
|
|
if (value != null) {
|
|
throw new Error('If you supply `defaultValue` on a <textarea>, do not pass children.');
|
|
}
|
|
|
|
if (isArray(children)) {
|
|
if (children.length > 1) {
|
|
throw new Error('<textarea> can only have at most one child.');
|
|
} // TODO: remove the coercion and the DEV check below because it will
|
|
// always be overwritten by the coercion several lines below it. #22309
|
|
|
|
|
|
{
|
|
checkHtmlStringCoercion(children[0]);
|
|
}
|
|
|
|
value = '' + children[0];
|
|
}
|
|
|
|
{
|
|
checkHtmlStringCoercion(children);
|
|
}
|
|
|
|
value = '' + children;
|
|
}
|
|
|
|
if (typeof value === 'string' && value[0] === '\n') {
|
|
// text/html ignores the first character in these tags if it's a newline
|
|
// Prefer to break application/xml over text/html (for now) by adding
|
|
// a newline specifically to get eaten by the parser. (Alternately for
|
|
// textareas, replacing "^\n" with "\r\n" doesn't get eaten, and the first
|
|
// \r is normalized out by HTMLTextAreaElement#value.)
|
|
// See: <http://www.w3.org/TR/html-polyglot/#newlines-in-textarea-and-pre>
|
|
// See: <http://www.w3.org/TR/html5/syntax.html#element-restrictions>
|
|
// See: <http://www.w3.org/TR/html5/syntax.html#newlines>
|
|
// See: Parsing of "textarea" "listing" and "pre" elements
|
|
// from <http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody>
|
|
target.push(leadingNewline);
|
|
} // ToString and push directly instead of recurse over children.
|
|
// We don't really support complex children in the value anyway.
|
|
// This also currently avoids a trailing comment node which breaks textarea.
|
|
|
|
|
|
if (value !== null) {
|
|
{
|
|
checkAttributeStringCoercion(value, 'value');
|
|
}
|
|
|
|
target.push(stringToChunk(encodeHTMLTextNode('' + value)));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function pushSelfClosing(target, props, tag, responseState) {
|
|
target.push(startChunkForTag(tag));
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
case 'dangerouslySetInnerHTML':
|
|
throw new Error(tag + " is a self-closing tag and must neither have `children` nor " + 'use `dangerouslySetInnerHTML`.');
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.push(endOfStartTagSelfClosing);
|
|
return null;
|
|
}
|
|
|
|
function pushStartMenuItem(target, props, responseState) {
|
|
target.push(startChunkForTag('menuitem'));
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
case 'dangerouslySetInnerHTML':
|
|
throw new Error('menuitems cannot have `children` nor `dangerouslySetInnerHTML`.');
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.push(endOfStartTag);
|
|
return null;
|
|
}
|
|
|
|
function pushStartTitle(target, props, responseState) {
|
|
target.push(startChunkForTag('title'));
|
|
var children = null;
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
children = propValue;
|
|
break;
|
|
|
|
case 'dangerouslySetInnerHTML':
|
|
throw new Error('`dangerouslySetInnerHTML` does not make sense on <title>.');
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.push(endOfStartTag);
|
|
|
|
{
|
|
var child = Array.isArray(children) && children.length < 2 ? children[0] || null : children;
|
|
|
|
if (Array.isArray(children) && children.length > 1) {
|
|
error('A title element received an array with more than 1 element as children. ' + 'In browsers title Elements can only have Text Nodes as children. If ' + 'the children being rendered output more than a single text node in aggregate the browser ' + 'will display markup and comments as text in the title and hydration will likely fail and ' + 'fall back to client rendering');
|
|
} else if (child != null && child.$$typeof != null) {
|
|
error('A title element received a React element for children. ' + 'In the browser title Elements can only have Text Nodes as children. If ' + 'the children being rendered output more than a single text node in aggregate the browser ' + 'will display markup and comments as text in the title and hydration will likely fail and ' + 'fall back to client rendering');
|
|
} else if (child != null && typeof child !== 'string' && typeof child !== 'number') {
|
|
error('A title element received a value that was not a string or number for children. ' + 'In the browser title Elements can only have Text Nodes as children. If ' + 'the children being rendered output more than a single text node in aggregate the browser ' + 'will display markup and comments as text in the title and hydration will likely fail and ' + 'fall back to client rendering');
|
|
}
|
|
}
|
|
|
|
return children;
|
|
}
|
|
|
|
function pushStartGenericElement(target, props, tag, responseState) {
|
|
target.push(startChunkForTag(tag));
|
|
var children = null;
|
|
var innerHTML = null;
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
children = propValue;
|
|
break;
|
|
|
|
case 'dangerouslySetInnerHTML':
|
|
innerHTML = propValue;
|
|
break;
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.push(endOfStartTag);
|
|
pushInnerHTML(target, innerHTML, children);
|
|
|
|
if (typeof children === 'string') {
|
|
// Special case children as a string to avoid the unnecessary comment.
|
|
// TODO: Remove this special case after the general optimization is in place.
|
|
target.push(stringToChunk(encodeHTMLTextNode(children)));
|
|
return null;
|
|
}
|
|
|
|
return children;
|
|
}
|
|
|
|
function pushStartCustomElement(target, props, tag, responseState) {
|
|
target.push(startChunkForTag(tag));
|
|
var children = null;
|
|
var innerHTML = null;
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
children = propValue;
|
|
break;
|
|
|
|
case 'dangerouslySetInnerHTML':
|
|
innerHTML = propValue;
|
|
break;
|
|
|
|
case 'style':
|
|
pushStyle(target, responseState, propValue);
|
|
break;
|
|
|
|
case 'suppressContentEditableWarning':
|
|
case 'suppressHydrationWarning':
|
|
// Ignored. These are built-in to React on the client.
|
|
break;
|
|
|
|
default:
|
|
if (isAttributeNameSafe(propKey) && typeof propValue !== 'function' && typeof propValue !== 'symbol') {
|
|
target.push(attributeSeparator, stringToChunk(propKey), attributeAssign, stringToChunk(escapeTextForBrowser(propValue)), attributeEnd);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.push(endOfStartTag);
|
|
pushInnerHTML(target, innerHTML, children);
|
|
return children;
|
|
}
|
|
|
|
var leadingNewline = stringToPrecomputedChunk('\n');
|
|
|
|
function pushStartPreformattedElement(target, props, tag, responseState) {
|
|
target.push(startChunkForTag(tag));
|
|
var children = null;
|
|
var innerHTML = null;
|
|
|
|
for (var propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
var propValue = props[propKey];
|
|
|
|
if (propValue == null) {
|
|
continue;
|
|
}
|
|
|
|
switch (propKey) {
|
|
case 'children':
|
|
children = propValue;
|
|
break;
|
|
|
|
case 'dangerouslySetInnerHTML':
|
|
innerHTML = propValue;
|
|
break;
|
|
|
|
default:
|
|
pushAttribute(target, responseState, propKey, propValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
target.push(endOfStartTag); // text/html ignores the first character in these tags if it's a newline
|
|
// Prefer to break application/xml over text/html (for now) by adding
|
|
// a newline specifically to get eaten by the parser. (Alternately for
|
|
// textareas, replacing "^\n" with "\r\n" doesn't get eaten, and the first
|
|
// \r is normalized out by HTMLTextAreaElement#value.)
|
|
// See: <http://www.w3.org/TR/html-polyglot/#newlines-in-textarea-and-pre>
|
|
// See: <http://www.w3.org/TR/html5/syntax.html#element-restrictions>
|
|
// See: <http://www.w3.org/TR/html5/syntax.html#newlines>
|
|
// See: Parsing of "textarea" "listing" and "pre" elements
|
|
// from <http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody>
|
|
// TODO: This doesn't deal with the case where the child is an array
|
|
// or component that returns a string.
|
|
|
|
if (innerHTML != null) {
|
|
if (children != null) {
|
|
throw new Error('Can only set one of `children` or `props.dangerouslySetInnerHTML`.');
|
|
}
|
|
|
|
if (typeof innerHTML !== 'object' || !('__html' in innerHTML)) {
|
|
throw new Error('`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' + 'Please visit https://reactjs.org/link/dangerously-set-inner-html ' + 'for more information.');
|
|
}
|
|
|
|
var html = innerHTML.__html;
|
|
|
|
if (html !== null && html !== undefined) {
|
|
if (typeof html === 'string' && html.length > 0 && html[0] === '\n') {
|
|
target.push(leadingNewline, stringToChunk(html));
|
|
} else {
|
|
{
|
|
checkHtmlStringCoercion(html);
|
|
}
|
|
|
|
target.push(stringToChunk('' + html));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof children === 'string' && children[0] === '\n') {
|
|
target.push(leadingNewline);
|
|
}
|
|
|
|
return children;
|
|
} // We accept any tag to be rendered but since this gets injected into arbitrary
|
|
// HTML, we want to make sure that it's a safe tag.
|
|
// http://www.w3.org/TR/REC-xml/#NT-Name
|
|
|
|
|
|
var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset
|
|
|
|
var validatedTagCache = new Map();
|
|
|
|
function startChunkForTag(tag) {
|
|
var tagStartChunk = validatedTagCache.get(tag);
|
|
|
|
if (tagStartChunk === undefined) {
|
|
if (!VALID_TAG_REGEX.test(tag)) {
|
|
throw new Error("Invalid tag: " + tag);
|
|
}
|
|
|
|
tagStartChunk = stringToPrecomputedChunk('<' + tag);
|
|
validatedTagCache.set(tag, tagStartChunk);
|
|
}
|
|
|
|
return tagStartChunk;
|
|
}
|
|
|
|
var DOCTYPE = stringToPrecomputedChunk('<!DOCTYPE html>');
|
|
function pushStartInstance(target, type, props, responseState, formatContext) {
|
|
{
|
|
validateProperties(type, props);
|
|
validateProperties$1(type, props);
|
|
validateProperties$2(type, props, null);
|
|
|
|
if (!props.suppressContentEditableWarning && props.contentEditable && props.children != null) {
|
|
error('A component is `contentEditable` and contains `children` managed by ' + 'React. It is now your responsibility to guarantee that none of ' + 'those nodes are unexpectedly modified or duplicated. This is ' + 'probably not intentional.');
|
|
}
|
|
|
|
if (formatContext.insertionMode !== SVG_MODE && formatContext.insertionMode !== MATHML_MODE) {
|
|
if (type.indexOf('-') === -1 && typeof props.is !== 'string' && type.toLowerCase() !== type) {
|
|
error('<%s /> is using incorrect casing. ' + 'Use PascalCase for React components, ' + 'or lowercase for HTML elements.', type);
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
// Special tags
|
|
case 'select':
|
|
return pushStartSelect(target, props, responseState);
|
|
|
|
case 'option':
|
|
return pushStartOption(target, props, responseState, formatContext);
|
|
|
|
case 'textarea':
|
|
return pushStartTextArea(target, props, responseState);
|
|
|
|
case 'input':
|
|
return pushInput(target, props, responseState);
|
|
|
|
case 'menuitem':
|
|
return pushStartMenuItem(target, props, responseState);
|
|
|
|
case 'title':
|
|
return pushStartTitle(target, props, responseState);
|
|
// Newline eating tags
|
|
|
|
case 'listing':
|
|
case 'pre':
|
|
{
|
|
return pushStartPreformattedElement(target, props, type, responseState);
|
|
}
|
|
// Omitted close tags
|
|
|
|
case 'area':
|
|
case 'base':
|
|
case 'br':
|
|
case 'col':
|
|
case 'embed':
|
|
case 'hr':
|
|
case 'img':
|
|
case 'keygen':
|
|
case 'link':
|
|
case 'meta':
|
|
case 'param':
|
|
case 'source':
|
|
case 'track':
|
|
case 'wbr':
|
|
{
|
|
return pushSelfClosing(target, props, type, responseState);
|
|
}
|
|
// These are reserved SVG and MathML elements, that are never custom elements.
|
|
// https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts
|
|
|
|
case 'annotation-xml':
|
|
case 'color-profile':
|
|
case 'font-face':
|
|
case 'font-face-src':
|
|
case 'font-face-uri':
|
|
case 'font-face-format':
|
|
case 'font-face-name':
|
|
case 'missing-glyph':
|
|
{
|
|
return pushStartGenericElement(target, props, type, responseState);
|
|
}
|
|
|
|
case 'html':
|
|
{
|
|
if (formatContext.insertionMode === ROOT_HTML_MODE) {
|
|
// If we're rendering the html tag and we're at the root (i.e. not in foreignObject)
|
|
// then we also emit the DOCTYPE as part of the root content as a convenience for
|
|
// rendering the whole document.
|
|
target.push(DOCTYPE);
|
|
}
|
|
|
|
return pushStartGenericElement(target, props, type, responseState);
|
|
}
|
|
|
|
default:
|
|
{
|
|
if (type.indexOf('-') === -1 && typeof props.is !== 'string') {
|
|
// Generic element
|
|
return pushStartGenericElement(target, props, type, responseState);
|
|
} else {
|
|
// Custom element
|
|
return pushStartCustomElement(target, props, type, responseState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var endTag1 = stringToPrecomputedChunk('</');
|
|
var endTag2 = stringToPrecomputedChunk('>');
|
|
function pushEndInstance(target, type, props) {
|
|
switch (type) {
|
|
// Omitted close tags
|
|
// TODO: Instead of repeating this switch we could try to pass a flag from above.
|
|
// That would require returning a tuple. Which might be ok if it gets inlined.
|
|
case 'area':
|
|
case 'base':
|
|
case 'br':
|
|
case 'col':
|
|
case 'embed':
|
|
case 'hr':
|
|
case 'img':
|
|
case 'input':
|
|
case 'keygen':
|
|
case 'link':
|
|
case 'meta':
|
|
case 'param':
|
|
case 'source':
|
|
case 'track':
|
|
case 'wbr':
|
|
{
|
|
// No close tag needed.
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
target.push(endTag1, stringToChunk(type), endTag2);
|
|
}
|
|
}
|
|
}
|
|
function writeCompletedRoot(destination, responseState) {
|
|
var bootstrapChunks = responseState.bootstrapChunks;
|
|
var i = 0;
|
|
|
|
for (; i < bootstrapChunks.length - 1; i++) {
|
|
writeChunk(destination, bootstrapChunks[i]);
|
|
}
|
|
|
|
if (i < bootstrapChunks.length) {
|
|
return writeChunkAndReturn(destination, bootstrapChunks[i]);
|
|
}
|
|
|
|
return true;
|
|
} // Structural Nodes
|
|
// A placeholder is a node inside a hidden partial tree that can be filled in later, but before
|
|
// display. It's never visible to users. We use the template tag because it can be used in every
|
|
// type of parent. <script> tags also work in every other tag except <colgroup>.
|
|
|
|
var placeholder1 = stringToPrecomputedChunk('<template id="');
|
|
var placeholder2 = stringToPrecomputedChunk('"></template>');
|
|
function writePlaceholder(destination, responseState, id) {
|
|
writeChunk(destination, placeholder1);
|
|
writeChunk(destination, responseState.placeholderPrefix);
|
|
var formattedID = stringToChunk(id.toString(16));
|
|
writeChunk(destination, formattedID);
|
|
return writeChunkAndReturn(destination, placeholder2);
|
|
} // Suspense boundaries are encoded as comments.
|
|
|
|
var startCompletedSuspenseBoundary = stringToPrecomputedChunk('<!--$-->');
|
|
var startPendingSuspenseBoundary1 = stringToPrecomputedChunk('<!--$?--><template id="');
|
|
var startPendingSuspenseBoundary2 = stringToPrecomputedChunk('"></template>');
|
|
var startClientRenderedSuspenseBoundary = stringToPrecomputedChunk('<!--$!-->');
|
|
var endSuspenseBoundary = stringToPrecomputedChunk('<!--/$-->');
|
|
var clientRenderedSuspenseBoundaryError1 = stringToPrecomputedChunk('<template');
|
|
var clientRenderedSuspenseBoundaryErrorAttrInterstitial = stringToPrecomputedChunk('"');
|
|
var clientRenderedSuspenseBoundaryError1A = stringToPrecomputedChunk(' data-dgst="');
|
|
var clientRenderedSuspenseBoundaryError1B = stringToPrecomputedChunk(' data-msg="');
|
|
var clientRenderedSuspenseBoundaryError1C = stringToPrecomputedChunk(' data-stck="');
|
|
var clientRenderedSuspenseBoundaryError2 = stringToPrecomputedChunk('></template>');
|
|
function writeStartCompletedSuspenseBoundary(destination, responseState) {
|
|
return writeChunkAndReturn(destination, startCompletedSuspenseBoundary);
|
|
}
|
|
function writeStartPendingSuspenseBoundary(destination, responseState, id) {
|
|
writeChunk(destination, startPendingSuspenseBoundary1);
|
|
|
|
if (id === null) {
|
|
throw new Error('An ID must have been assigned before we can complete the boundary.');
|
|
}
|
|
|
|
writeChunk(destination, id);
|
|
return writeChunkAndReturn(destination, startPendingSuspenseBoundary2);
|
|
}
|
|
function writeStartClientRenderedSuspenseBoundary(destination, responseState, errorDigest, errorMesssage, errorComponentStack) {
|
|
var result;
|
|
result = writeChunkAndReturn(destination, startClientRenderedSuspenseBoundary);
|
|
writeChunk(destination, clientRenderedSuspenseBoundaryError1);
|
|
|
|
if (errorDigest) {
|
|
writeChunk(destination, clientRenderedSuspenseBoundaryError1A);
|
|
writeChunk(destination, stringToChunk(escapeTextForBrowser(errorDigest)));
|
|
writeChunk(destination, clientRenderedSuspenseBoundaryErrorAttrInterstitial);
|
|
}
|
|
|
|
{
|
|
if (errorMesssage) {
|
|
writeChunk(destination, clientRenderedSuspenseBoundaryError1B);
|
|
writeChunk(destination, stringToChunk(escapeTextForBrowser(errorMesssage)));
|
|
writeChunk(destination, clientRenderedSuspenseBoundaryErrorAttrInterstitial);
|
|
}
|
|
|
|
if (errorComponentStack) {
|
|
writeChunk(destination, clientRenderedSuspenseBoundaryError1C);
|
|
writeChunk(destination, stringToChunk(escapeTextForBrowser(errorComponentStack)));
|
|
writeChunk(destination, clientRenderedSuspenseBoundaryErrorAttrInterstitial);
|
|
}
|
|
}
|
|
|
|
result = writeChunkAndReturn(destination, clientRenderedSuspenseBoundaryError2);
|
|
return result;
|
|
}
|
|
function writeEndCompletedSuspenseBoundary(destination, responseState) {
|
|
return writeChunkAndReturn(destination, endSuspenseBoundary);
|
|
}
|
|
function writeEndPendingSuspenseBoundary(destination, responseState) {
|
|
return writeChunkAndReturn(destination, endSuspenseBoundary);
|
|
}
|
|
function writeEndClientRenderedSuspenseBoundary(destination, responseState) {
|
|
return writeChunkAndReturn(destination, endSuspenseBoundary);
|
|
}
|
|
var startSegmentHTML = stringToPrecomputedChunk('<div hidden id="');
|
|
var startSegmentHTML2 = stringToPrecomputedChunk('">');
|
|
var endSegmentHTML = stringToPrecomputedChunk('</div>');
|
|
var startSegmentSVG = stringToPrecomputedChunk('<svg aria-hidden="true" style="display:none" id="');
|
|
var startSegmentSVG2 = stringToPrecomputedChunk('">');
|
|
var endSegmentSVG = stringToPrecomputedChunk('</svg>');
|
|
var startSegmentMathML = stringToPrecomputedChunk('<math aria-hidden="true" style="display:none" id="');
|
|
var startSegmentMathML2 = stringToPrecomputedChunk('">');
|
|
var endSegmentMathML = stringToPrecomputedChunk('</math>');
|
|
var startSegmentTable = stringToPrecomputedChunk('<table hidden id="');
|
|
var startSegmentTable2 = stringToPrecomputedChunk('">');
|
|
var endSegmentTable = stringToPrecomputedChunk('</table>');
|
|
var startSegmentTableBody = stringToPrecomputedChunk('<table hidden><tbody id="');
|
|
var startSegmentTableBody2 = stringToPrecomputedChunk('">');
|
|
var endSegmentTableBody = stringToPrecomputedChunk('</tbody></table>');
|
|
var startSegmentTableRow = stringToPrecomputedChunk('<table hidden><tr id="');
|
|
var startSegmentTableRow2 = stringToPrecomputedChunk('">');
|
|
var endSegmentTableRow = stringToPrecomputedChunk('</tr></table>');
|
|
var startSegmentColGroup = stringToPrecomputedChunk('<table hidden><colgroup id="');
|
|
var startSegmentColGroup2 = stringToPrecomputedChunk('">');
|
|
var endSegmentColGroup = stringToPrecomputedChunk('</colgroup></table>');
|
|
function writeStartSegment(destination, responseState, formatContext, id) {
|
|
switch (formatContext.insertionMode) {
|
|
case ROOT_HTML_MODE:
|
|
case HTML_MODE:
|
|
{
|
|
writeChunk(destination, startSegmentHTML);
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
writeChunk(destination, stringToChunk(id.toString(16)));
|
|
return writeChunkAndReturn(destination, startSegmentHTML2);
|
|
}
|
|
|
|
case SVG_MODE:
|
|
{
|
|
writeChunk(destination, startSegmentSVG);
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
writeChunk(destination, stringToChunk(id.toString(16)));
|
|
return writeChunkAndReturn(destination, startSegmentSVG2);
|
|
}
|
|
|
|
case MATHML_MODE:
|
|
{
|
|
writeChunk(destination, startSegmentMathML);
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
writeChunk(destination, stringToChunk(id.toString(16)));
|
|
return writeChunkAndReturn(destination, startSegmentMathML2);
|
|
}
|
|
|
|
case HTML_TABLE_MODE:
|
|
{
|
|
writeChunk(destination, startSegmentTable);
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
writeChunk(destination, stringToChunk(id.toString(16)));
|
|
return writeChunkAndReturn(destination, startSegmentTable2);
|
|
}
|
|
// TODO: For the rest of these, there will be extra wrapper nodes that never
|
|
// get deleted from the document. We need to delete the table too as part
|
|
// of the injected scripts. They are invisible though so it's not too terrible
|
|
// and it's kind of an edge case to suspend in a table. Totally supported though.
|
|
|
|
case HTML_TABLE_BODY_MODE:
|
|
{
|
|
writeChunk(destination, startSegmentTableBody);
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
writeChunk(destination, stringToChunk(id.toString(16)));
|
|
return writeChunkAndReturn(destination, startSegmentTableBody2);
|
|
}
|
|
|
|
case HTML_TABLE_ROW_MODE:
|
|
{
|
|
writeChunk(destination, startSegmentTableRow);
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
writeChunk(destination, stringToChunk(id.toString(16)));
|
|
return writeChunkAndReturn(destination, startSegmentTableRow2);
|
|
}
|
|
|
|
case HTML_COLGROUP_MODE:
|
|
{
|
|
writeChunk(destination, startSegmentColGroup);
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
writeChunk(destination, stringToChunk(id.toString(16)));
|
|
return writeChunkAndReturn(destination, startSegmentColGroup2);
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new Error('Unknown insertion mode. This is a bug in React.');
|
|
}
|
|
}
|
|
}
|
|
function writeEndSegment(destination, formatContext) {
|
|
switch (formatContext.insertionMode) {
|
|
case ROOT_HTML_MODE:
|
|
case HTML_MODE:
|
|
{
|
|
return writeChunkAndReturn(destination, endSegmentHTML);
|
|
}
|
|
|
|
case SVG_MODE:
|
|
{
|
|
return writeChunkAndReturn(destination, endSegmentSVG);
|
|
}
|
|
|
|
case MATHML_MODE:
|
|
{
|
|
return writeChunkAndReturn(destination, endSegmentMathML);
|
|
}
|
|
|
|
case HTML_TABLE_MODE:
|
|
{
|
|
return writeChunkAndReturn(destination, endSegmentTable);
|
|
}
|
|
|
|
case HTML_TABLE_BODY_MODE:
|
|
{
|
|
return writeChunkAndReturn(destination, endSegmentTableBody);
|
|
}
|
|
|
|
case HTML_TABLE_ROW_MODE:
|
|
{
|
|
return writeChunkAndReturn(destination, endSegmentTableRow);
|
|
}
|
|
|
|
case HTML_COLGROUP_MODE:
|
|
{
|
|
return writeChunkAndReturn(destination, endSegmentColGroup);
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new Error('Unknown insertion mode. This is a bug in React.');
|
|
}
|
|
}
|
|
} // Instruction Set
|
|
// The following code is the source scripts that we then minify and inline below,
|
|
// with renamed function names that we hope don't collide:
|
|
// const COMMENT_NODE = 8;
|
|
// const SUSPENSE_START_DATA = '$';
|
|
// const SUSPENSE_END_DATA = '/$';
|
|
// const SUSPENSE_PENDING_START_DATA = '$?';
|
|
// const SUSPENSE_FALLBACK_START_DATA = '$!';
|
|
//
|
|
// function clientRenderBoundary(suspenseBoundaryID, errorDigest, errorMsg, errorComponentStack) {
|
|
// // Find the fallback's first element.
|
|
// const suspenseIdNode = document.getElementById(suspenseBoundaryID);
|
|
// if (!suspenseIdNode) {
|
|
// // The user must have already navigated away from this tree.
|
|
// // E.g. because the parent was hydrated.
|
|
// return;
|
|
// }
|
|
// // Find the boundary around the fallback. This is always the previous node.
|
|
// const suspenseNode = suspenseIdNode.previousSibling;
|
|
// // Tag it to be client rendered.
|
|
// suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
|
|
// // assign error metadata to first sibling
|
|
// let dataset = suspenseIdNode.dataset;
|
|
// if (errorDigest) dataset.dgst = errorDigest;
|
|
// if (errorMsg) dataset.msg = errorMsg;
|
|
// if (errorComponentStack) dataset.stck = errorComponentStack;
|
|
// // Tell React to retry it if the parent already hydrated.
|
|
// if (suspenseNode._reactRetry) {
|
|
// suspenseNode._reactRetry();
|
|
// }
|
|
// }
|
|
//
|
|
// function completeBoundary(suspenseBoundaryID, contentID) {
|
|
// // Find the fallback's first element.
|
|
// const suspenseIdNode = document.getElementById(suspenseBoundaryID);
|
|
// const contentNode = document.getElementById(contentID);
|
|
// // We'll detach the content node so that regardless of what happens next we don't leave in the tree.
|
|
// // This might also help by not causing recalcing each time we move a child from here to the target.
|
|
// contentNode.parentNode.removeChild(contentNode);
|
|
// if (!suspenseIdNode) {
|
|
// // The user must have already navigated away from this tree.
|
|
// // E.g. because the parent was hydrated. That's fine there's nothing to do
|
|
// // but we have to make sure that we already deleted the container node.
|
|
// return;
|
|
// }
|
|
// // Find the boundary around the fallback. This is always the previous node.
|
|
// const suspenseNode = suspenseIdNode.previousSibling;
|
|
//
|
|
// // Clear all the existing children. This is complicated because
|
|
// // there can be embedded Suspense boundaries in the fallback.
|
|
// // This is similar to clearSuspenseBoundary in ReactDOMHostConfig.
|
|
// // TODO: We could avoid this if we never emitted suspense boundaries in fallback trees.
|
|
// // They never hydrate anyway. However, currently we support incrementally loading the fallback.
|
|
// const parentInstance = suspenseNode.parentNode;
|
|
// let node = suspenseNode.nextSibling;
|
|
// let depth = 0;
|
|
// do {
|
|
// if (node && node.nodeType === COMMENT_NODE) {
|
|
// const data = node.data;
|
|
// if (data === SUSPENSE_END_DATA) {
|
|
// if (depth === 0) {
|
|
// break;
|
|
// } else {
|
|
// depth--;
|
|
// }
|
|
// } else if (
|
|
// data === SUSPENSE_START_DATA ||
|
|
// data === SUSPENSE_PENDING_START_DATA ||
|
|
// data === SUSPENSE_FALLBACK_START_DATA
|
|
// ) {
|
|
// depth++;
|
|
// }
|
|
// }
|
|
//
|
|
// const nextNode = node.nextSibling;
|
|
// parentInstance.removeChild(node);
|
|
// node = nextNode;
|
|
// } while (node);
|
|
//
|
|
// const endOfBoundary = node;
|
|
//
|
|
// // Insert all the children from the contentNode between the start and end of suspense boundary.
|
|
// while (contentNode.firstChild) {
|
|
// parentInstance.insertBefore(contentNode.firstChild, endOfBoundary);
|
|
// }
|
|
// suspenseNode.data = SUSPENSE_START_DATA;
|
|
// if (suspenseNode._reactRetry) {
|
|
// suspenseNode._reactRetry();
|
|
// }
|
|
// }
|
|
//
|
|
// function completeSegment(containerID, placeholderID) {
|
|
// const segmentContainer = document.getElementById(containerID);
|
|
// const placeholderNode = document.getElementById(placeholderID);
|
|
// // We always expect both nodes to exist here because, while we might
|
|
// // have navigated away from the main tree, we still expect the detached
|
|
// // tree to exist.
|
|
// segmentContainer.parentNode.removeChild(segmentContainer);
|
|
// while (segmentContainer.firstChild) {
|
|
// placeholderNode.parentNode.insertBefore(
|
|
// segmentContainer.firstChild,
|
|
// placeholderNode,
|
|
// );
|
|
// }
|
|
// placeholderNode.parentNode.removeChild(placeholderNode);
|
|
// }
|
|
|
|
var completeSegmentFunction = 'function $RS(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)}';
|
|
var completeBoundaryFunction = 'function $RC(a,b){a=document.getElementById(a);b=document.getElementById(b);b.parentNode.removeChild(b);if(a){a=a.previousSibling;var f=a.parentNode,c=a.nextSibling,e=0;do{if(c&&8===c.nodeType){var d=c.data;if("/$"===d)if(0===e)break;else e--;else"$"!==d&&"$?"!==d&&"$!"!==d||e++}d=c.nextSibling;f.removeChild(c);c=d}while(c);for(;b.firstChild;)f.insertBefore(b.firstChild,c);a.data="$";a._reactRetry&&a._reactRetry()}}';
|
|
var clientRenderFunction = 'function $RX(b,c,d,e){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),b._reactRetry&&b._reactRetry())}';
|
|
var completeSegmentScript1Full = stringToPrecomputedChunk(completeSegmentFunction + ';$RS("');
|
|
var completeSegmentScript1Partial = stringToPrecomputedChunk('$RS("');
|
|
var completeSegmentScript2 = stringToPrecomputedChunk('","');
|
|
var completeSegmentScript3 = stringToPrecomputedChunk('")</script>');
|
|
function writeCompletedSegmentInstruction(destination, responseState, contentSegmentID) {
|
|
writeChunk(destination, responseState.startInlineScript);
|
|
|
|
if (!responseState.sentCompleteSegmentFunction) {
|
|
// The first time we write this, we'll need to include the full implementation.
|
|
responseState.sentCompleteSegmentFunction = true;
|
|
writeChunk(destination, completeSegmentScript1Full);
|
|
} else {
|
|
// Future calls can just reuse the same function.
|
|
writeChunk(destination, completeSegmentScript1Partial);
|
|
}
|
|
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
var formattedID = stringToChunk(contentSegmentID.toString(16));
|
|
writeChunk(destination, formattedID);
|
|
writeChunk(destination, completeSegmentScript2);
|
|
writeChunk(destination, responseState.placeholderPrefix);
|
|
writeChunk(destination, formattedID);
|
|
return writeChunkAndReturn(destination, completeSegmentScript3);
|
|
}
|
|
var completeBoundaryScript1Full = stringToPrecomputedChunk(completeBoundaryFunction + ';$RC("');
|
|
var completeBoundaryScript1Partial = stringToPrecomputedChunk('$RC("');
|
|
var completeBoundaryScript2 = stringToPrecomputedChunk('","');
|
|
var completeBoundaryScript3 = stringToPrecomputedChunk('")</script>');
|
|
function writeCompletedBoundaryInstruction(destination, responseState, boundaryID, contentSegmentID) {
|
|
writeChunk(destination, responseState.startInlineScript);
|
|
|
|
if (!responseState.sentCompleteBoundaryFunction) {
|
|
// The first time we write this, we'll need to include the full implementation.
|
|
responseState.sentCompleteBoundaryFunction = true;
|
|
writeChunk(destination, completeBoundaryScript1Full);
|
|
} else {
|
|
// Future calls can just reuse the same function.
|
|
writeChunk(destination, completeBoundaryScript1Partial);
|
|
}
|
|
|
|
if (boundaryID === null) {
|
|
throw new Error('An ID must have been assigned before we can complete the boundary.');
|
|
}
|
|
|
|
var formattedContentID = stringToChunk(contentSegmentID.toString(16));
|
|
writeChunk(destination, boundaryID);
|
|
writeChunk(destination, completeBoundaryScript2);
|
|
writeChunk(destination, responseState.segmentPrefix);
|
|
writeChunk(destination, formattedContentID);
|
|
return writeChunkAndReturn(destination, completeBoundaryScript3);
|
|
}
|
|
var clientRenderScript1Full = stringToPrecomputedChunk(clientRenderFunction + ';$RX("');
|
|
var clientRenderScript1Partial = stringToPrecomputedChunk('$RX("');
|
|
var clientRenderScript1A = stringToPrecomputedChunk('"');
|
|
var clientRenderScript2 = stringToPrecomputedChunk(')</script>');
|
|
var clientRenderErrorScriptArgInterstitial = stringToPrecomputedChunk(',');
|
|
function writeClientRenderBoundaryInstruction(destination, responseState, boundaryID, errorDigest, errorMessage, errorComponentStack) {
|
|
writeChunk(destination, responseState.startInlineScript);
|
|
|
|
if (!responseState.sentClientRenderFunction) {
|
|
// The first time we write this, we'll need to include the full implementation.
|
|
responseState.sentClientRenderFunction = true;
|
|
writeChunk(destination, clientRenderScript1Full);
|
|
} else {
|
|
// Future calls can just reuse the same function.
|
|
writeChunk(destination, clientRenderScript1Partial);
|
|
}
|
|
|
|
if (boundaryID === null) {
|
|
throw new Error('An ID must have been assigned before we can complete the boundary.');
|
|
}
|
|
|
|
writeChunk(destination, boundaryID);
|
|
writeChunk(destination, clientRenderScript1A);
|
|
|
|
if (errorDigest || errorMessage || errorComponentStack) {
|
|
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
|
|
writeChunk(destination, stringToChunk(escapeJSStringsForInstructionScripts(errorDigest || '')));
|
|
}
|
|
|
|
if (errorMessage || errorComponentStack) {
|
|
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
|
|
writeChunk(destination, stringToChunk(escapeJSStringsForInstructionScripts(errorMessage || '')));
|
|
}
|
|
|
|
if (errorComponentStack) {
|
|
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
|
|
writeChunk(destination, stringToChunk(escapeJSStringsForInstructionScripts(errorComponentStack)));
|
|
}
|
|
|
|
return writeChunkAndReturn(destination, clientRenderScript2);
|
|
}
|
|
var regexForJSStringsInScripts = /[<\u2028\u2029]/g;
|
|
|
|
function escapeJSStringsForInstructionScripts(input) {
|
|
var escaped = JSON.stringify(input);
|
|
return escaped.replace(regexForJSStringsInScripts, function (match) {
|
|
switch (match) {
|
|
// santizing breaking out of strings and script tags
|
|
case '<':
|
|
return "\\u003c";
|
|
|
|
case "\u2028":
|
|
return "\\u2028";
|
|
|
|
case "\u2029":
|
|
return "\\u2029";
|
|
|
|
default:
|
|
{
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
throw new Error('escapeJSStringsForInstructionScripts encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
var assign = Object.assign;
|
|
|
|
// ATTENTION
|
|
// When adding new symbols to this file,
|
|
// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
|
|
// The Symbol used to tag the ReactElement-like types.
|
|
var REACT_ELEMENT_TYPE = Symbol.for('react.element');
|
|
var REACT_PORTAL_TYPE = Symbol.for('react.portal');
|
|
var REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
|
|
var REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
|
|
var REACT_PROFILER_TYPE = Symbol.for('react.profiler');
|
|
var REACT_PROVIDER_TYPE = Symbol.for('react.provider');
|
|
var REACT_CONTEXT_TYPE = Symbol.for('react.context');
|
|
var REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
|
|
var REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
|
|
var REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
|
|
var REACT_MEMO_TYPE = Symbol.for('react.memo');
|
|
var REACT_LAZY_TYPE = Symbol.for('react.lazy');
|
|
var REACT_SCOPE_TYPE = Symbol.for('react.scope');
|
|
var REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for('react.debug_trace_mode');
|
|
var REACT_LEGACY_HIDDEN_TYPE = Symbol.for('react.legacy_hidden');
|
|
var REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = Symbol.for('react.default_value');
|
|
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
|
|
var FAUX_ITERATOR_SYMBOL = '@@iterator';
|
|
function getIteratorFn(maybeIterable) {
|
|
if (maybeIterable === null || typeof maybeIterable !== 'object') {
|
|
return null;
|
|
}
|
|
|
|
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
|
|
|
|
if (typeof maybeIterator === 'function') {
|
|
return maybeIterator;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getWrappedName(outerType, innerType, wrapperName) {
|
|
var displayName = outerType.displayName;
|
|
|
|
if (displayName) {
|
|
return displayName;
|
|
}
|
|
|
|
var functionName = innerType.displayName || innerType.name || '';
|
|
return functionName !== '' ? wrapperName + "(" + functionName + ")" : wrapperName;
|
|
} // Keep in sync with react-reconciler/getComponentNameFromFiber
|
|
|
|
|
|
function getContextName(type) {
|
|
return type.displayName || 'Context';
|
|
} // Note that the reconciler package should generally prefer to use getComponentNameFromFiber() instead.
|
|
|
|
|
|
function getComponentNameFromType(type) {
|
|
if (type == null) {
|
|
// Host root, text node or just invalid type.
|
|
return null;
|
|
}
|
|
|
|
{
|
|
if (typeof type.tag === 'number') {
|
|
error('Received an unexpected object in getComponentNameFromType(). ' + 'This is likely a bug in React. Please file an issue.');
|
|
}
|
|
}
|
|
|
|
if (typeof type === 'function') {
|
|
return type.displayName || type.name || null;
|
|
}
|
|
|
|
if (typeof type === 'string') {
|
|
return type;
|
|
}
|
|
|
|
switch (type) {
|
|
case REACT_FRAGMENT_TYPE:
|
|
return 'Fragment';
|
|
|
|
case REACT_PORTAL_TYPE:
|
|
return 'Portal';
|
|
|
|
case REACT_PROFILER_TYPE:
|
|
return 'Profiler';
|
|
|
|
case REACT_STRICT_MODE_TYPE:
|
|
return 'StrictMode';
|
|
|
|
case REACT_SUSPENSE_TYPE:
|
|
return 'Suspense';
|
|
|
|
case REACT_SUSPENSE_LIST_TYPE:
|
|
return 'SuspenseList';
|
|
|
|
}
|
|
|
|
if (typeof type === 'object') {
|
|
switch (type.$$typeof) {
|
|
case REACT_CONTEXT_TYPE:
|
|
var context = type;
|
|
return getContextName(context) + '.Consumer';
|
|
|
|
case REACT_PROVIDER_TYPE:
|
|
var provider = type;
|
|
return getContextName(provider._context) + '.Provider';
|
|
|
|
case REACT_FORWARD_REF_TYPE:
|
|
return getWrappedName(type, type.render, 'ForwardRef');
|
|
|
|
case REACT_MEMO_TYPE:
|
|
var outerName = type.displayName || null;
|
|
|
|
if (outerName !== null) {
|
|
return outerName;
|
|
}
|
|
|
|
return getComponentNameFromType(type.type) || 'Memo';
|
|
|
|
case REACT_LAZY_TYPE:
|
|
{
|
|
var lazyComponent = type;
|
|
var payload = lazyComponent._payload;
|
|
var init = lazyComponent._init;
|
|
|
|
try {
|
|
return getComponentNameFromType(init(payload));
|
|
} catch (x) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Helpers to patch console.logs to avoid logging during side-effect free
|
|
// replaying on render function. This currently only patches the object
|
|
// lazily which won't cover if the log function was extracted eagerly.
|
|
// We could also eagerly patch the method.
|
|
var disabledDepth = 0;
|
|
var prevLog;
|
|
var prevInfo;
|
|
var prevWarn;
|
|
var prevError;
|
|
var prevGroup;
|
|
var prevGroupCollapsed;
|
|
var prevGroupEnd;
|
|
|
|
function disabledLog() {}
|
|
|
|
disabledLog.__reactDisabledLog = true;
|
|
function disableLogs() {
|
|
{
|
|
if (disabledDepth === 0) {
|
|
/* eslint-disable react-internal/no-production-logging */
|
|
prevLog = console.log;
|
|
prevInfo = console.info;
|
|
prevWarn = console.warn;
|
|
prevError = console.error;
|
|
prevGroup = console.group;
|
|
prevGroupCollapsed = console.groupCollapsed;
|
|
prevGroupEnd = console.groupEnd; // https://github.com/facebook/react/issues/19099
|
|
|
|
var props = {
|
|
configurable: true,
|
|
enumerable: true,
|
|
value: disabledLog,
|
|
writable: true
|
|
}; // $FlowFixMe Flow thinks console is immutable.
|
|
|
|
Object.defineProperties(console, {
|
|
info: props,
|
|
log: props,
|
|
warn: props,
|
|
error: props,
|
|
group: props,
|
|
groupCollapsed: props,
|
|
groupEnd: props
|
|
});
|
|
/* eslint-enable react-internal/no-production-logging */
|
|
}
|
|
|
|
disabledDepth++;
|
|
}
|
|
}
|
|
function reenableLogs() {
|
|
{
|
|
disabledDepth--;
|
|
|
|
if (disabledDepth === 0) {
|
|
/* eslint-disable react-internal/no-production-logging */
|
|
var props = {
|
|
configurable: true,
|
|
enumerable: true,
|
|
writable: true
|
|
}; // $FlowFixMe Flow thinks console is immutable.
|
|
|
|
Object.defineProperties(console, {
|
|
log: assign({}, props, {
|
|
value: prevLog
|
|
}),
|
|
info: assign({}, props, {
|
|
value: prevInfo
|
|
}),
|
|
warn: assign({}, props, {
|
|
value: prevWarn
|
|
}),
|
|
error: assign({}, props, {
|
|
value: prevError
|
|
}),
|
|
group: assign({}, props, {
|
|
value: prevGroup
|
|
}),
|
|
groupCollapsed: assign({}, props, {
|
|
value: prevGroupCollapsed
|
|
}),
|
|
groupEnd: assign({}, props, {
|
|
value: prevGroupEnd
|
|
})
|
|
});
|
|
/* eslint-enable react-internal/no-production-logging */
|
|
}
|
|
|
|
if (disabledDepth < 0) {
|
|
error('disabledDepth fell below zero. ' + 'This is a bug in React. Please file an issue.');
|
|
}
|
|
}
|
|
}
|
|
|
|
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
|
var prefix;
|
|
function describeBuiltInComponentFrame(name, source, ownerFn) {
|
|
{
|
|
if (prefix === undefined) {
|
|
// Extract the VM specific prefix used by each line.
|
|
try {
|
|
throw Error();
|
|
} catch (x) {
|
|
var match = x.stack.trim().match(/\n( *(at )?)/);
|
|
prefix = match && match[1] || '';
|
|
}
|
|
} // We use the prefix to ensure our stacks line up with native stack frames.
|
|
|
|
|
|
return '\n' + prefix + name;
|
|
}
|
|
}
|
|
var reentry = false;
|
|
var componentFrameCache;
|
|
|
|
{
|
|
var PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
|
componentFrameCache = new PossiblyWeakMap();
|
|
}
|
|
|
|
function describeNativeComponentFrame(fn, construct) {
|
|
// If something asked for a stack inside a fake render, it should get ignored.
|
|
if ( !fn || reentry) {
|
|
return '';
|
|
}
|
|
|
|
{
|
|
var frame = componentFrameCache.get(fn);
|
|
|
|
if (frame !== undefined) {
|
|
return frame;
|
|
}
|
|
}
|
|
|
|
var control;
|
|
reentry = true;
|
|
var previousPrepareStackTrace = Error.prepareStackTrace; // $FlowFixMe It does accept undefined.
|
|
|
|
Error.prepareStackTrace = undefined;
|
|
var previousDispatcher;
|
|
|
|
{
|
|
previousDispatcher = ReactCurrentDispatcher.current; // Set the dispatcher in DEV because this might be call in the render function
|
|
// for warnings.
|
|
|
|
ReactCurrentDispatcher.current = null;
|
|
disableLogs();
|
|
}
|
|
|
|
try {
|
|
// This should throw.
|
|
if (construct) {
|
|
// Something should be setting the props in the constructor.
|
|
var Fake = function () {
|
|
throw Error();
|
|
}; // $FlowFixMe
|
|
|
|
|
|
Object.defineProperty(Fake.prototype, 'props', {
|
|
set: function () {
|
|
// We use a throwing setter instead of frozen or non-writable props
|
|
// because that won't throw in a non-strict mode function.
|
|
throw Error();
|
|
}
|
|
});
|
|
|
|
if (typeof Reflect === 'object' && Reflect.construct) {
|
|
// We construct a different control for this case to include any extra
|
|
// frames added by the construct call.
|
|
try {
|
|
Reflect.construct(Fake, []);
|
|
} catch (x) {
|
|
control = x;
|
|
}
|
|
|
|
Reflect.construct(fn, [], Fake);
|
|
} else {
|
|
try {
|
|
Fake.call();
|
|
} catch (x) {
|
|
control = x;
|
|
}
|
|
|
|
fn.call(Fake.prototype);
|
|
}
|
|
} else {
|
|
try {
|
|
throw Error();
|
|
} catch (x) {
|
|
control = x;
|
|
}
|
|
|
|
fn();
|
|
}
|
|
} catch (sample) {
|
|
// This is inlined manually because closure doesn't do it for us.
|
|
if (sample && control && typeof sample.stack === 'string') {
|
|
// This extracts the first frame from the sample that isn't also in the control.
|
|
// Skipping one frame that we assume is the frame that calls the two.
|
|
var sampleLines = sample.stack.split('\n');
|
|
var controlLines = control.stack.split('\n');
|
|
var s = sampleLines.length - 1;
|
|
var c = controlLines.length - 1;
|
|
|
|
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
|
|
// We expect at least one stack frame to be shared.
|
|
// Typically this will be the root most one. However, stack frames may be
|
|
// cut off due to maximum stack limits. In this case, one maybe cut off
|
|
// earlier than the other. We assume that the sample is longer or the same
|
|
// and there for cut off earlier. So we should find the root most frame in
|
|
// the sample somewhere in the control.
|
|
c--;
|
|
}
|
|
|
|
for (; s >= 1 && c >= 0; s--, c--) {
|
|
// Next we find the first one that isn't the same which should be the
|
|
// frame that called our sample function and the control.
|
|
if (sampleLines[s] !== controlLines[c]) {
|
|
// In V8, the first line is describing the message but other VMs don't.
|
|
// If we're about to return the first line, and the control is also on the same
|
|
// line, that's a pretty good indicator that our sample threw at same line as
|
|
// the control. I.e. before we entered the sample frame. So we ignore this result.
|
|
// This can happen if you passed a class to function component, or non-function.
|
|
if (s !== 1 || c !== 1) {
|
|
do {
|
|
s--;
|
|
c--; // We may still have similar intermediate frames from the construct call.
|
|
// The next one that isn't the same should be our match though.
|
|
|
|
if (c < 0 || sampleLines[s] !== controlLines[c]) {
|
|
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
|
|
var _frame = '\n' + sampleLines[s].replace(' at new ', ' at '); // If our component frame is labeled "<anonymous>"
|
|
// but we have a user-provided "displayName"
|
|
// splice it in to make the stack more readable.
|
|
|
|
|
|
if (fn.displayName && _frame.includes('<anonymous>')) {
|
|
_frame = _frame.replace('<anonymous>', fn.displayName);
|
|
}
|
|
|
|
{
|
|
if (typeof fn === 'function') {
|
|
componentFrameCache.set(fn, _frame);
|
|
}
|
|
} // Return the line we found.
|
|
|
|
|
|
return _frame;
|
|
}
|
|
} while (s >= 1 && c >= 0);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
reentry = false;
|
|
|
|
{
|
|
ReactCurrentDispatcher.current = previousDispatcher;
|
|
reenableLogs();
|
|
}
|
|
|
|
Error.prepareStackTrace = previousPrepareStackTrace;
|
|
} // Fallback to just using the name if we couldn't make it throw.
|
|
|
|
|
|
var name = fn ? fn.displayName || fn.name : '';
|
|
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
|
|
|
|
{
|
|
if (typeof fn === 'function') {
|
|
componentFrameCache.set(fn, syntheticFrame);
|
|
}
|
|
}
|
|
|
|
return syntheticFrame;
|
|
}
|
|
|
|
function describeClassComponentFrame(ctor, source, ownerFn) {
|
|
{
|
|
return describeNativeComponentFrame(ctor, true);
|
|
}
|
|
}
|
|
function describeFunctionComponentFrame(fn, source, ownerFn) {
|
|
{
|
|
return describeNativeComponentFrame(fn, false);
|
|
}
|
|
}
|
|
|
|
function shouldConstruct(Component) {
|
|
var prototype = Component.prototype;
|
|
return !!(prototype && prototype.isReactComponent);
|
|
}
|
|
|
|
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
|
|
|
|
if (type == null) {
|
|
return '';
|
|
}
|
|
|
|
if (typeof type === 'function') {
|
|
{
|
|
return describeNativeComponentFrame(type, shouldConstruct(type));
|
|
}
|
|
}
|
|
|
|
if (typeof type === 'string') {
|
|
return describeBuiltInComponentFrame(type);
|
|
}
|
|
|
|
switch (type) {
|
|
case REACT_SUSPENSE_TYPE:
|
|
return describeBuiltInComponentFrame('Suspense');
|
|
|
|
case REACT_SUSPENSE_LIST_TYPE:
|
|
return describeBuiltInComponentFrame('SuspenseList');
|
|
}
|
|
|
|
if (typeof type === 'object') {
|
|
switch (type.$$typeof) {
|
|
case REACT_FORWARD_REF_TYPE:
|
|
return describeFunctionComponentFrame(type.render);
|
|
|
|
case REACT_MEMO_TYPE:
|
|
// Memo may contain any component type so we recursively resolve it.
|
|
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
|
|
|
|
case REACT_LAZY_TYPE:
|
|
{
|
|
var lazyComponent = type;
|
|
var payload = lazyComponent._payload;
|
|
var init = lazyComponent._init;
|
|
|
|
try {
|
|
// Lazy may contain any component type so we recursively resolve it.
|
|
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
|
|
} catch (x) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
var loggedTypeFailures = {};
|
|
var ReactDebugCurrentFrame = Rea |