mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
269610e2da
This isn't configured to a dynamic value on www, so hardcode here to
false.
DiffTrain build for [379dd741e9](https://github.com/facebook/react/commit/379dd741e9aabefb880af1794d3c828984f9e143)
[View git log for this commit](https://github.com/facebook/react/commits/379dd741e9aabefb880af1794d3c828984f9e143)
39655 lines
1.2 MiB
Plaintext
39655 lines
1.2 MiB
Plaintext
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @noflow
|
|
* @nolint
|
|
* @preventMunge
|
|
* @preserve-invariant-messages
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
if (__DEV__) {
|
|
(function() {
|
|
"use strict";
|
|
|
|
var React = require("react");
|
|
var Scheduler = require("scheduler");
|
|
|
|
var Internals = {
|
|
usingClientEntryPoint: false,
|
|
Events: null,
|
|
Dispatcher: {
|
|
current: null
|
|
}
|
|
};
|
|
|
|
// This refers to a WWW module.
|
|
var warningWWW = require("warning");
|
|
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) {
|
|
{
|
|
var React = require("react");
|
|
|
|
var ReactSharedInternals =
|
|
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; // Defensive in case this is fired before React is initialized.
|
|
|
|
if (ReactSharedInternals != null) {
|
|
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
|
var stack = ReactDebugCurrentFrame.getStackAddendum();
|
|
|
|
if (stack !== "") {
|
|
format += "%s";
|
|
args.push(stack);
|
|
}
|
|
} // TODO: don't ignore level and pass it down somewhere too.
|
|
|
|
args.unshift(format);
|
|
args.unshift(false);
|
|
warningWWW.apply(null, args);
|
|
}
|
|
}
|
|
|
|
var FunctionComponent = 0;
|
|
var ClassComponent = 1;
|
|
var IndeterminateComponent = 2; // Before we know whether it is function or class
|
|
|
|
var HostRoot = 3; // Root of a host tree. Could be nested inside another node.
|
|
|
|
var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
|
|
|
|
var HostComponent = 5;
|
|
var HostText = 6;
|
|
var Fragment = 7;
|
|
var Mode = 8;
|
|
var ContextConsumer = 9;
|
|
var ContextProvider = 10;
|
|
var ForwardRef = 11;
|
|
var Profiler = 12;
|
|
var SuspenseComponent = 13;
|
|
var MemoComponent = 14;
|
|
var SimpleMemoComponent = 15;
|
|
var LazyComponent = 16;
|
|
var IncompleteClassComponent = 17;
|
|
var DehydratedFragment = 18;
|
|
var SuspenseListComponent = 19;
|
|
var ScopeComponent = 21;
|
|
var OffscreenComponent = 22;
|
|
var LegacyHiddenComponent = 23;
|
|
var CacheComponent = 24;
|
|
var TracingMarkerComponent = 25;
|
|
var HostResource = 26;
|
|
var HostSingleton = 27;
|
|
|
|
// Below code forked from dom-accessibility-api
|
|
var tagToRoleMappings = {
|
|
ARTICLE: "article",
|
|
ASIDE: "complementary",
|
|
BODY: "document",
|
|
BUTTON: "button",
|
|
DATALIST: "listbox",
|
|
DD: "definition",
|
|
DETAILS: "group",
|
|
DIALOG: "dialog",
|
|
DT: "term",
|
|
FIELDSET: "group",
|
|
FIGURE: "figure",
|
|
// WARNING: Only with an accessible name
|
|
FORM: "form",
|
|
FOOTER: "contentinfo",
|
|
H1: "heading",
|
|
H2: "heading",
|
|
H3: "heading",
|
|
H4: "heading",
|
|
H5: "heading",
|
|
H6: "heading",
|
|
HEADER: "banner",
|
|
HR: "separator",
|
|
LEGEND: "legend",
|
|
LI: "listitem",
|
|
MATH: "math",
|
|
MAIN: "main",
|
|
MENU: "list",
|
|
NAV: "navigation",
|
|
OL: "list",
|
|
OPTGROUP: "group",
|
|
// WARNING: Only in certain context
|
|
OPTION: "option",
|
|
OUTPUT: "status",
|
|
PROGRESS: "progressbar",
|
|
// WARNING: Only with an accessible name
|
|
SECTION: "region",
|
|
SUMMARY: "button",
|
|
TABLE: "table",
|
|
TBODY: "rowgroup",
|
|
TEXTAREA: "textbox",
|
|
TFOOT: "rowgroup",
|
|
// WARNING: Only in certain context
|
|
TD: "cell",
|
|
TH: "columnheader",
|
|
THEAD: "rowgroup",
|
|
TR: "row",
|
|
UL: "list"
|
|
};
|
|
|
|
function getImplicitRole(element) {
|
|
var mappedByTag = tagToRoleMappings[element.tagName];
|
|
|
|
if (mappedByTag !== undefined) {
|
|
return mappedByTag;
|
|
}
|
|
|
|
switch (element.tagName) {
|
|
case "A":
|
|
case "AREA":
|
|
case "LINK":
|
|
if (element.hasAttribute("href")) {
|
|
return "link";
|
|
}
|
|
|
|
break;
|
|
|
|
case "IMG":
|
|
if ((element.getAttribute("alt") || "").length > 0) {
|
|
return "img";
|
|
}
|
|
|
|
break;
|
|
|
|
case "INPUT": {
|
|
var type = element.type;
|
|
|
|
switch (type) {
|
|
case "button":
|
|
case "image":
|
|
case "reset":
|
|
case "submit":
|
|
return "button";
|
|
|
|
case "checkbox":
|
|
case "radio":
|
|
return type;
|
|
|
|
case "range":
|
|
return "slider";
|
|
|
|
case "email":
|
|
case "tel":
|
|
case "text":
|
|
case "url":
|
|
if (element.hasAttribute("list")) {
|
|
return "combobox";
|
|
}
|
|
|
|
return "textbox";
|
|
|
|
case "search":
|
|
if (element.hasAttribute("list")) {
|
|
return "combobox";
|
|
}
|
|
|
|
return "searchbox";
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
case "SELECT":
|
|
if (element.hasAttribute("multiple") || element.size > 1) {
|
|
return "listbox";
|
|
}
|
|
|
|
return "combobox";
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getExplicitRoles(element) {
|
|
var role = element.getAttribute("role");
|
|
|
|
if (role) {
|
|
return role.trim().split(" ");
|
|
}
|
|
|
|
return null;
|
|
} // https://w3c.github.io/html-aria/#document-conformance-requirements-for-use-of-aria-attributes-in-html
|
|
|
|
function hasRole(element, role) {
|
|
var explicitRoles = getExplicitRoles(element);
|
|
|
|
if (explicitRoles !== null && explicitRoles.indexOf(role) >= 0) {
|
|
return true;
|
|
}
|
|
|
|
return role === getImplicitRole(element);
|
|
}
|
|
|
|
var debugRenderPhaseSideEffectsForStrictMode = false;
|
|
var enableSchedulingProfiler = false;
|
|
var enableProfilerTimer = false;
|
|
var disableCommentsAsDOMContainers = true;
|
|
var enableSuspenseCallback = true;
|
|
var enableClientRenderFallbackOnTextMismatch = true;
|
|
var createRootStrictEffectsByDefault = false;
|
|
var enableLazyContextPropagation = false;
|
|
var enableLegacyHidden = false;
|
|
var enableCustomElementPropertySupport = false;
|
|
|
|
var allNativeEvents = new Set();
|
|
|
|
{
|
|
allNativeEvents.add("beforeblur");
|
|
allNativeEvents.add("afterblur");
|
|
}
|
|
/**
|
|
* Mapping from registration name to event name
|
|
*/
|
|
|
|
var registrationNameDependencies = {};
|
|
/**
|
|
* Mapping from lowercase registration names to the properly cased version,
|
|
* used to warn in the case of missing event handlers. Available
|
|
* only in true.
|
|
* @type {Object}
|
|
*/
|
|
|
|
var possibleRegistrationNames = {}; // Trust the developer to only use possibleRegistrationNames in true
|
|
|
|
function registerTwoPhaseEvent(registrationName, dependencies) {
|
|
registerDirectEvent(registrationName, dependencies);
|
|
registerDirectEvent(registrationName + "Capture", dependencies);
|
|
}
|
|
function registerDirectEvent(registrationName, dependencies) {
|
|
{
|
|
if (registrationNameDependencies[registrationName]) {
|
|
error(
|
|
"EventRegistry: More than one plugin attempted to publish the same " +
|
|
"registration name, `%s`.",
|
|
registrationName
|
|
);
|
|
}
|
|
}
|
|
|
|
registrationNameDependencies[registrationName] = dependencies;
|
|
|
|
{
|
|
var lowerCasedName = registrationName.toLowerCase();
|
|
possibleRegistrationNames[lowerCasedName] = registrationName;
|
|
|
|
if (registrationName === "onDoubleClick") {
|
|
possibleRegistrationNames.ondblclick = registrationName;
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < dependencies.length; i++) {
|
|
allNativeEvents.add(dependencies[i]);
|
|
}
|
|
}
|
|
|
|
var canUseDOM = !!(
|
|
typeof window !== "undefined" &&
|
|
typeof window.document !== "undefined" &&
|
|
typeof window.document.createElement !== "undefined"
|
|
);
|
|
|
|
// $FlowFixMe[method-unbinding]
|
|
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
|
|
/*
|
|
* The `'' + value` pattern (used 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"; // $FlowFixMe
|
|
|
|
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 checkKeyStringCoercion(value) {
|
|
{
|
|
if (willCoercionThrow(value)) {
|
|
error(
|
|
"The provided key is an 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)
|
|
}
|
|
}
|
|
}
|
|
function checkPropStringCoercion(value, propName) {
|
|
{
|
|
if (willCoercionThrow(value)) {
|
|
error(
|
|
"The provided `%s` prop 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 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)
|
|
}
|
|
}
|
|
}
|
|
function checkFormFieldValueStringCoercion(value) {
|
|
{
|
|
if (willCoercionThrow(value)) {
|
|
error(
|
|
"Form field values (value, checked, defaultValue, or defaultChecked props)" +
|
|
" must be strings, not %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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag) {
|
|
if (propertyInfo !== null) {
|
|
return propertyInfo.type === RESERVED;
|
|
}
|
|
|
|
if (isCustomComponentTag) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
name.length > 2 &&
|
|
(name[0] === "o" || name[0] === "O") &&
|
|
(name[1] === "n" || name[1] === "N")
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
function shouldRemoveAttributeWithWarning(
|
|
name,
|
|
value,
|
|
propertyInfo,
|
|
isCustomComponentTag
|
|
) {
|
|
if (propertyInfo !== null && propertyInfo.type === RESERVED) {
|
|
return false;
|
|
}
|
|
|
|
switch (typeof value) {
|
|
case "function":
|
|
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 shouldRemoveAttribute(
|
|
name,
|
|
value,
|
|
propertyInfo,
|
|
isCustomComponentTag
|
|
) {
|
|
if (value === null || typeof value === "undefined") {
|
|
return true;
|
|
}
|
|
|
|
if (
|
|
shouldRemoveAttributeWithWarning(
|
|
name,
|
|
value,
|
|
propertyInfo,
|
|
isCustomComponentTag
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
if (isCustomComponentTag) {
|
|
return false;
|
|
}
|
|
|
|
if (propertyInfo !== null) {
|
|
switch (propertyInfo.type) {
|
|
case BOOLEAN:
|
|
return !value;
|
|
|
|
case OVERLOADED_BOOLEAN:
|
|
return value === false;
|
|
|
|
case NUMERIC:
|
|
return isNaN(value);
|
|
|
|
case POSITIVE_NUMERIC:
|
|
return isNaN(value) || value < 1;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
function getPropertyInfo(name) {
|
|
return properties.hasOwnProperty(name) ? properties[name] : null;
|
|
} // $FlowFixMe[missing-this-annot]
|
|
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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];
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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); // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
|
|
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); // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
|
|
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); // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
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"; // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
|
|
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) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
properties[attributeName] = new PropertyInfoRecord(
|
|
attributeName,
|
|
STRING,
|
|
false, // mustUseProperty
|
|
attributeName.toLowerCase(), // attributeName
|
|
null, // attributeNamespace
|
|
true, // sanitizeURL
|
|
true
|
|
);
|
|
});
|
|
|
|
// 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;
|
|
|
|
function sanitizeURL(url) {
|
|
{
|
|
if (isJavaScriptProtocol.test(url)) {
|
|
throw new Error(
|
|
"React has blocked a javascript: URL as a security precaution."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the value for a property on a node. Only used in DEV for SSR validation.
|
|
* The "expected" argument is used as a hint of what the expected value is.
|
|
* Some properties have multiple equivalent values.
|
|
*/
|
|
|
|
function getValueForProperty(node, name, expected, propertyInfo) {
|
|
{
|
|
if (propertyInfo.mustUseProperty) {
|
|
var propertyName = propertyInfo.propertyName;
|
|
return node[propertyName];
|
|
} else {
|
|
// This check protects multiple uses of `expected`, which is why the
|
|
// react-internal/safe-string-coercion rule is disabled in several spots
|
|
// below.
|
|
{
|
|
checkAttributeStringCoercion(expected, name);
|
|
}
|
|
|
|
var attributeName = propertyInfo.attributeName;
|
|
var stringValue = null;
|
|
|
|
if (propertyInfo.type === OVERLOADED_BOOLEAN) {
|
|
if (node.hasAttribute(attributeName)) {
|
|
var value = node.getAttribute(attributeName);
|
|
|
|
if (value === "") {
|
|
return true;
|
|
}
|
|
|
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
|
|
return value;
|
|
} // eslint-disable-next-line react-internal/safe-string-coercion
|
|
|
|
if (value === "" + expected) {
|
|
return expected;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
} else if (node.hasAttribute(attributeName)) {
|
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
|
|
// We had an attribute but shouldn't have had one, so read it
|
|
// for the error message.
|
|
return node.getAttribute(attributeName);
|
|
}
|
|
|
|
if (propertyInfo.type === BOOLEAN) {
|
|
// If this was a boolean, it doesn't matter what the value is
|
|
// the fact that we have it is the same as the expected.
|
|
return expected;
|
|
} // Even if this property uses a namespace we use getAttribute
|
|
// because we assume its namespaced name is the same as our config.
|
|
// To use getAttributeNS we need the local name which we don't have
|
|
// in our config atm.
|
|
|
|
stringValue = node.getAttribute(attributeName);
|
|
}
|
|
|
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
|
|
return stringValue === null ? expected : stringValue; // eslint-disable-next-line react-internal/safe-string-coercion
|
|
} else if (stringValue === "" + expected) {
|
|
return expected;
|
|
} else {
|
|
return stringValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Get the value for a attribute on a node. Only used in DEV for SSR validation.
|
|
* The third argument is used as a hint of what the expected value is. Some
|
|
* attributes have multiple equivalent values.
|
|
*/
|
|
|
|
function getValueForAttribute(node, name, expected, isCustomComponentTag) {
|
|
{
|
|
if (!isAttributeNameSafe(name)) {
|
|
return;
|
|
}
|
|
|
|
if (!node.hasAttribute(name)) {
|
|
return expected === undefined ? undefined : null;
|
|
}
|
|
|
|
var value = node.getAttribute(name);
|
|
|
|
{
|
|
checkAttributeStringCoercion(expected, name);
|
|
}
|
|
|
|
if (value === "" + expected) {
|
|
return expected;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|
|
/**
|
|
* Sets the value for a property on a node.
|
|
*
|
|
* @param {DOMElement} node
|
|
* @param {string} name
|
|
* @param {*} value
|
|
*/
|
|
|
|
function setValueForProperty(node, name, value, isCustomComponentTag) {
|
|
var propertyInfo = getPropertyInfo(name);
|
|
|
|
if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) {
|
|
return;
|
|
}
|
|
|
|
if (shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)) {
|
|
value = null;
|
|
}
|
|
|
|
if (isCustomComponentTag || propertyInfo === null) {
|
|
if (isAttributeNameSafe(name)) {
|
|
var _attributeName = name;
|
|
|
|
if (value === null) {
|
|
node.removeAttribute(_attributeName);
|
|
} else {
|
|
{
|
|
checkAttributeStringCoercion(value, name);
|
|
}
|
|
|
|
node.setAttribute(_attributeName, "" + value);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var mustUseProperty = propertyInfo.mustUseProperty;
|
|
|
|
if (mustUseProperty) {
|
|
var propertyName = propertyInfo.propertyName;
|
|
|
|
if (value === null) {
|
|
var type = propertyInfo.type;
|
|
node[propertyName] = type === BOOLEAN ? false : "";
|
|
} else {
|
|
// Contrary to `setAttribute`, object properties are properly
|
|
// `toString`ed by IE8/9.
|
|
node[propertyName] = value;
|
|
}
|
|
|
|
return;
|
|
} // The rest are treated as attributes with special cases.
|
|
|
|
var attributeName = propertyInfo.attributeName,
|
|
attributeNamespace = propertyInfo.attributeNamespace;
|
|
|
|
if (value === null) {
|
|
node.removeAttribute(attributeName);
|
|
} else {
|
|
var _type = propertyInfo.type;
|
|
var attributeValue;
|
|
|
|
if (_type === BOOLEAN || (_type === OVERLOADED_BOOLEAN && value === true)) {
|
|
// If attribute type is boolean, we know for sure it won't be an execution sink
|
|
// and we won't require Trusted Type here.
|
|
attributeValue = "";
|
|
} else {
|
|
// `setAttribute` with objects becomes only `[object]` in IE8/9,
|
|
// ('' + value) makes it output the correct toString()-value.
|
|
{
|
|
{
|
|
checkAttributeStringCoercion(value, attributeName);
|
|
}
|
|
|
|
attributeValue = "" + value;
|
|
}
|
|
|
|
if (propertyInfo.sanitizeURL) {
|
|
sanitizeURL(attributeValue.toString());
|
|
}
|
|
}
|
|
|
|
if (attributeNamespace) {
|
|
node.setAttributeNS(attributeNamespace, attributeName, attributeValue);
|
|
} else {
|
|
node.setAttribute(attributeName, attributeValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
var ReactSharedInternals =
|
|
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
|
|
// 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_SERVER_CONTEXT_TYPE = Symbol.for("react.server_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_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
|
|
var REACT_LEGACY_HIDDEN_TYPE = Symbol.for("react.legacy_hidden");
|
|
var REACT_CACHE_TYPE = Symbol.for("react.cache");
|
|
var REACT_TRACING_MARKER_TYPE = Symbol.for("react.tracing_marker");
|
|
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;
|
|
}
|
|
|
|
var assign = Object.assign;
|
|
|
|
// 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;
|
|
} // $FlowFixMe[prop-missing] found when upgrading Flow
|
|
|
|
fn.call(Fake.prototype);
|
|
}
|
|
} else {
|
|
try {
|
|
throw Error();
|
|
} catch (x) {
|
|
control = x;
|
|
} // TODO(luna): This will currently only throw if the function component
|
|
// tries to access React/ReactDOM/props. We should probably make this throw
|
|
// in simple components too
|
|
|
|
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 "";
|
|
}
|
|
|
|
function describeFiber(fiber) {
|
|
var owner = fiber._debugOwner ? fiber._debugOwner.type : null;
|
|
var source = fiber._debugSource;
|
|
|
|
switch (fiber.tag) {
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
return describeBuiltInComponentFrame(fiber.type);
|
|
|
|
case LazyComponent:
|
|
return describeBuiltInComponentFrame("Lazy");
|
|
|
|
case SuspenseComponent:
|
|
return describeBuiltInComponentFrame("Suspense");
|
|
|
|
case SuspenseListComponent:
|
|
return describeBuiltInComponentFrame("SuspenseList");
|
|
|
|
case FunctionComponent:
|
|
case IndeterminateComponent:
|
|
case SimpleMemoComponent:
|
|
return describeFunctionComponentFrame(fiber.type);
|
|
|
|
case ForwardRef:
|
|
return describeFunctionComponentFrame(fiber.type.render);
|
|
|
|
case ClassComponent:
|
|
return describeClassComponentFrame(fiber.type);
|
|
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function getStackByFiberInDevAndProd(workInProgress) {
|
|
try {
|
|
var info = "";
|
|
var node = workInProgress;
|
|
|
|
do {
|
|
info += describeFiber(node); // $FlowFixMe[incompatible-type] we bail out when we get a null
|
|
|
|
node = node.return;
|
|
} while (node);
|
|
|
|
return info;
|
|
} catch (x) {
|
|
return "\nError generating stack: " + x.message + "\n" + x.stack;
|
|
}
|
|
}
|
|
|
|
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";
|
|
|
|
case REACT_CACHE_TYPE: {
|
|
return "Cache";
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
case REACT_SERVER_CONTEXT_TYPE: {
|
|
var context2 = type;
|
|
return (context2.displayName || context2._globalName) + ".Provider";
|
|
}
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getWrappedName$1(outerType, innerType, wrapperName) {
|
|
var functionName = innerType.displayName || innerType.name || "";
|
|
return (
|
|
outerType.displayName ||
|
|
(functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName)
|
|
);
|
|
} // Keep in sync with shared/getComponentNameFromType
|
|
|
|
function getContextName$1(type) {
|
|
return type.displayName || "Context";
|
|
}
|
|
|
|
function getComponentNameFromFiber(fiber) {
|
|
var tag = fiber.tag,
|
|
type = fiber.type;
|
|
|
|
switch (tag) {
|
|
case CacheComponent:
|
|
return "Cache";
|
|
|
|
case ContextConsumer:
|
|
var context = type;
|
|
return getContextName$1(context) + ".Consumer";
|
|
|
|
case ContextProvider:
|
|
var provider = type;
|
|
return getContextName$1(provider._context) + ".Provider";
|
|
|
|
case DehydratedFragment:
|
|
return "DehydratedFragment";
|
|
|
|
case ForwardRef:
|
|
return getWrappedName$1(type, type.render, "ForwardRef");
|
|
|
|
case Fragment:
|
|
return "Fragment";
|
|
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
// Host component type is the display name (e.g. "div", "View")
|
|
return type;
|
|
|
|
case HostPortal:
|
|
return "Portal";
|
|
|
|
case HostRoot:
|
|
return "Root";
|
|
|
|
case HostText:
|
|
return "Text";
|
|
|
|
case LazyComponent:
|
|
// Name comes from the type in this case; we don't have a tag.
|
|
return getComponentNameFromType(type);
|
|
|
|
case Mode:
|
|
if (type === REACT_STRICT_MODE_TYPE) {
|
|
// Don't be less specific than shared/getComponentNameFromType
|
|
return "StrictMode";
|
|
}
|
|
|
|
return "Mode";
|
|
|
|
case OffscreenComponent:
|
|
return "Offscreen";
|
|
|
|
case Profiler:
|
|
return "Profiler";
|
|
|
|
case ScopeComponent:
|
|
return "Scope";
|
|
|
|
case SuspenseComponent:
|
|
return "Suspense";
|
|
|
|
case SuspenseListComponent:
|
|
return "SuspenseList";
|
|
|
|
case TracingMarkerComponent:
|
|
return "TracingMarker";
|
|
// The display name for this tags come from the user-provided type:
|
|
|
|
case ClassComponent:
|
|
case FunctionComponent:
|
|
case IncompleteClassComponent:
|
|
case IndeterminateComponent:
|
|
case MemoComponent:
|
|
case SimpleMemoComponent:
|
|
if (typeof type === "function") {
|
|
return type.displayName || type.name || null;
|
|
}
|
|
|
|
if (typeof type === "string") {
|
|
return type;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
|
var current = null;
|
|
var isRendering = false;
|
|
function getCurrentFiberOwnerNameInDevOrNull() {
|
|
{
|
|
if (current === null) {
|
|
return null;
|
|
}
|
|
|
|
var owner = current._debugOwner;
|
|
|
|
if (owner !== null && typeof owner !== "undefined") {
|
|
return getComponentNameFromFiber(owner);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getCurrentFiberStackInDev() {
|
|
{
|
|
if (current === null) {
|
|
return "";
|
|
} // Safe because if current fiber exists, we are reconciling,
|
|
// and it is guaranteed to be the work-in-progress version.
|
|
|
|
return getStackByFiberInDevAndProd(current);
|
|
}
|
|
}
|
|
|
|
function resetCurrentFiber() {
|
|
{
|
|
ReactDebugCurrentFrame.getCurrentStack = null;
|
|
current = null;
|
|
isRendering = false;
|
|
}
|
|
}
|
|
function setCurrentFiber(fiber) {
|
|
{
|
|
ReactDebugCurrentFrame.getCurrentStack =
|
|
fiber === null ? null : getCurrentFiberStackInDev;
|
|
current = fiber;
|
|
isRendering = false;
|
|
}
|
|
}
|
|
function getCurrentFiber() {
|
|
{
|
|
return current;
|
|
}
|
|
}
|
|
function setIsRendering(rendering) {
|
|
{
|
|
isRendering = rendering;
|
|
}
|
|
}
|
|
|
|
// around this limitation, we use an opaque type that can only be obtained by
|
|
// passing the value through getToStringValue first.
|
|
|
|
function toString(value) {
|
|
// The coercion safety check is performed in getToStringValue().
|
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
|
return "" + value;
|
|
}
|
|
function getToStringValue(value) {
|
|
switch (typeof value) {
|
|
case "boolean":
|
|
case "number":
|
|
case "string":
|
|
case "undefined":
|
|
return value;
|
|
|
|
case "object":
|
|
{
|
|
checkFormFieldValueStringCoercion(value);
|
|
}
|
|
|
|
return value;
|
|
|
|
default:
|
|
// function, symbol are assigned as empty strings
|
|
return "";
|
|
}
|
|
}
|
|
|
|
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 isCheckable(elem) {
|
|
var type = elem.type;
|
|
var nodeName = elem.nodeName;
|
|
return (
|
|
nodeName &&
|
|
nodeName.toLowerCase() === "input" &&
|
|
(type === "checkbox" || type === "radio")
|
|
);
|
|
}
|
|
|
|
function getTracker(node) {
|
|
return node._valueTracker;
|
|
}
|
|
|
|
function detachTracker(node) {
|
|
node._valueTracker = null;
|
|
}
|
|
|
|
function getValueFromNode(node) {
|
|
var value = "";
|
|
|
|
if (!node) {
|
|
return value;
|
|
}
|
|
|
|
if (isCheckable(node)) {
|
|
value = node.checked ? "true" : "false";
|
|
} else {
|
|
value = node.value;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function trackValueOnNode(node) {
|
|
var valueField = isCheckable(node) ? "checked" : "value";
|
|
var descriptor = Object.getOwnPropertyDescriptor(
|
|
node.constructor.prototype,
|
|
valueField
|
|
);
|
|
|
|
{
|
|
checkFormFieldValueStringCoercion(node[valueField]);
|
|
}
|
|
|
|
var currentValue = "" + node[valueField]; // if someone has already defined a value or Safari, then bail
|
|
// and don't track value will cause over reporting of changes,
|
|
// but it's better then a hard failure
|
|
// (needed for certain tests that spyOn input values and Safari)
|
|
|
|
if (
|
|
node.hasOwnProperty(valueField) ||
|
|
typeof descriptor === "undefined" ||
|
|
typeof descriptor.get !== "function" ||
|
|
typeof descriptor.set !== "function"
|
|
) {
|
|
return;
|
|
}
|
|
|
|
var get = descriptor.get,
|
|
set = descriptor.set;
|
|
Object.defineProperty(node, valueField, {
|
|
configurable: true,
|
|
// $FlowFixMe[missing-this-annot]
|
|
get: function() {
|
|
return get.call(this);
|
|
},
|
|
// $FlowFixMe[missing-local-annot]
|
|
// $FlowFixMe[missing-this-annot]
|
|
set: function(value) {
|
|
{
|
|
checkFormFieldValueStringCoercion(value);
|
|
}
|
|
|
|
currentValue = "" + value;
|
|
set.call(this, value);
|
|
}
|
|
}); // We could've passed this the first time
|
|
// but it triggers a bug in IE11 and Edge 14/15.
|
|
// Calling defineProperty() again should be equivalent.
|
|
// https://github.com/facebook/react/issues/11768
|
|
|
|
Object.defineProperty(node, valueField, {
|
|
enumerable: descriptor.enumerable
|
|
});
|
|
var tracker = {
|
|
getValue: function() {
|
|
return currentValue;
|
|
},
|
|
setValue: function(value) {
|
|
{
|
|
checkFormFieldValueStringCoercion(value);
|
|
}
|
|
|
|
currentValue = "" + value;
|
|
},
|
|
stopTracking: function() {
|
|
detachTracker(node);
|
|
delete node[valueField];
|
|
}
|
|
};
|
|
return tracker;
|
|
}
|
|
|
|
function track(node) {
|
|
if (getTracker(node)) {
|
|
return;
|
|
} // TODO: Once it's just Fiber we can move this to node._wrapperState
|
|
|
|
node._valueTracker = trackValueOnNode(node);
|
|
}
|
|
function updateValueIfChanged(node) {
|
|
if (!node) {
|
|
return false;
|
|
}
|
|
|
|
var tracker = getTracker(node); // if there is no tracker at this point it's unlikely
|
|
// that trying again will succeed
|
|
|
|
if (!tracker) {
|
|
return true;
|
|
}
|
|
|
|
var lastValue = tracker.getValue();
|
|
var nextValue = getValueFromNode(node);
|
|
|
|
if (nextValue !== lastValue) {
|
|
tracker.setValue(nextValue);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getActiveElement(doc) {
|
|
doc = doc || (typeof document !== "undefined" ? document : undefined);
|
|
|
|
if (typeof doc === "undefined") {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return doc.activeElement || doc.body;
|
|
} catch (e) {
|
|
return doc.body;
|
|
}
|
|
}
|
|
|
|
var didWarnValueDefaultValue = false;
|
|
var didWarnCheckedDefaultChecked = false;
|
|
var didWarnControlledToUncontrolled = false;
|
|
var didWarnUncontrolledToControlled = false;
|
|
|
|
function isControlled(props) {
|
|
var usesChecked = props.type === "checkbox" || props.type === "radio";
|
|
return usesChecked ? props.checked != null : props.value != null;
|
|
}
|
|
/**
|
|
* Implements an <input> host component that allows setting these optional
|
|
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
|
|
*
|
|
* If `checked` or `value` are not supplied (or null/undefined), user actions
|
|
* that affect the checked state or value will trigger updates to the element.
|
|
*
|
|
* If they are supplied (and not null/undefined), the rendered element will not
|
|
* trigger updates to the element. Instead, the props must change in order for
|
|
* the rendered element to be updated.
|
|
*
|
|
* The rendered element will be initialized as unchecked (or `defaultChecked`)
|
|
* with an empty value (or `defaultValue`).
|
|
*
|
|
* See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
|
|
*/
|
|
|
|
function getHostProps(element, props) {
|
|
var node = element;
|
|
var checked = props.checked;
|
|
var hostProps = assign({}, props, {
|
|
defaultChecked: undefined,
|
|
defaultValue: undefined,
|
|
value: undefined,
|
|
checked: checked != null ? checked : node._wrapperState.initialChecked
|
|
});
|
|
return hostProps;
|
|
}
|
|
function initWrapperState(element, props) {
|
|
{
|
|
checkControlledValueProps("input", props);
|
|
|
|
if (
|
|
props.checked !== undefined &&
|
|
props.defaultChecked !== undefined &&
|
|
!didWarnCheckedDefaultChecked
|
|
) {
|
|
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",
|
|
getCurrentFiberOwnerNameInDevOrNull() || "A component",
|
|
props.type
|
|
);
|
|
|
|
didWarnCheckedDefaultChecked = true;
|
|
}
|
|
|
|
if (
|
|
props.value !== undefined &&
|
|
props.defaultValue !== undefined &&
|
|
!didWarnValueDefaultValue
|
|
) {
|
|
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",
|
|
getCurrentFiberOwnerNameInDevOrNull() || "A component",
|
|
props.type
|
|
);
|
|
|
|
didWarnValueDefaultValue = true;
|
|
}
|
|
}
|
|
|
|
var node = element;
|
|
var defaultValue = props.defaultValue == null ? "" : props.defaultValue;
|
|
node._wrapperState = {
|
|
initialChecked:
|
|
props.checked != null ? props.checked : props.defaultChecked,
|
|
initialValue: getToStringValue(
|
|
props.value != null ? props.value : defaultValue
|
|
),
|
|
controlled: isControlled(props)
|
|
};
|
|
}
|
|
function updateChecked(element, props) {
|
|
var node = element;
|
|
var checked = props.checked;
|
|
|
|
if (checked != null) {
|
|
setValueForProperty(node, "checked", checked, false);
|
|
}
|
|
}
|
|
function updateWrapper(element, props) {
|
|
var node = element;
|
|
|
|
{
|
|
var controlled = isControlled(props);
|
|
|
|
if (
|
|
!node._wrapperState.controlled &&
|
|
controlled &&
|
|
!didWarnUncontrolledToControlled
|
|
) {
|
|
error(
|
|
"A component is changing an uncontrolled input to be controlled. " +
|
|
"This is likely caused by the value changing from undefined to " +
|
|
"a defined value, which should not happen. " +
|
|
"Decide between using a controlled or uncontrolled input " +
|
|
"element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"
|
|
);
|
|
|
|
didWarnUncontrolledToControlled = true;
|
|
}
|
|
|
|
if (
|
|
node._wrapperState.controlled &&
|
|
!controlled &&
|
|
!didWarnControlledToUncontrolled
|
|
) {
|
|
error(
|
|
"A component is changing a controlled input to be uncontrolled. " +
|
|
"This is likely caused by the value changing from a defined to " +
|
|
"undefined, which should not happen. " +
|
|
"Decide between using a controlled or uncontrolled input " +
|
|
"element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"
|
|
);
|
|
|
|
didWarnControlledToUncontrolled = true;
|
|
}
|
|
}
|
|
|
|
updateChecked(element, props);
|
|
var value = getToStringValue(props.value);
|
|
var type = props.type;
|
|
|
|
if (value != null) {
|
|
if (type === "number") {
|
|
if (
|
|
// $FlowFixMe[incompatible-type]
|
|
(value === 0 && node.value === "") || // We explicitly want to coerce to number here if possible.
|
|
// eslint-disable-next-line
|
|
node.value != value
|
|
) {
|
|
node.value = toString(value);
|
|
}
|
|
} else if (node.value !== toString(value)) {
|
|
node.value = toString(value);
|
|
}
|
|
} else if (type === "submit" || type === "reset") {
|
|
// Submit/reset inputs need the attribute removed completely to avoid
|
|
// blank-text buttons.
|
|
node.removeAttribute("value");
|
|
return;
|
|
}
|
|
|
|
{
|
|
// When syncing the value attribute, the value comes from a cascade of
|
|
// properties:
|
|
// 1. The value React property
|
|
// 2. The defaultValue React property
|
|
// 3. Otherwise there should be no change
|
|
if (props.hasOwnProperty("value")) {
|
|
setDefaultValue(node, props.type, value);
|
|
} else if (props.hasOwnProperty("defaultValue")) {
|
|
setDefaultValue(node, props.type, getToStringValue(props.defaultValue));
|
|
}
|
|
}
|
|
|
|
{
|
|
// When syncing the checked attribute, it only changes when it needs
|
|
// to be removed, such as transitioning from a checkbox into a text input
|
|
if (props.checked == null && props.defaultChecked != null) {
|
|
node.defaultChecked = !!props.defaultChecked;
|
|
}
|
|
}
|
|
}
|
|
function postMountWrapper(element, props, isHydrating) {
|
|
var node = element; // Do not assign value if it is already set. This prevents user text input
|
|
// from being lost during SSR hydration.
|
|
|
|
if (props.hasOwnProperty("value") || props.hasOwnProperty("defaultValue")) {
|
|
var type = props.type;
|
|
var isButton = type === "submit" || type === "reset"; // Avoid setting value attribute on submit/reset inputs as it overrides the
|
|
// default value provided by the browser. See: #12872
|
|
|
|
if (isButton && (props.value === undefined || props.value === null)) {
|
|
return;
|
|
}
|
|
|
|
var initialValue = toString(node._wrapperState.initialValue); // Do not assign value if it is already set. This prevents user text input
|
|
// from being lost during SSR hydration.
|
|
|
|
if (!isHydrating) {
|
|
{
|
|
// When syncing the value attribute, the value property should use
|
|
// the wrapperState._initialValue property. This uses:
|
|
//
|
|
// 1. The value React property when present
|
|
// 2. The defaultValue React property when present
|
|
// 3. An empty string
|
|
if (initialValue !== node.value) {
|
|
node.value = initialValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// Otherwise, the value attribute is synchronized to the property,
|
|
// so we assign defaultValue to the same thing as the value property
|
|
// assignment step above.
|
|
node.defaultValue = initialValue;
|
|
}
|
|
} // Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug
|
|
// this is needed to work around a chrome bug where setting defaultChecked
|
|
// will sometimes influence the value of checked (even after detachment).
|
|
// Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416
|
|
// We need to temporarily unset name to avoid disrupting radio button groups.
|
|
|
|
var name = node.name;
|
|
|
|
if (name !== "") {
|
|
node.name = "";
|
|
}
|
|
|
|
{
|
|
// When syncing the checked attribute, both the checked property and
|
|
// attribute are assigned at the same time using defaultChecked. This uses:
|
|
//
|
|
// 1. The checked React property when present
|
|
// 2. The defaultChecked React property when present
|
|
// 3. Otherwise, false
|
|
node.defaultChecked = !node.defaultChecked;
|
|
node.defaultChecked = !!node._wrapperState.initialChecked;
|
|
}
|
|
|
|
if (name !== "") {
|
|
node.name = name;
|
|
}
|
|
}
|
|
function restoreControlledState(element, props) {
|
|
var node = element;
|
|
updateWrapper(node, props);
|
|
updateNamedCousins(node, props);
|
|
}
|
|
|
|
function updateNamedCousins(rootNode, props) {
|
|
var name = props.name;
|
|
|
|
if (props.type === "radio" && name != null) {
|
|
var queryRoot = rootNode;
|
|
|
|
while (queryRoot.parentNode) {
|
|
queryRoot = queryRoot.parentNode;
|
|
} // If `rootNode.form` was non-null, then we could try `form.elements`,
|
|
// but that sometimes behaves strangely in IE8. We could also try using
|
|
// `form.getElementsByName`, but that will only return direct children
|
|
// and won't include inputs that use the HTML5 `form=` attribute. Since
|
|
// the input might not even be in a form. It might not even be in the
|
|
// document. Let's just use the local `querySelectorAll` to ensure we don't
|
|
// miss anything.
|
|
|
|
{
|
|
checkAttributeStringCoercion(name, "name");
|
|
}
|
|
|
|
var group = queryRoot.querySelectorAll(
|
|
"input[name=" + JSON.stringify("" + name) + '][type="radio"]'
|
|
);
|
|
|
|
for (var i = 0; i < group.length; i++) {
|
|
var otherNode = group[i];
|
|
|
|
if (otherNode === rootNode || otherNode.form !== rootNode.form) {
|
|
continue;
|
|
} // This will throw if radio buttons rendered by different copies of React
|
|
// and the same name are rendered into the same form (same as #1939).
|
|
// That's probably okay; we don't support it just as we don't support
|
|
// mixing React radio buttons with non-React ones.
|
|
|
|
var otherProps = getFiberCurrentPropsFromNode(otherNode);
|
|
|
|
if (!otherProps) {
|
|
throw new Error(
|
|
"ReactDOMInput: Mixing React and non-React radio inputs with the " +
|
|
"same `name` is not supported."
|
|
);
|
|
} // We need update the tracked value on the named cousin since the value
|
|
// was changed but the input saw no event or value set
|
|
|
|
updateValueIfChanged(otherNode); // If this is a controlled radio button group, forcing the input that
|
|
// was previously checked to update will cause it to be come re-checked
|
|
// as appropriate.
|
|
|
|
updateWrapper(otherNode, otherProps);
|
|
}
|
|
}
|
|
} // In Chrome, assigning defaultValue to certain input types triggers input validation.
|
|
// For number inputs, the display value loses trailing decimal points. For email inputs,
|
|
// Chrome raises "The specified value <x> is not a valid email address".
|
|
//
|
|
// Here we check to see if the defaultValue has actually changed, avoiding these problems
|
|
// when the user is inputting text
|
|
//
|
|
// https://github.com/facebook/react/issues/7253
|
|
|
|
function setDefaultValue(node, type, value) {
|
|
if (
|
|
// Focused number inputs synchronize on blur. See ChangeEventPlugin.js
|
|
type !== "number" ||
|
|
getActiveElement(node.ownerDocument) !== node
|
|
) {
|
|
if (value == null) {
|
|
node.defaultValue = toString(node._wrapperState.initialValue);
|
|
} else if (node.defaultValue !== toString(value)) {
|
|
node.defaultValue = toString(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
var didWarnSelectedSetOnOption = false;
|
|
var didWarnInvalidChild = false;
|
|
var didWarnInvalidInnerHTML = false;
|
|
/**
|
|
* Implements an <option> host component that warns when `selected` is set.
|
|
*/
|
|
|
|
function validateProps(element, props) {
|
|
{
|
|
// If a value is not provided, then the children must be simple.
|
|
if (props.value == null) {
|
|
if (typeof props.children === "object" && props.children !== null) {
|
|
React.Children.forEach(props.children, function(child) {
|
|
if (child == null) {
|
|
return;
|
|
}
|
|
|
|
if (typeof child === "string" || typeof child === "number") {
|
|
return;
|
|
}
|
|
|
|
if (!didWarnInvalidChild) {
|
|
didWarnInvalidChild = true;
|
|
|
|
error(
|
|
"Cannot infer the option value of complex children. " +
|
|
"Pass a `value` prop or use a plain string as children to <option>."
|
|
);
|
|
}
|
|
});
|
|
} else if (props.dangerouslySetInnerHTML != null) {
|
|
if (!didWarnInvalidInnerHTML) {
|
|
didWarnInvalidInnerHTML = true;
|
|
|
|
error(
|
|
"Pass a `value` prop if you set dangerouslyInnerHTML so React knows " +
|
|
"which value should be selected."
|
|
);
|
|
}
|
|
}
|
|
} // TODO: Remove support for `selected` in <option>.
|
|
|
|
if (props.selected != null && !didWarnSelectedSetOnOption) {
|
|
error(
|
|
"Use the `defaultValue` or `value` props on <select> instead of " +
|
|
"setting `selected` on <option>."
|
|
);
|
|
|
|
didWarnSelectedSetOnOption = true;
|
|
}
|
|
}
|
|
}
|
|
function postMountWrapper$1(element, props) {
|
|
// value="" should make a value attribute (#6219)
|
|
if (props.value != null) {
|
|
element.setAttribute("value", toString(getToStringValue(props.value)));
|
|
}
|
|
}
|
|
|
|
var isArrayImpl = Array.isArray; // eslint-disable-next-line no-redeclare
|
|
|
|
function isArray(a) {
|
|
return isArrayImpl(a);
|
|
}
|
|
|
|
var didWarnValueDefaultValue$1;
|
|
|
|
{
|
|
didWarnValueDefaultValue$1 = false;
|
|
}
|
|
|
|
function getDeclarationErrorAddendum() {
|
|
var ownerName = getCurrentFiberOwnerNameInDevOrNull();
|
|
|
|
if (ownerName) {
|
|
return "\n\nCheck the render method of `" + ownerName + "`.";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
var valuePropNames = ["value", "defaultValue"];
|
|
/**
|
|
* Validation function for `value` and `defaultValue`.
|
|
*/
|
|
|
|
function checkSelectPropTypes(props) {
|
|
{
|
|
checkControlledValueProps("select", props);
|
|
|
|
for (var i = 0; i < valuePropNames.length; i++) {
|
|
var propName = valuePropNames[i];
|
|
|
|
if (props[propName] == null) {
|
|
continue;
|
|
}
|
|
|
|
var propNameIsArray = isArray(props[propName]);
|
|
|
|
if (props.multiple && !propNameIsArray) {
|
|
error(
|
|
"The `%s` prop supplied to <select> must be an array if " +
|
|
"`multiple` is true.%s",
|
|
propName,
|
|
getDeclarationErrorAddendum()
|
|
);
|
|
} else if (!props.multiple && propNameIsArray) {
|
|
error(
|
|
"The `%s` prop supplied to <select> must be a scalar " +
|
|
"value if `multiple` is false.%s",
|
|
propName,
|
|
getDeclarationErrorAddendum()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateOptions(node, multiple, propValue, setDefaultSelected) {
|
|
var options = node.options;
|
|
|
|
if (multiple) {
|
|
var selectedValues = propValue;
|
|
var selectedValue = {};
|
|
|
|
for (var i = 0; i < selectedValues.length; i++) {
|
|
// Prefix to avoid chaos with special keys.
|
|
selectedValue["$" + selectedValues[i]] = true;
|
|
}
|
|
|
|
for (var _i = 0; _i < options.length; _i++) {
|
|
var selected = selectedValue.hasOwnProperty("$" + options[_i].value);
|
|
|
|
if (options[_i].selected !== selected) {
|
|
options[_i].selected = selected;
|
|
}
|
|
|
|
if (selected && setDefaultSelected) {
|
|
options[_i].defaultSelected = true;
|
|
}
|
|
}
|
|
} else {
|
|
// Do not set `select.value` as exact behavior isn't consistent across all
|
|
// browsers for all cases.
|
|
var _selectedValue = toString(getToStringValue(propValue));
|
|
|
|
var defaultSelected = null;
|
|
|
|
for (var _i2 = 0; _i2 < options.length; _i2++) {
|
|
if (options[_i2].value === _selectedValue) {
|
|
options[_i2].selected = true;
|
|
|
|
if (setDefaultSelected) {
|
|
options[_i2].defaultSelected = true;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (defaultSelected === null && !options[_i2].disabled) {
|
|
defaultSelected = options[_i2];
|
|
}
|
|
}
|
|
|
|
if (defaultSelected !== null) {
|
|
defaultSelected.selected = true;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Implements a <select> host component that allows optionally setting the
|
|
* props `value` and `defaultValue`. If `multiple` is false, the prop must be a
|
|
* stringable. If `multiple` is true, the prop must be an array of stringables.
|
|
*
|
|
* If `value` is not supplied (or null/undefined), user actions that change the
|
|
* selected option will trigger updates to the rendered options.
|
|
*
|
|
* If it is supplied (and not null/undefined), the rendered options will not
|
|
* update in response to user actions. Instead, the `value` prop must change in
|
|
* order for the rendered options to update.
|
|
*
|
|
* If `defaultValue` is provided, any options with the supplied values will be
|
|
* selected.
|
|
*/
|
|
|
|
function getHostProps$1(element, props) {
|
|
return assign({}, props, {
|
|
value: undefined
|
|
});
|
|
}
|
|
function initWrapperState$1(element, props) {
|
|
var node = element;
|
|
|
|
{
|
|
checkSelectPropTypes(props);
|
|
}
|
|
|
|
node._wrapperState = {
|
|
wasMultiple: !!props.multiple
|
|
};
|
|
|
|
{
|
|
if (
|
|
props.value !== undefined &&
|
|
props.defaultValue !== undefined &&
|
|
!didWarnValueDefaultValue$1
|
|
) {
|
|
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"
|
|
);
|
|
|
|
didWarnValueDefaultValue$1 = true;
|
|
}
|
|
}
|
|
}
|
|
function postMountWrapper$2(element, props) {
|
|
var node = element;
|
|
node.multiple = !!props.multiple;
|
|
var value = props.value;
|
|
|
|
if (value != null) {
|
|
updateOptions(node, !!props.multiple, value, false);
|
|
} else if (props.defaultValue != null) {
|
|
updateOptions(node, !!props.multiple, props.defaultValue, true);
|
|
}
|
|
}
|
|
function postUpdateWrapper(element, props) {
|
|
var node = element;
|
|
var wasMultiple = node._wrapperState.wasMultiple;
|
|
node._wrapperState.wasMultiple = !!props.multiple;
|
|
var value = props.value;
|
|
|
|
if (value != null) {
|
|
updateOptions(node, !!props.multiple, value, false);
|
|
} else if (wasMultiple !== !!props.multiple) {
|
|
// For simplicity, reapply `defaultValue` if `multiple` is toggled.
|
|
if (props.defaultValue != null) {
|
|
updateOptions(node, !!props.multiple, props.defaultValue, true);
|
|
} else {
|
|
// Revert the select back to its default unselected state.
|
|
updateOptions(node, !!props.multiple, props.multiple ? [] : "", false);
|
|
}
|
|
}
|
|
}
|
|
function restoreControlledState$1(element, props) {
|
|
var node = element;
|
|
var value = props.value;
|
|
|
|
if (value != null) {
|
|
updateOptions(node, !!props.multiple, value, false);
|
|
}
|
|
}
|
|
|
|
var didWarnValDefaultVal = false;
|
|
/**
|
|
* Implements a <textarea> host component that allows setting `value`, and
|
|
* `defaultValue`. This differs from the traditional DOM API because value is
|
|
* usually set as PCDATA children.
|
|
*
|
|
* If `value` is not supplied (or null/undefined), user actions that affect the
|
|
* value will trigger updates to the element.
|
|
*
|
|
* If `value` is supplied (and not null/undefined), the rendered element will
|
|
* not trigger updates to the element. Instead, the `value` prop must change in
|
|
* order for the rendered element to be updated.
|
|
*
|
|
* The rendered element will be initialized with an empty value, the prop
|
|
* `defaultValue` if specified, or the children content (deprecated).
|
|
*/
|
|
|
|
function getHostProps$2(element, props) {
|
|
var node = element;
|
|
|
|
if (props.dangerouslySetInnerHTML != null) {
|
|
throw new Error(
|
|
"`dangerouslySetInnerHTML` does not make sense on <textarea>."
|
|
);
|
|
} // Always set children to the same thing. In IE9, the selection range will
|
|
// get reset if `textContent` is mutated. We could add a check in setTextContent
|
|
// to only set the value if/when the value differs from the node value (which would
|
|
// completely solve this IE9 bug), but Sebastian+Sophie seemed to like this
|
|
// solution. The value can be a boolean or object so that's why it's forced
|
|
// to be a string.
|
|
|
|
var hostProps = assign({}, props, {
|
|
value: undefined,
|
|
defaultValue: undefined,
|
|
children: toString(node._wrapperState.initialValue)
|
|
});
|
|
|
|
return hostProps;
|
|
}
|
|
function initWrapperState$2(element, props) {
|
|
var node = element;
|
|
|
|
{
|
|
checkControlledValueProps("textarea", props);
|
|
|
|
if (
|
|
props.value !== undefined &&
|
|
props.defaultValue !== undefined &&
|
|
!didWarnValDefaultVal
|
|
) {
|
|
error(
|
|
"%s contains a textarea with both value and defaultValue props. " +
|
|
"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",
|
|
getCurrentFiberOwnerNameInDevOrNull() || "A component"
|
|
);
|
|
|
|
didWarnValDefaultVal = true;
|
|
}
|
|
}
|
|
|
|
var initialValue = props.value; // Only bother fetching default value if we're going to use it
|
|
|
|
if (initialValue == null) {
|
|
var children = props.children,
|
|
defaultValue = props.defaultValue;
|
|
|
|
if (children != null) {
|
|
{
|
|
error(
|
|
"Use the `defaultValue` or `value` props instead of setting " +
|
|
"children on <textarea>."
|
|
);
|
|
}
|
|
}
|
|
|
|
if (defaultValue == null) {
|
|
defaultValue = "";
|
|
}
|
|
|
|
initialValue = defaultValue;
|
|
}
|
|
|
|
node._wrapperState = {
|
|
initialValue: getToStringValue(initialValue)
|
|
};
|
|
}
|
|
function updateWrapper$1(element, props) {
|
|
var node = element;
|
|
var value = getToStringValue(props.value);
|
|
var defaultValue = getToStringValue(props.defaultValue);
|
|
|
|
if (value != null) {
|
|
// Cast `value` to a string to ensure the value is set correctly. While
|
|
// browsers typically do this as necessary, jsdom doesn't.
|
|
var newValue = toString(value); // To avoid side effects (such as losing text selection), only set value if changed
|
|
|
|
if (newValue !== node.value) {
|
|
node.value = newValue;
|
|
}
|
|
|
|
if (props.defaultValue == null && node.defaultValue !== newValue) {
|
|
node.defaultValue = newValue;
|
|
}
|
|
}
|
|
|
|
if (defaultValue != null) {
|
|
node.defaultValue = toString(defaultValue);
|
|
}
|
|
}
|
|
function postMountWrapper$3(element, props) {
|
|
var node = element; // This is in postMount because we need access to the DOM node, which is not
|
|
// available until after the component has mounted.
|
|
|
|
var textContent = node.textContent; // Only set node.value if textContent is equal to the expected
|
|
// initial value. In IE10/IE11 there is a bug where the placeholder attribute
|
|
// will populate textContent as well.
|
|
// https://developer.microsoft.com/microsoft-edge/platform/issues/101525/
|
|
|
|
if (textContent === node._wrapperState.initialValue) {
|
|
if (textContent !== "" && textContent !== null) {
|
|
node.value = textContent;
|
|
}
|
|
}
|
|
}
|
|
function restoreControlledState$2(element, props) {
|
|
// DOM component is still mounted; update
|
|
updateWrapper$1(element, props);
|
|
}
|
|
|
|
var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
var MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
|
|
var SVG_NAMESPACE = "http://www.w3.org/2000/svg"; // Assumes there is no parent namespace.
|
|
|
|
function getIntrinsicNamespace(type) {
|
|
switch (type) {
|
|
case "svg":
|
|
return SVG_NAMESPACE;
|
|
|
|
case "math":
|
|
return MATH_NAMESPACE;
|
|
|
|
default:
|
|
return HTML_NAMESPACE;
|
|
}
|
|
}
|
|
function getChildNamespace(parentNamespace, type) {
|
|
if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
|
|
// No (or default) parent namespace: potential entry point.
|
|
return getIntrinsicNamespace(type);
|
|
}
|
|
|
|
if (parentNamespace === SVG_NAMESPACE && type === "foreignObject") {
|
|
// We're leaving SVG.
|
|
return HTML_NAMESPACE;
|
|
} // By default, pass namespace below.
|
|
|
|
return parentNamespace;
|
|
}
|
|
|
|
/* globals MSApp */
|
|
|
|
/**
|
|
* Create a function which has 'unsafe' privileges (required by windows8 apps)
|
|
*/
|
|
var createMicrosoftUnsafeLocalFunction = function(func) {
|
|
if (typeof MSApp !== "undefined" && MSApp.execUnsafeLocalFunction) {
|
|
return function(arg0, arg1, arg2, arg3) {
|
|
MSApp.execUnsafeLocalFunction(function() {
|
|
return func(arg0, arg1, arg2, arg3);
|
|
});
|
|
};
|
|
} else {
|
|
return func;
|
|
}
|
|
};
|
|
|
|
var reusableSVGContainer;
|
|
/**
|
|
* Set the innerHTML property of a node
|
|
*
|
|
* @param {DOMElement} node
|
|
* @param {string} html
|
|
* @internal
|
|
*/
|
|
|
|
var setInnerHTML = createMicrosoftUnsafeLocalFunction(function(node, html) {
|
|
if (node.namespaceURI === SVG_NAMESPACE) {
|
|
if (!("innerHTML" in node)) {
|
|
// IE does not have innerHTML for SVG nodes, so instead we inject the
|
|
// new markup in a temp node and then move the child nodes across into
|
|
// the target node
|
|
reusableSVGContainer =
|
|
reusableSVGContainer || document.createElement("div");
|
|
reusableSVGContainer.innerHTML =
|
|
"<svg>" + html.valueOf().toString() + "</svg>";
|
|
var svgNode = reusableSVGContainer.firstChild;
|
|
|
|
while (node.firstChild) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
|
|
while (svgNode.firstChild) {
|
|
node.appendChild(svgNode.firstChild);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
node.innerHTML = html;
|
|
});
|
|
|
|
/**
|
|
* HTML nodeType values that represent the type of the node
|
|
*/
|
|
var ELEMENT_NODE = 1;
|
|
var TEXT_NODE = 3;
|
|
var COMMENT_NODE = 8;
|
|
var DOCUMENT_NODE = 9;
|
|
var DOCUMENT_TYPE_NODE = 10;
|
|
var DOCUMENT_FRAGMENT_NODE = 11;
|
|
|
|
/**
|
|
* Set the textContent property of a node. For text updates, it's faster
|
|
* to set the `nodeValue` of the Text node directly instead of using
|
|
* `.textContent` which will remove the existing node and create a new one.
|
|
*
|
|
* @param {DOMElement} node
|
|
* @param {string} text
|
|
* @internal
|
|
*/
|
|
|
|
var setTextContent = function(node, text) {
|
|
if (text) {
|
|
var firstChild = node.firstChild;
|
|
|
|
if (
|
|
firstChild &&
|
|
firstChild === node.lastChild &&
|
|
firstChild.nodeType === TEXT_NODE
|
|
) {
|
|
firstChild.nodeValue = text;
|
|
return;
|
|
}
|
|
}
|
|
|
|
node.textContent = text;
|
|
};
|
|
|
|
// List derived from Gecko source code:
|
|
// https://github.com/mozilla/gecko-dev/blob/4e638efc71/layout/style/test/property_database.js
|
|
var shorthandToLonghand = {
|
|
animation: [
|
|
"animationDelay",
|
|
"animationDirection",
|
|
"animationDuration",
|
|
"animationFillMode",
|
|
"animationIterationCount",
|
|
"animationName",
|
|
"animationPlayState",
|
|
"animationTimingFunction"
|
|
],
|
|
background: [
|
|
"backgroundAttachment",
|
|
"backgroundClip",
|
|
"backgroundColor",
|
|
"backgroundImage",
|
|
"backgroundOrigin",
|
|
"backgroundPositionX",
|
|
"backgroundPositionY",
|
|
"backgroundRepeat",
|
|
"backgroundSize"
|
|
],
|
|
backgroundPosition: ["backgroundPositionX", "backgroundPositionY"],
|
|
border: [
|
|
"borderBottomColor",
|
|
"borderBottomStyle",
|
|
"borderBottomWidth",
|
|
"borderImageOutset",
|
|
"borderImageRepeat",
|
|
"borderImageSlice",
|
|
"borderImageSource",
|
|
"borderImageWidth",
|
|
"borderLeftColor",
|
|
"borderLeftStyle",
|
|
"borderLeftWidth",
|
|
"borderRightColor",
|
|
"borderRightStyle",
|
|
"borderRightWidth",
|
|
"borderTopColor",
|
|
"borderTopStyle",
|
|
"borderTopWidth"
|
|
],
|
|
borderBlockEnd: [
|
|
"borderBlockEndColor",
|
|
"borderBlockEndStyle",
|
|
"borderBlockEndWidth"
|
|
],
|
|
borderBlockStart: [
|
|
"borderBlockStartColor",
|
|
"borderBlockStartStyle",
|
|
"borderBlockStartWidth"
|
|
],
|
|
borderBottom: ["borderBottomColor", "borderBottomStyle", "borderBottomWidth"],
|
|
borderColor: [
|
|
"borderBottomColor",
|
|
"borderLeftColor",
|
|
"borderRightColor",
|
|
"borderTopColor"
|
|
],
|
|
borderImage: [
|
|
"borderImageOutset",
|
|
"borderImageRepeat",
|
|
"borderImageSlice",
|
|
"borderImageSource",
|
|
"borderImageWidth"
|
|
],
|
|
borderInlineEnd: [
|
|
"borderInlineEndColor",
|
|
"borderInlineEndStyle",
|
|
"borderInlineEndWidth"
|
|
],
|
|
borderInlineStart: [
|
|
"borderInlineStartColor",
|
|
"borderInlineStartStyle",
|
|
"borderInlineStartWidth"
|
|
],
|
|
borderLeft: ["borderLeftColor", "borderLeftStyle", "borderLeftWidth"],
|
|
borderRadius: [
|
|
"borderBottomLeftRadius",
|
|
"borderBottomRightRadius",
|
|
"borderTopLeftRadius",
|
|
"borderTopRightRadius"
|
|
],
|
|
borderRight: ["borderRightColor", "borderRightStyle", "borderRightWidth"],
|
|
borderStyle: [
|
|
"borderBottomStyle",
|
|
"borderLeftStyle",
|
|
"borderRightStyle",
|
|
"borderTopStyle"
|
|
],
|
|
borderTop: ["borderTopColor", "borderTopStyle", "borderTopWidth"],
|
|
borderWidth: [
|
|
"borderBottomWidth",
|
|
"borderLeftWidth",
|
|
"borderRightWidth",
|
|
"borderTopWidth"
|
|
],
|
|
columnRule: ["columnRuleColor", "columnRuleStyle", "columnRuleWidth"],
|
|
columns: ["columnCount", "columnWidth"],
|
|
flex: ["flexBasis", "flexGrow", "flexShrink"],
|
|
flexFlow: ["flexDirection", "flexWrap"],
|
|
font: [
|
|
"fontFamily",
|
|
"fontFeatureSettings",
|
|
"fontKerning",
|
|
"fontLanguageOverride",
|
|
"fontSize",
|
|
"fontSizeAdjust",
|
|
"fontStretch",
|
|
"fontStyle",
|
|
"fontVariant",
|
|
"fontVariantAlternates",
|
|
"fontVariantCaps",
|
|
"fontVariantEastAsian",
|
|
"fontVariantLigatures",
|
|
"fontVariantNumeric",
|
|
"fontVariantPosition",
|
|
"fontWeight",
|
|
"lineHeight"
|
|
],
|
|
fontVariant: [
|
|
"fontVariantAlternates",
|
|
"fontVariantCaps",
|
|
"fontVariantEastAsian",
|
|
"fontVariantLigatures",
|
|
"fontVariantNumeric",
|
|
"fontVariantPosition"
|
|
],
|
|
gap: ["columnGap", "rowGap"],
|
|
grid: [
|
|
"gridAutoColumns",
|
|
"gridAutoFlow",
|
|
"gridAutoRows",
|
|
"gridTemplateAreas",
|
|
"gridTemplateColumns",
|
|
"gridTemplateRows"
|
|
],
|
|
gridArea: ["gridColumnEnd", "gridColumnStart", "gridRowEnd", "gridRowStart"],
|
|
gridColumn: ["gridColumnEnd", "gridColumnStart"],
|
|
gridColumnGap: ["columnGap"],
|
|
gridGap: ["columnGap", "rowGap"],
|
|
gridRow: ["gridRowEnd", "gridRowStart"],
|
|
gridRowGap: ["rowGap"],
|
|
gridTemplate: [
|
|
"gridTemplateAreas",
|
|
"gridTemplateColumns",
|
|
"gridTemplateRows"
|
|
],
|
|
listStyle: ["listStyleImage", "listStylePosition", "listStyleType"],
|
|
margin: ["marginBottom", "marginLeft", "marginRight", "marginTop"],
|
|
marker: ["markerEnd", "markerMid", "markerStart"],
|
|
mask: [
|
|
"maskClip",
|
|
"maskComposite",
|
|
"maskImage",
|
|
"maskMode",
|
|
"maskOrigin",
|
|
"maskPositionX",
|
|
"maskPositionY",
|
|
"maskRepeat",
|
|
"maskSize"
|
|
],
|
|
maskPosition: ["maskPositionX", "maskPositionY"],
|
|
outline: ["outlineColor", "outlineStyle", "outlineWidth"],
|
|
overflow: ["overflowX", "overflowY"],
|
|
padding: ["paddingBottom", "paddingLeft", "paddingRight", "paddingTop"],
|
|
placeContent: ["alignContent", "justifyContent"],
|
|
placeItems: ["alignItems", "justifyItems"],
|
|
placeSelf: ["alignSelf", "justifySelf"],
|
|
textDecoration: [
|
|
"textDecorationColor",
|
|
"textDecorationLine",
|
|
"textDecorationStyle"
|
|
],
|
|
textEmphasis: ["textEmphasisColor", "textEmphasisStyle"],
|
|
transition: [
|
|
"transitionDelay",
|
|
"transitionDuration",
|
|
"transitionProperty",
|
|
"transitionTimingFunction"
|
|
],
|
|
wordWrap: ["overflowWrap"]
|
|
};
|
|
|
|
/**
|
|
* 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];
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Convert a value into the proper css writable value. The style name `name`
|
|
* should be logical (no hyphens), as specified
|
|
* in `CSSProperty.isUnitlessNumber`.
|
|
*
|
|
* @param {string} name CSS property name such as `topMargin`.
|
|
* @param {*} value CSS property value such as `10px`.
|
|
* @return {string} Normalized style value with dimensions applied.
|
|
*/
|
|
|
|
function dangerousStyleValue(name, value, isCustomProperty) {
|
|
// Note that we've removed escapeTextForBrowser() calls here since the
|
|
// whole string will be escaped when the attribute is injected into
|
|
// the markup. 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 isEmpty = value == null || typeof value === "boolean" || value === "";
|
|
|
|
if (isEmpty) {
|
|
return "";
|
|
}
|
|
|
|
if (
|
|
!isCustomProperty &&
|
|
typeof value === "number" &&
|
|
value !== 0 &&
|
|
!(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name])
|
|
) {
|
|
return value + "px"; // Presumes implicit 'px' suffix for unitless numbers
|
|
}
|
|
|
|
{
|
|
checkCSSPropertyStringCoercion(value, name);
|
|
}
|
|
|
|
return ("" + value).trim();
|
|
}
|
|
|
|
var uppercasePattern = /([A-Z])/g;
|
|
var msPattern = /^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, "-ms-");
|
|
}
|
|
|
|
var warnValidStyle = function() {};
|
|
|
|
{
|
|
// 'msTransform' is correct, but the other prefixes should be capitalized
|
|
var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
|
|
var msPattern$1 = /^-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$1, "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;
|
|
|
|
/**
|
|
* Operations for dealing with CSS properties.
|
|
*/
|
|
|
|
/**
|
|
* This creates a string that is expected to be equivalent to the style
|
|
* attribute generated by server-side rendering. It by-passes warnings and
|
|
* security checks so it's not safe to use this value for anything other than
|
|
* comparison. It is only used in DEV for SSR validation.
|
|
*/
|
|
|
|
function createDangerousStringForStyles(styles) {
|
|
{
|
|
var serialized = "";
|
|
var delimiter = "";
|
|
|
|
for (var styleName in styles) {
|
|
if (!styles.hasOwnProperty(styleName)) {
|
|
continue;
|
|
}
|
|
|
|
var styleValue = styles[styleName];
|
|
|
|
if (styleValue != null) {
|
|
var isCustomProperty = styleName.indexOf("--") === 0;
|
|
serialized +=
|
|
delimiter +
|
|
(isCustomProperty ? styleName : hyphenateStyleName(styleName)) +
|
|
":";
|
|
serialized += dangerousStyleValue(
|
|
styleName,
|
|
styleValue,
|
|
isCustomProperty
|
|
);
|
|
delimiter = ";";
|
|
}
|
|
}
|
|
|
|
return serialized || null;
|
|
}
|
|
}
|
|
/**
|
|
* Sets the value for multiple styles on a node. If a value is specified as
|
|
* '' (empty string), the corresponding style property will be unset.
|
|
*
|
|
* @param {DOMElement} node
|
|
* @param {object} styles
|
|
*/
|
|
|
|
function setValueForStyles(node, styles) {
|
|
var style = node.style;
|
|
|
|
for (var styleName in styles) {
|
|
if (!styles.hasOwnProperty(styleName)) {
|
|
continue;
|
|
}
|
|
|
|
var isCustomProperty = styleName.indexOf("--") === 0;
|
|
|
|
{
|
|
if (!isCustomProperty) {
|
|
warnValidStyle$1(styleName, styles[styleName]);
|
|
}
|
|
}
|
|
|
|
var styleValue = dangerousStyleValue(
|
|
styleName,
|
|
styles[styleName],
|
|
isCustomProperty
|
|
);
|
|
|
|
if (styleName === "float") {
|
|
styleName = "cssFloat";
|
|
}
|
|
|
|
if (isCustomProperty) {
|
|
style.setProperty(styleName, styleValue);
|
|
} else {
|
|
style[styleName] = styleValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
function isValueEmpty(value) {
|
|
return value == null || typeof value === "boolean" || value === "";
|
|
}
|
|
/**
|
|
* Given {color: 'red', overflow: 'hidden'} returns {
|
|
* color: 'color',
|
|
* overflowX: 'overflow',
|
|
* overflowY: 'overflow',
|
|
* }. This can be read as "the overflowY property was set by the overflow
|
|
* shorthand". That is, the values are the property that each was derived from.
|
|
*/
|
|
|
|
function expandShorthandMap(styles) {
|
|
var expanded = {};
|
|
|
|
for (var key in styles) {
|
|
var longhands = shorthandToLonghand[key] || [key];
|
|
|
|
for (var i = 0; i < longhands.length; i++) {
|
|
expanded[longhands[i]] = key;
|
|
}
|
|
}
|
|
|
|
return expanded;
|
|
}
|
|
/**
|
|
* When mixing shorthand and longhand property names, we warn during updates if
|
|
* we expect an incorrect result to occur. In particular, we warn for:
|
|
*
|
|
* Updating a shorthand property (longhand gets overwritten):
|
|
* {font: 'foo', fontVariant: 'bar'} -> {font: 'baz', fontVariant: 'bar'}
|
|
* becomes .style.font = 'baz'
|
|
* Removing a shorthand property (longhand gets lost too):
|
|
* {font: 'foo', fontVariant: 'bar'} -> {fontVariant: 'bar'}
|
|
* becomes .style.font = ''
|
|
* Removing a longhand property (should revert to shorthand; doesn't):
|
|
* {font: 'foo', fontVariant: 'bar'} -> {font: 'foo'}
|
|
* becomes .style.fontVariant = ''
|
|
*/
|
|
|
|
function validateShorthandPropertyCollisionInDev(styleUpdates, nextStyles) {
|
|
{
|
|
if (!nextStyles) {
|
|
return;
|
|
}
|
|
|
|
var expandedUpdates = expandShorthandMap(styleUpdates);
|
|
var expandedStyles = expandShorthandMap(nextStyles);
|
|
var warnedAbout = {};
|
|
|
|
for (var key in expandedUpdates) {
|
|
var originalKey = expandedUpdates[key];
|
|
var correctOriginalKey = expandedStyles[key];
|
|
|
|
if (correctOriginalKey && originalKey !== correctOriginalKey) {
|
|
var warningKey = originalKey + "," + correctOriginalKey;
|
|
|
|
if (warnedAbout[warningKey]) {
|
|
continue;
|
|
}
|
|
|
|
warnedAbout[warningKey] = true;
|
|
|
|
error(
|
|
"%s a style property during rerender (%s) when a " +
|
|
"conflicting property is set (%s) can lead to styling bugs. To " +
|
|
"avoid this, don't mix shorthand and non-shorthand properties " +
|
|
"for the same value; instead, replace the shorthand with " +
|
|
"separate values.",
|
|
isValueEmpty(styleUpdates[originalKey]) ? "Removing" : "Updating",
|
|
originalKey,
|
|
correctOriginalKey
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For HTML, certain tags should omit their close tag. We keep a list for
|
|
// those special-case tags.
|
|
var omittedCloseTags = {
|
|
area: true,
|
|
base: true,
|
|
br: true,
|
|
col: true,
|
|
embed: true,
|
|
hr: true,
|
|
img: true,
|
|
input: true,
|
|
keygen: true,
|
|
link: true,
|
|
meta: true,
|
|
param: true,
|
|
source: true,
|
|
track: true,
|
|
wbr: true // NOTE: menuitem's close tag should be omitted, but that causes problems.
|
|
};
|
|
|
|
// `omittedCloseTags` except that `menuitem` should still have its closing tag.
|
|
|
|
var voidElementTags = assign(
|
|
{
|
|
menuitem: true
|
|
},
|
|
omittedCloseTags
|
|
);
|
|
|
|
var HTML = "__html";
|
|
|
|
function assertValidProps(tag, props) {
|
|
if (!props) {
|
|
return;
|
|
} // Note the use of `==` which checks for null or undefined.
|
|
|
|
if (voidElementTags[tag]) {
|
|
if (props.children != null || props.dangerouslySetInnerHTML != null) {
|
|
throw new Error(
|
|
tag +
|
|
" is a void element tag and must neither have `children` nor " +
|
|
"use `dangerouslySetInnerHTML`."
|
|
);
|
|
}
|
|
}
|
|
|
|
if (props.dangerouslySetInnerHTML != null) {
|
|
if (props.children != null) {
|
|
throw new Error(
|
|
"Can only set one of `children` or `props.dangerouslySetInnerHTML`."
|
|
);
|
|
}
|
|
|
|
if (
|
|
typeof props.dangerouslySetInnerHTML !== "object" ||
|
|
!(HTML in props.dangerouslySetInnerHTML)
|
|
) {
|
|
throw new Error(
|
|
"`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. " +
|
|
"Please visit https://reactjs.org/link/dangerously-set-inner-html " +
|
|
"for more information."
|
|
);
|
|
}
|
|
}
|
|
|
|
{
|
|
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 (props.style != null && typeof props.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."
|
|
);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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",
|
|
fetchpriority: "fetchPriority",
|
|
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 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
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 IS_EVENT_HANDLE_NON_MANAGED_NODE = 1;
|
|
var IS_NON_DELEGATED = 1 << 1;
|
|
var IS_CAPTURE_PHASE = 1 << 2;
|
|
// set to LEGACY_FB_SUPPORT. LEGACY_FB_SUPPORT only gets set when
|
|
// we call willDeferLaterForLegacyFBSupport, thus not bailing out
|
|
// will result in endless cycles like an infinite loop.
|
|
// We also don't want to defer during event replaying.
|
|
|
|
var SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS =
|
|
IS_EVENT_HANDLE_NON_MANAGED_NODE | IS_NON_DELEGATED | IS_CAPTURE_PHASE;
|
|
|
|
// This exists to avoid circular dependency between ReactDOMEventReplaying
|
|
// and DOMPluginEventSystem.
|
|
var currentReplayingEvent = null;
|
|
function setReplayingEvent(event) {
|
|
{
|
|
if (currentReplayingEvent !== null) {
|
|
error(
|
|
"Expected currently replaying event to be null. This error " +
|
|
"is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
|
|
currentReplayingEvent = event;
|
|
}
|
|
function resetReplayingEvent() {
|
|
{
|
|
if (currentReplayingEvent === null) {
|
|
error(
|
|
"Expected currently replaying event to not be null. This error " +
|
|
"is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
|
|
currentReplayingEvent = null;
|
|
}
|
|
function isReplayingEvent(event) {
|
|
return event === currentReplayingEvent;
|
|
}
|
|
|
|
/**
|
|
* Gets the target node from a native browser event by accounting for
|
|
* inconsistencies in browser DOM APIs.
|
|
*
|
|
* @param {object} nativeEvent Native browser event.
|
|
* @return {DOMEventTarget} Target node.
|
|
*/
|
|
|
|
function getEventTarget(nativeEvent) {
|
|
// Fallback to nativeEvent.srcElement for IE9
|
|
// https://github.com/facebook/react/issues/12506
|
|
var target = nativeEvent.target || nativeEvent.srcElement || window; // Normalize SVG <use> element events #4963
|
|
|
|
if (target.correspondingUseElement) {
|
|
target = target.correspondingUseElement;
|
|
} // Safari may fire events on text nodes (Node.TEXT_NODE is 3).
|
|
// @see http://www.quirksmode.org/js/events_properties.html
|
|
|
|
return target.nodeType === TEXT_NODE ? target.parentNode : target;
|
|
}
|
|
|
|
var restoreImpl = null;
|
|
var restoreTarget = null;
|
|
var restoreQueue = null;
|
|
|
|
function restoreStateOfTarget(target) {
|
|
// We perform this translation at the end of the event loop so that we
|
|
// always receive the correct fiber here
|
|
var internalInstance = getInstanceFromNode$1(target);
|
|
|
|
if (!internalInstance) {
|
|
// Unmounted
|
|
return;
|
|
}
|
|
|
|
if (typeof restoreImpl !== "function") {
|
|
throw new Error(
|
|
"setRestoreImplementation() needs to be called to handle a target for controlled " +
|
|
"events. This error is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
var stateNode = internalInstance.stateNode; // Guard against Fiber being unmounted.
|
|
|
|
if (stateNode) {
|
|
var props = getFiberCurrentPropsFromNode(stateNode);
|
|
restoreImpl(internalInstance.stateNode, internalInstance.type, props);
|
|
}
|
|
}
|
|
|
|
function setRestoreImplementation(impl) {
|
|
restoreImpl = impl;
|
|
}
|
|
function enqueueStateRestore(target) {
|
|
if (restoreTarget) {
|
|
if (restoreQueue) {
|
|
restoreQueue.push(target);
|
|
} else {
|
|
restoreQueue = [target];
|
|
}
|
|
} else {
|
|
restoreTarget = target;
|
|
}
|
|
}
|
|
function needsStateRestore() {
|
|
return restoreTarget !== null || restoreQueue !== null;
|
|
}
|
|
function restoreStateIfNeeded() {
|
|
if (!restoreTarget) {
|
|
return;
|
|
}
|
|
|
|
var target = restoreTarget;
|
|
var queuedTargets = restoreQueue;
|
|
restoreTarget = null;
|
|
restoreQueue = null;
|
|
restoreStateOfTarget(target);
|
|
|
|
if (queuedTargets) {
|
|
for (var i = 0; i < queuedTargets.length; i++) {
|
|
restoreStateOfTarget(queuedTargets[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// the renderer. Such as when we're dispatching events or if third party
|
|
// libraries need to call batchedUpdates. Eventually, this API will go away when
|
|
// everything is batched by default. We'll then have a similar API to opt-out of
|
|
// scheduled work and instead do synchronous work.
|
|
// Defaults
|
|
|
|
var batchedUpdatesImpl = function(fn, bookkeeping) {
|
|
return fn(bookkeeping);
|
|
};
|
|
|
|
var flushSyncImpl = function() {};
|
|
|
|
var isInsideEventHandler = false;
|
|
|
|
function finishEventHandler() {
|
|
// Here we wait until all updates have propagated, which is important
|
|
// when using controlled components within layers:
|
|
// https://github.com/facebook/react/issues/1698
|
|
// Then we restore state of any controlled component.
|
|
var controlledComponentsHavePendingUpdates = needsStateRestore();
|
|
|
|
if (controlledComponentsHavePendingUpdates) {
|
|
// If a controlled event was fired, we may need to restore the state of
|
|
// the DOM node back to the controlled value. This is necessary when React
|
|
// bails out of the update without touching the DOM.
|
|
// TODO: Restore state in the microtask, after the discrete updates flush,
|
|
// instead of early flushing them here.
|
|
flushSyncImpl();
|
|
restoreStateIfNeeded();
|
|
}
|
|
}
|
|
|
|
function batchedUpdates(fn, a, b) {
|
|
if (isInsideEventHandler) {
|
|
// If we are currently inside another batch, we need to wait until it
|
|
// fully completes before restoring state.
|
|
return fn(a, b);
|
|
}
|
|
|
|
isInsideEventHandler = true;
|
|
|
|
try {
|
|
return batchedUpdatesImpl(fn, a, b);
|
|
} finally {
|
|
isInsideEventHandler = false;
|
|
finishEventHandler();
|
|
}
|
|
} // TODO: Replace with flushSync
|
|
function setBatchingImplementation(
|
|
_batchedUpdatesImpl,
|
|
_discreteUpdatesImpl,
|
|
_flushSyncImpl
|
|
) {
|
|
batchedUpdatesImpl = _batchedUpdatesImpl;
|
|
flushSyncImpl = _flushSyncImpl;
|
|
}
|
|
|
|
function isInteractive(tag) {
|
|
return (
|
|
tag === "button" ||
|
|
tag === "input" ||
|
|
tag === "select" ||
|
|
tag === "textarea"
|
|
);
|
|
}
|
|
|
|
function shouldPreventMouseEvent(name, type, props) {
|
|
switch (name) {
|
|
case "onClick":
|
|
case "onClickCapture":
|
|
case "onDoubleClick":
|
|
case "onDoubleClickCapture":
|
|
case "onMouseDown":
|
|
case "onMouseDownCapture":
|
|
case "onMouseMove":
|
|
case "onMouseMoveCapture":
|
|
case "onMouseUp":
|
|
case "onMouseUpCapture":
|
|
case "onMouseEnter":
|
|
return !!(props.disabled && isInteractive(type));
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* @param {object} inst The instance, which is the source of events.
|
|
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
|
* @return {?function} The stored callback.
|
|
*/
|
|
|
|
function getListener(inst, registrationName) {
|
|
var stateNode = inst.stateNode;
|
|
|
|
if (stateNode === null) {
|
|
// Work in progress (ex: onload events in incremental mode).
|
|
return null;
|
|
}
|
|
|
|
var props = getFiberCurrentPropsFromNode(stateNode);
|
|
|
|
if (props === null) {
|
|
// Work in progress.
|
|
return null;
|
|
}
|
|
|
|
var listener = props[registrationName];
|
|
|
|
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
|
|
return null;
|
|
}
|
|
|
|
if (listener && typeof listener !== "function") {
|
|
throw new Error(
|
|
"Expected `" +
|
|
registrationName +
|
|
"` listener to be a function, instead got a value of `" +
|
|
typeof listener +
|
|
"` type."
|
|
);
|
|
}
|
|
|
|
return listener;
|
|
}
|
|
|
|
var passiveBrowserEventsSupported = false; // Check if browser support events with passive listeners
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
|
|
|
|
if (canUseDOM) {
|
|
try {
|
|
var options = {};
|
|
Object.defineProperty(options, "passive", {
|
|
get: function() {
|
|
passiveBrowserEventsSupported = true;
|
|
}
|
|
});
|
|
window.addEventListener("test", options, options);
|
|
window.removeEventListener("test", options, options);
|
|
} catch (e) {
|
|
passiveBrowserEventsSupported = false;
|
|
}
|
|
}
|
|
|
|
// Provided by www
|
|
var ReactFbErrorUtils = require("ReactFbErrorUtils");
|
|
|
|
if (typeof ReactFbErrorUtils.invokeGuardedCallback !== "function") {
|
|
throw new Error(
|
|
"Expected ReactFbErrorUtils.invokeGuardedCallback to be a function."
|
|
);
|
|
}
|
|
|
|
var invokeGuardedCallbackImpl = function(
|
|
name,
|
|
func,
|
|
context,
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f
|
|
) {
|
|
// This will call `this.onError(err)` if an error was caught.
|
|
ReactFbErrorUtils.invokeGuardedCallback.apply(this, arguments);
|
|
};
|
|
|
|
var hasError = false;
|
|
var caughtError = null; // Used by event system to capture/rethrow the first error.
|
|
|
|
var hasRethrowError = false;
|
|
var rethrowError = null;
|
|
var reporter = {
|
|
onError: function(error) {
|
|
hasError = true;
|
|
caughtError = error;
|
|
}
|
|
};
|
|
/**
|
|
* Call a function while guarding against errors that happens within it.
|
|
* Returns an error if it throws, otherwise null.
|
|
*
|
|
* In production, this is implemented using a try-catch. The reason we don't
|
|
* use a try-catch directly is so that we can swap out a different
|
|
* implementation in DEV mode.
|
|
*
|
|
* @param {String} name of the guard to use for logging or debugging
|
|
* @param {Function} func The function to invoke
|
|
* @param {*} context The context to use when calling the function
|
|
* @param {...*} args Arguments for function
|
|
*/
|
|
|
|
function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {
|
|
hasError = false;
|
|
caughtError = null;
|
|
invokeGuardedCallbackImpl.apply(reporter, arguments);
|
|
}
|
|
/**
|
|
* Same as invokeGuardedCallback, but instead of returning an error, it stores
|
|
* it in a global so it can be rethrown by `rethrowCaughtError` later.
|
|
* TODO: See if caughtError and rethrowError can be unified.
|
|
*
|
|
* @param {String} name of the guard to use for logging or debugging
|
|
* @param {Function} func The function to invoke
|
|
* @param {*} context The context to use when calling the function
|
|
* @param {...*} args Arguments for function
|
|
*/
|
|
|
|
function invokeGuardedCallbackAndCatchFirstError(
|
|
name,
|
|
func,
|
|
context,
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f
|
|
) {
|
|
invokeGuardedCallback.apply(this, arguments);
|
|
|
|
if (hasError) {
|
|
var error = clearCaughtError();
|
|
|
|
if (!hasRethrowError) {
|
|
hasRethrowError = true;
|
|
rethrowError = error;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* During execution of guarded functions we will capture the first error which
|
|
* we will rethrow to be handled by the top level error handler.
|
|
*/
|
|
|
|
function rethrowCaughtError() {
|
|
if (hasRethrowError) {
|
|
var error = rethrowError;
|
|
hasRethrowError = false;
|
|
rethrowError = null;
|
|
throw error;
|
|
}
|
|
}
|
|
function clearCaughtError() {
|
|
if (hasError) {
|
|
var error = caughtError;
|
|
hasError = false;
|
|
caughtError = null;
|
|
return error;
|
|
} else {
|
|
throw new Error(
|
|
"clearCaughtError was called but no error was captured. This error " +
|
|
"is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `ReactInstanceMap` maintains a mapping from a public facing stateful
|
|
* instance (key) and the internal representation (value). This allows public
|
|
* methods to accept the user facing instance as an argument and map them back
|
|
* to internal methods.
|
|
*
|
|
* Note that this module is currently shared and assumed to be stateless.
|
|
* If this becomes an actual Map, that will break.
|
|
*/
|
|
function get(key) {
|
|
return key._reactInternals;
|
|
}
|
|
function set(key, value) {
|
|
key._reactInternals = value;
|
|
}
|
|
|
|
var NoFlags =
|
|
/* */
|
|
0;
|
|
var PerformedWork =
|
|
/* */
|
|
1;
|
|
var Placement =
|
|
/* */
|
|
2;
|
|
var DidCapture =
|
|
/* */
|
|
128;
|
|
var Hydrating =
|
|
/* */
|
|
4096; // You can change the rest (and add more).
|
|
|
|
var Update =
|
|
/* */
|
|
4;
|
|
/* Skipped value: 0b000000000000000000000001000; */
|
|
|
|
var ChildDeletion =
|
|
/* */
|
|
16;
|
|
var ContentReset =
|
|
/* */
|
|
32;
|
|
var Callback =
|
|
/* */
|
|
64;
|
|
/* Used by DidCapture: 0b000000000000000000010000000; */
|
|
|
|
var ForceClientRender =
|
|
/* */
|
|
256;
|
|
var Ref =
|
|
/* */
|
|
512;
|
|
var Snapshot =
|
|
/* */
|
|
1024;
|
|
var Passive =
|
|
/* */
|
|
2048;
|
|
/* Used by Hydrating: 0b000000000000001000000000000; */
|
|
|
|
var Visibility =
|
|
/* */
|
|
8192;
|
|
var StoreConsistency =
|
|
/* */
|
|
16384;
|
|
var LifecycleEffectMask =
|
|
Passive | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
|
|
|
|
var HostEffectMask =
|
|
/* */
|
|
16383; // These are not really side effects, but we still reuse this field.
|
|
|
|
var Incomplete =
|
|
/* */
|
|
32768;
|
|
var ShouldCapture =
|
|
/* */
|
|
65536;
|
|
var ForceUpdateForLegacySuspense =
|
|
/* */
|
|
131072;
|
|
var Forked =
|
|
/* */
|
|
1048576; // Static tags describe aspects of a fiber that are not specific to a render,
|
|
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
|
|
// This enables us to defer more work in the unmount case,
|
|
// since we can defer traversing the tree during layout to look for Passive effects,
|
|
// and instead rely on the static flag as a signal that there may be cleanup work.
|
|
|
|
var RefStatic =
|
|
/* */
|
|
2097152;
|
|
var LayoutStatic =
|
|
/* */
|
|
4194304;
|
|
var PassiveStatic =
|
|
/* */
|
|
8388608; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
|
|
|
|
var PlacementDEV =
|
|
/* */
|
|
16777216;
|
|
var MountLayoutDev =
|
|
/* */
|
|
33554432;
|
|
var MountPassiveDev =
|
|
/* */
|
|
67108864; // Groups of flags that are used in the commit phase to skip over trees that
|
|
// don't contain effects, by checking subtreeFlags.
|
|
|
|
var BeforeMutationMask = // TODO: Remove Update flag from before mutation phase by re-landing Visibility
|
|
// flag logic (see #20043)
|
|
Update |
|
|
Snapshot | // createEventHandle needs to visit deleted and hidden trees to
|
|
// fire beforeblur
|
|
// TODO: Only need to visit Deletions during BeforeMutation phase if an
|
|
// element is focused.
|
|
(ChildDeletion | Visibility);
|
|
var MutationMask =
|
|
Placement |
|
|
Update |
|
|
ChildDeletion |
|
|
ContentReset |
|
|
Ref |
|
|
Hydrating |
|
|
Visibility;
|
|
var LayoutMask = Update | Callback | Ref | Visibility; // TODO: Split into PassiveMountMask and PassiveUnmountMask
|
|
|
|
var PassiveMask = Passive | Visibility | ChildDeletion; // Union of tags that don't get reset on clones.
|
|
// This allows certain concepts to persist without recalculating them,
|
|
// e.g. whether a subtree contains passive effects or portals.
|
|
|
|
var StaticMask = LayoutStatic | PassiveStatic | RefStatic;
|
|
|
|
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
|
function getNearestMountedFiber(fiber) {
|
|
var node = fiber;
|
|
var nearestMounted = fiber;
|
|
|
|
if (!fiber.alternate) {
|
|
// If there is no alternate, this might be a new tree that isn't inserted
|
|
// yet. If it is, then it will have a pending insertion effect on it.
|
|
var nextNode = node;
|
|
|
|
do {
|
|
node = nextNode;
|
|
|
|
if ((node.flags & (Placement | Hydrating)) !== NoFlags) {
|
|
// This is an insertion or in-progress hydration. The nearest possible
|
|
// mounted fiber is the parent but we need to continue to figure out
|
|
// if that one is still mounted.
|
|
nearestMounted = node.return;
|
|
} // $FlowFixMe[incompatible-type] we bail out when we get a null
|
|
|
|
nextNode = node.return;
|
|
} while (nextNode);
|
|
} else {
|
|
while (node.return) {
|
|
node = node.return;
|
|
}
|
|
}
|
|
|
|
if (node.tag === HostRoot) {
|
|
// TODO: Check if this was a nested HostRoot when used with
|
|
// renderContainerIntoSubtree.
|
|
return nearestMounted;
|
|
} // If we didn't hit the root, that means that we're in an disconnected tree
|
|
// that has been unmounted.
|
|
|
|
return null;
|
|
}
|
|
function getSuspenseInstanceFromFiber(fiber) {
|
|
if (fiber.tag === SuspenseComponent) {
|
|
var suspenseState = fiber.memoizedState;
|
|
|
|
if (suspenseState === null) {
|
|
var current = fiber.alternate;
|
|
|
|
if (current !== null) {
|
|
suspenseState = current.memoizedState;
|
|
}
|
|
}
|
|
|
|
if (suspenseState !== null) {
|
|
return suspenseState.dehydrated;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
function getContainerFromFiber(fiber) {
|
|
return fiber.tag === HostRoot ? fiber.stateNode.containerInfo : null;
|
|
}
|
|
function isMounted(component) {
|
|
{
|
|
var owner = ReactCurrentOwner.current;
|
|
|
|
if (owner !== null && owner.tag === ClassComponent) {
|
|
var ownerFiber = owner;
|
|
var instance = ownerFiber.stateNode;
|
|
|
|
if (!instance._warnedAboutRefsInRender) {
|
|
error(
|
|
"%s is accessing isMounted inside its render() function. " +
|
|
"render() should be a pure function of props and state. It should " +
|
|
"never access something that requires stale data from the previous " +
|
|
"render, such as refs. Move this logic to componentDidMount and " +
|
|
"componentDidUpdate instead.",
|
|
getComponentNameFromFiber(ownerFiber) || "A component"
|
|
);
|
|
}
|
|
|
|
instance._warnedAboutRefsInRender = true;
|
|
}
|
|
}
|
|
|
|
var fiber = get(component);
|
|
|
|
if (!fiber) {
|
|
return false;
|
|
}
|
|
|
|
return getNearestMountedFiber(fiber) === fiber;
|
|
}
|
|
|
|
function assertIsMounted(fiber) {
|
|
if (getNearestMountedFiber(fiber) !== fiber) {
|
|
throw new Error("Unable to find node on an unmounted component.");
|
|
}
|
|
}
|
|
|
|
function findCurrentFiberUsingSlowPath(fiber) {
|
|
var alternate = fiber.alternate;
|
|
|
|
if (!alternate) {
|
|
// If there is no alternate, then we only need to check if it is mounted.
|
|
var nearestMounted = getNearestMountedFiber(fiber);
|
|
|
|
if (nearestMounted === null) {
|
|
throw new Error("Unable to find node on an unmounted component.");
|
|
}
|
|
|
|
if (nearestMounted !== fiber) {
|
|
return null;
|
|
}
|
|
|
|
return fiber;
|
|
} // If we have two possible branches, we'll walk backwards up to the root
|
|
// to see what path the root points to. On the way we may hit one of the
|
|
// special cases and we'll deal with them.
|
|
|
|
var a = fiber;
|
|
var b = alternate;
|
|
|
|
while (true) {
|
|
var parentA = a.return;
|
|
|
|
if (parentA === null) {
|
|
// We're at the root.
|
|
break;
|
|
}
|
|
|
|
var parentB = parentA.alternate;
|
|
|
|
if (parentB === null) {
|
|
// There is no alternate. This is an unusual case. Currently, it only
|
|
// happens when a Suspense component is hidden. An extra fragment fiber
|
|
// is inserted in between the Suspense fiber and its children. Skip
|
|
// over this extra fragment fiber and proceed to the next parent.
|
|
var nextParent = parentA.return;
|
|
|
|
if (nextParent !== null) {
|
|
a = b = nextParent;
|
|
continue;
|
|
} // If there's no parent, we're at the root.
|
|
|
|
break;
|
|
} // If both copies of the parent fiber point to the same child, we can
|
|
// assume that the child is current. This happens when we bailout on low
|
|
// priority: the bailed out fiber's child reuses the current child.
|
|
|
|
if (parentA.child === parentB.child) {
|
|
var child = parentA.child;
|
|
|
|
while (child) {
|
|
if (child === a) {
|
|
// We've determined that A is the current branch.
|
|
assertIsMounted(parentA);
|
|
return fiber;
|
|
}
|
|
|
|
if (child === b) {
|
|
// We've determined that B is the current branch.
|
|
assertIsMounted(parentA);
|
|
return alternate;
|
|
}
|
|
|
|
child = child.sibling;
|
|
} // We should never have an alternate for any mounting node. So the only
|
|
// way this could possibly happen is if this was unmounted, if at all.
|
|
|
|
throw new Error("Unable to find node on an unmounted component.");
|
|
}
|
|
|
|
if (a.return !== b.return) {
|
|
// The return pointer of A and the return pointer of B point to different
|
|
// fibers. We assume that return pointers never criss-cross, so A must
|
|
// belong to the child set of A.return, and B must belong to the child
|
|
// set of B.return.
|
|
a = parentA;
|
|
b = parentB;
|
|
} else {
|
|
// The return pointers point to the same fiber. We'll have to use the
|
|
// default, slow path: scan the child sets of each parent alternate to see
|
|
// which child belongs to which set.
|
|
//
|
|
// Search parent A's child set
|
|
var didFindChild = false;
|
|
var _child = parentA.child;
|
|
|
|
while (_child) {
|
|
if (_child === a) {
|
|
didFindChild = true;
|
|
a = parentA;
|
|
b = parentB;
|
|
break;
|
|
}
|
|
|
|
if (_child === b) {
|
|
didFindChild = true;
|
|
b = parentA;
|
|
a = parentB;
|
|
break;
|
|
}
|
|
|
|
_child = _child.sibling;
|
|
}
|
|
|
|
if (!didFindChild) {
|
|
// Search parent B's child set
|
|
_child = parentB.child;
|
|
|
|
while (_child) {
|
|
if (_child === a) {
|
|
didFindChild = true;
|
|
a = parentB;
|
|
b = parentA;
|
|
break;
|
|
}
|
|
|
|
if (_child === b) {
|
|
didFindChild = true;
|
|
b = parentB;
|
|
a = parentA;
|
|
break;
|
|
}
|
|
|
|
_child = _child.sibling;
|
|
}
|
|
|
|
if (!didFindChild) {
|
|
throw new Error(
|
|
"Child was not found in either parent set. This indicates a bug " +
|
|
"in React related to the return pointer. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (a.alternate !== b) {
|
|
throw new Error(
|
|
"Return fibers should always be each others' alternates. " +
|
|
"This error is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
} // If the root is not a host container, we're in a disconnected tree. I.e.
|
|
// unmounted.
|
|
|
|
if (a.tag !== HostRoot) {
|
|
throw new Error("Unable to find node on an unmounted component.");
|
|
}
|
|
|
|
if (a.stateNode.current === a) {
|
|
// We've determined that A is the current branch.
|
|
return fiber;
|
|
} // Otherwise B has to be current branch.
|
|
|
|
return alternate;
|
|
}
|
|
function findCurrentHostFiber(parent) {
|
|
var currentParent = findCurrentFiberUsingSlowPath(parent);
|
|
return currentParent !== null
|
|
? findCurrentHostFiberImpl(currentParent)
|
|
: null;
|
|
}
|
|
|
|
function findCurrentHostFiberImpl(node) {
|
|
// Next we'll drill down this component to find the first HostComponent/Text.
|
|
var tag = node.tag;
|
|
|
|
if (
|
|
tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton ||
|
|
tag === HostText
|
|
) {
|
|
return node;
|
|
}
|
|
|
|
var child = node.child;
|
|
|
|
while (child !== null) {
|
|
var match = findCurrentHostFiberImpl(child);
|
|
|
|
if (match !== null) {
|
|
return match;
|
|
}
|
|
|
|
child = child.sibling;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function isFiberSuspenseAndTimedOut(fiber) {
|
|
var memoizedState = fiber.memoizedState;
|
|
return (
|
|
fiber.tag === SuspenseComponent &&
|
|
memoizedState !== null &&
|
|
memoizedState.dehydrated === null
|
|
);
|
|
}
|
|
function doesFiberContain(parentFiber, childFiber) {
|
|
var node = childFiber;
|
|
var parentFiberAlternate = parentFiber.alternate;
|
|
|
|
while (node !== null) {
|
|
if (node === parentFiber || node === parentFiberAlternate) {
|
|
return true;
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// This module only exists as an ESM wrapper around the external CommonJS
|
|
var scheduleCallback = Scheduler.unstable_scheduleCallback;
|
|
var cancelCallback = Scheduler.unstable_cancelCallback;
|
|
var shouldYield = Scheduler.unstable_shouldYield;
|
|
var requestPaint = Scheduler.unstable_requestPaint;
|
|
var now = Scheduler.unstable_now;
|
|
var getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
|
|
var ImmediatePriority = Scheduler.unstable_ImmediatePriority;
|
|
var UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
|
|
var NormalPriority = Scheduler.unstable_NormalPriority;
|
|
var LowPriority = Scheduler.unstable_LowPriority;
|
|
var IdlePriority = Scheduler.unstable_IdlePriority; // this doesn't actually exist on the scheduler, but it *does*
|
|
|
|
var rendererID = null;
|
|
var injectedHook = null;
|
|
var hasLoggedError = false;
|
|
function injectInternals(internals) {
|
|
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === "undefined") {
|
|
// No DevTools
|
|
return false;
|
|
}
|
|
|
|
var hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
|
|
if (hook.isDisabled) {
|
|
// This isn't a real property on the hook, but it can be set to opt out
|
|
// of DevTools integration and associated warnings and logs.
|
|
// https://github.com/facebook/react/issues/3877
|
|
return true;
|
|
}
|
|
|
|
if (!hook.supportsFiber) {
|
|
{
|
|
error(
|
|
"The installed version of React DevTools is too old and will not work " +
|
|
"with the current version of React. Please update React DevTools. " +
|
|
"https://reactjs.org/link/react-devtools"
|
|
);
|
|
} // DevTools exists, even though it doesn't support Fiber.
|
|
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
if (enableSchedulingProfiler) {
|
|
// Conditionally inject these hooks only if Timeline profiler is supported by this build.
|
|
// This gives DevTools a way to feature detect that isn't tied to version number
|
|
// (since profiling and timeline are controlled by different feature flags).
|
|
internals = assign({}, internals, {
|
|
getLaneLabelMap: getLaneLabelMap,
|
|
injectProfilingHooks: injectProfilingHooks
|
|
});
|
|
}
|
|
|
|
rendererID = hook.inject(internals); // We have successfully injected, so now it is safe to set up hooks.
|
|
|
|
injectedHook = hook;
|
|
} catch (err) {
|
|
// Catch all errors because it is unsafe to throw during initialization.
|
|
{
|
|
error("React instrumentation encountered an error: %s.", err);
|
|
}
|
|
}
|
|
|
|
if (hook.checkDCE) {
|
|
// This is the real DevTools.
|
|
return true;
|
|
} else {
|
|
// This is likely a hook installed by Fast Refresh runtime.
|
|
return false;
|
|
}
|
|
}
|
|
function onScheduleRoot(root, children) {
|
|
{
|
|
if (
|
|
injectedHook &&
|
|
typeof injectedHook.onScheduleFiberRoot === "function"
|
|
) {
|
|
try {
|
|
injectedHook.onScheduleFiberRoot(rendererID, root, children);
|
|
} catch (err) {
|
|
if (!hasLoggedError) {
|
|
hasLoggedError = true;
|
|
|
|
error("React instrumentation encountered an error: %s", err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function onCommitRoot(root, eventPriority) {
|
|
if (injectedHook && typeof injectedHook.onCommitFiberRoot === "function") {
|
|
try {
|
|
var didError = (root.current.flags & DidCapture) === DidCapture;
|
|
|
|
if (enableProfilerTimer) {
|
|
var schedulerPriority;
|
|
|
|
switch (eventPriority) {
|
|
case DiscreteEventPriority:
|
|
schedulerPriority = ImmediatePriority;
|
|
break;
|
|
|
|
case ContinuousEventPriority:
|
|
schedulerPriority = UserBlockingPriority;
|
|
break;
|
|
|
|
case DefaultEventPriority:
|
|
schedulerPriority = NormalPriority;
|
|
break;
|
|
|
|
case IdleEventPriority:
|
|
schedulerPriority = IdlePriority;
|
|
break;
|
|
|
|
default:
|
|
schedulerPriority = NormalPriority;
|
|
break;
|
|
}
|
|
|
|
injectedHook.onCommitFiberRoot(
|
|
rendererID,
|
|
root,
|
|
schedulerPriority,
|
|
didError
|
|
);
|
|
} else {
|
|
injectedHook.onCommitFiberRoot(rendererID, root, undefined, didError);
|
|
}
|
|
} catch (err) {
|
|
{
|
|
if (!hasLoggedError) {
|
|
hasLoggedError = true;
|
|
|
|
error("React instrumentation encountered an error: %s", err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function onPostCommitRoot(root) {
|
|
if (
|
|
injectedHook &&
|
|
typeof injectedHook.onPostCommitFiberRoot === "function"
|
|
) {
|
|
try {
|
|
injectedHook.onPostCommitFiberRoot(rendererID, root);
|
|
} catch (err) {
|
|
{
|
|
if (!hasLoggedError) {
|
|
hasLoggedError = true;
|
|
|
|
error("React instrumentation encountered an error: %s", err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function onCommitUnmount(fiber) {
|
|
if (injectedHook && typeof injectedHook.onCommitFiberUnmount === "function") {
|
|
try {
|
|
injectedHook.onCommitFiberUnmount(rendererID, fiber);
|
|
} catch (err) {
|
|
{
|
|
if (!hasLoggedError) {
|
|
hasLoggedError = true;
|
|
|
|
error("React instrumentation encountered an error: %s", err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function injectProfilingHooks(profilingHooks) {}
|
|
|
|
function getLaneLabelMap() {
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
var NoMode =
|
|
/* */
|
|
0; // TODO: Remove ConcurrentMode by reading from the root tag instead
|
|
|
|
var ConcurrentMode =
|
|
/* */
|
|
1;
|
|
var ProfileMode =
|
|
/* */
|
|
2;
|
|
var StrictLegacyMode =
|
|
/* */
|
|
8;
|
|
var StrictEffectsMode =
|
|
/* */
|
|
16;
|
|
var ConcurrentUpdatesByDefaultMode =
|
|
/* */
|
|
32;
|
|
|
|
// TODO: This is pretty well supported by browsers. Maybe we can drop it.
|
|
var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback; // Count leading zeros.
|
|
// Based on:
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
|
|
|
|
var log = Math.log;
|
|
var LN2 = Math.LN2;
|
|
|
|
function clz32Fallback(x) {
|
|
var asUint = x >>> 0;
|
|
|
|
if (asUint === 0) {
|
|
return 32;
|
|
}
|
|
|
|
return (31 - ((log(asUint) / LN2) | 0)) | 0;
|
|
}
|
|
|
|
// If those values are changed that package should be rebuilt and redeployed.
|
|
|
|
var TotalLanes = 31;
|
|
var NoLanes =
|
|
/* */
|
|
0;
|
|
var NoLane =
|
|
/* */
|
|
0;
|
|
var SyncHydrationLane =
|
|
/* */
|
|
1;
|
|
var SyncLane =
|
|
/* */
|
|
2;
|
|
var InputContinuousHydrationLane =
|
|
/* */
|
|
4;
|
|
var InputContinuousLane =
|
|
/* */
|
|
8;
|
|
var DefaultHydrationLane =
|
|
/* */
|
|
16;
|
|
var DefaultLane =
|
|
/* */
|
|
32;
|
|
var SyncUpdateLanes =
|
|
/* */
|
|
42;
|
|
var TransitionHydrationLane =
|
|
/* */
|
|
64;
|
|
var TransitionLanes =
|
|
/* */
|
|
8388480;
|
|
var TransitionLane1 =
|
|
/* */
|
|
128;
|
|
var TransitionLane2 =
|
|
/* */
|
|
256;
|
|
var TransitionLane3 =
|
|
/* */
|
|
512;
|
|
var TransitionLane4 =
|
|
/* */
|
|
1024;
|
|
var TransitionLane5 =
|
|
/* */
|
|
2048;
|
|
var TransitionLane6 =
|
|
/* */
|
|
4096;
|
|
var TransitionLane7 =
|
|
/* */
|
|
8192;
|
|
var TransitionLane8 =
|
|
/* */
|
|
16384;
|
|
var TransitionLane9 =
|
|
/* */
|
|
32768;
|
|
var TransitionLane10 =
|
|
/* */
|
|
65536;
|
|
var TransitionLane11 =
|
|
/* */
|
|
131072;
|
|
var TransitionLane12 =
|
|
/* */
|
|
262144;
|
|
var TransitionLane13 =
|
|
/* */
|
|
524288;
|
|
var TransitionLane14 =
|
|
/* */
|
|
1048576;
|
|
var TransitionLane15 =
|
|
/* */
|
|
2097152;
|
|
var TransitionLane16 =
|
|
/* */
|
|
4194304;
|
|
var RetryLanes =
|
|
/* */
|
|
125829120;
|
|
var RetryLane1 =
|
|
/* */
|
|
8388608;
|
|
var RetryLane2 =
|
|
/* */
|
|
16777216;
|
|
var RetryLane3 =
|
|
/* */
|
|
33554432;
|
|
var RetryLane4 =
|
|
/* */
|
|
67108864;
|
|
var SomeRetryLane = RetryLane1;
|
|
var SelectiveHydrationLane =
|
|
/* */
|
|
134217728;
|
|
var NonIdleLanes =
|
|
/* */
|
|
268435455;
|
|
var IdleHydrationLane =
|
|
/* */
|
|
268435456;
|
|
var IdleLane =
|
|
/* */
|
|
536870912;
|
|
var OffscreenLane =
|
|
/* */
|
|
1073741824; // This function is used for the experimental timeline (react-devtools-timeline)
|
|
var NoTimestamp = -1;
|
|
var nextTransitionLane = TransitionLane1;
|
|
var nextRetryLane = RetryLane1;
|
|
|
|
function getHighestPriorityLanes(lanes) {
|
|
{
|
|
var pendingSyncLanes = lanes & SyncUpdateLanes;
|
|
|
|
if (pendingSyncLanes !== 0) {
|
|
return pendingSyncLanes;
|
|
}
|
|
}
|
|
|
|
switch (getHighestPriorityLane(lanes)) {
|
|
case SyncHydrationLane:
|
|
return SyncHydrationLane;
|
|
|
|
case SyncLane:
|
|
return SyncLane;
|
|
|
|
case InputContinuousHydrationLane:
|
|
return InputContinuousHydrationLane;
|
|
|
|
case InputContinuousLane:
|
|
return InputContinuousLane;
|
|
|
|
case DefaultHydrationLane:
|
|
return DefaultHydrationLane;
|
|
|
|
case DefaultLane:
|
|
return DefaultLane;
|
|
|
|
case TransitionHydrationLane:
|
|
return TransitionHydrationLane;
|
|
|
|
case TransitionLane1:
|
|
case TransitionLane2:
|
|
case TransitionLane3:
|
|
case TransitionLane4:
|
|
case TransitionLane5:
|
|
case TransitionLane6:
|
|
case TransitionLane7:
|
|
case TransitionLane8:
|
|
case TransitionLane9:
|
|
case TransitionLane10:
|
|
case TransitionLane11:
|
|
case TransitionLane12:
|
|
case TransitionLane13:
|
|
case TransitionLane14:
|
|
case TransitionLane15:
|
|
case TransitionLane16:
|
|
return lanes & TransitionLanes;
|
|
|
|
case RetryLane1:
|
|
case RetryLane2:
|
|
case RetryLane3:
|
|
case RetryLane4:
|
|
return lanes & RetryLanes;
|
|
|
|
case SelectiveHydrationLane:
|
|
return SelectiveHydrationLane;
|
|
|
|
case IdleHydrationLane:
|
|
return IdleHydrationLane;
|
|
|
|
case IdleLane:
|
|
return IdleLane;
|
|
|
|
case OffscreenLane:
|
|
return OffscreenLane;
|
|
|
|
default:
|
|
{
|
|
error("Should have found matching lanes. This is a bug in React.");
|
|
} // This shouldn't be reachable, but as a fallback, return the entire bitmask.
|
|
|
|
return lanes;
|
|
}
|
|
}
|
|
|
|
function getNextLanes(root, wipLanes) {
|
|
// Early bailout if there's no pending work left.
|
|
var pendingLanes = root.pendingLanes;
|
|
|
|
if (pendingLanes === NoLanes) {
|
|
return NoLanes;
|
|
}
|
|
|
|
var nextLanes = NoLanes;
|
|
var suspendedLanes = root.suspendedLanes;
|
|
var pingedLanes = root.pingedLanes; // Do not work on any idle work until all the non-idle work has finished,
|
|
// even if the work is suspended.
|
|
|
|
var nonIdlePendingLanes = pendingLanes & NonIdleLanes;
|
|
|
|
if (nonIdlePendingLanes !== NoLanes) {
|
|
var nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
|
|
|
|
if (nonIdleUnblockedLanes !== NoLanes) {
|
|
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
|
|
} else {
|
|
var nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
|
|
|
|
if (nonIdlePingedLanes !== NoLanes) {
|
|
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
|
|
}
|
|
}
|
|
} else {
|
|
// The only remaining work is Idle.
|
|
var unblockedLanes = pendingLanes & ~suspendedLanes;
|
|
|
|
if (unblockedLanes !== NoLanes) {
|
|
nextLanes = getHighestPriorityLanes(unblockedLanes);
|
|
} else {
|
|
if (pingedLanes !== NoLanes) {
|
|
nextLanes = getHighestPriorityLanes(pingedLanes);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nextLanes === NoLanes) {
|
|
// This should only be reachable if we're suspended
|
|
// TODO: Consider warning in this path if a fallback timer is not scheduled.
|
|
return NoLanes;
|
|
} // If we're already in the middle of a render, switching lanes will interrupt
|
|
// it and we'll lose our progress. We should only do this if the new lanes are
|
|
// higher priority.
|
|
|
|
if (
|
|
wipLanes !== NoLanes &&
|
|
wipLanes !== nextLanes && // If we already suspended with a delay, then interrupting is fine. Don't
|
|
// bother waiting until the root is complete.
|
|
(wipLanes & suspendedLanes) === NoLanes
|
|
) {
|
|
var nextLane = getHighestPriorityLane(nextLanes);
|
|
var wipLane = getHighestPriorityLane(wipLanes);
|
|
|
|
if (
|
|
// Tests whether the next lane is equal or lower priority than the wip
|
|
// one. This works because the bits decrease in priority as you go left.
|
|
nextLane >= wipLane || // Default priority updates should not interrupt transition updates. The
|
|
// only difference between default updates and transition updates is that
|
|
// default updates do not support refresh transitions.
|
|
(nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
|
|
) {
|
|
// Keep working on the existing in-progress tree. Do not interrupt.
|
|
return wipLanes;
|
|
}
|
|
}
|
|
|
|
if ((root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode);
|
|
else if ((nextLanes & InputContinuousLane) !== NoLanes) {
|
|
// When updates are sync by default, we entangle continuous priority updates
|
|
// and default updates, so they render in the same batch. The only reason
|
|
// they use separate lanes is because continuous updates should interrupt
|
|
// transitions, but default updates should not.
|
|
nextLanes |= pendingLanes & DefaultLane;
|
|
} // Check for entangled lanes and add them to the batch.
|
|
//
|
|
// A lane is said to be entangled with another when it's not allowed to render
|
|
// in a batch that does not also include the other lane. Typically we do this
|
|
// when multiple updates have the same source, and we only want to respond to
|
|
// the most recent event from that source.
|
|
//
|
|
// Note that we apply entanglements *after* checking for partial work above.
|
|
// This means that if a lane is entangled during an interleaved event while
|
|
// it's already rendering, we won't interrupt it. This is intentional, since
|
|
// entanglement is usually "best effort": we'll try our best to render the
|
|
// lanes in the same batch, but it's not worth throwing out partially
|
|
// completed work in order to do it.
|
|
// TODO: Reconsider this. The counter-argument is that the partial work
|
|
// represents an intermediate state, which we don't want to show to the user.
|
|
// And by spending extra time finishing it, we're increasing the amount of
|
|
// time it takes to show the final state, which is what they are actually
|
|
// waiting for.
|
|
//
|
|
// For those exceptions where entanglement is semantically important, like
|
|
// useMutableSource, we should ensure that there is no partial work at the
|
|
// time we apply the entanglement.
|
|
|
|
var entangledLanes = root.entangledLanes;
|
|
|
|
if (entangledLanes !== NoLanes) {
|
|
var entanglements = root.entanglements;
|
|
var lanes = nextLanes & entangledLanes;
|
|
|
|
while (lanes > 0) {
|
|
var index = pickArbitraryLaneIndex(lanes);
|
|
var lane = 1 << index;
|
|
nextLanes |= entanglements[index];
|
|
lanes &= ~lane;
|
|
}
|
|
}
|
|
|
|
return nextLanes;
|
|
}
|
|
function getMostRecentEventTime(root, lanes) {
|
|
var eventTimes = root.eventTimes;
|
|
var mostRecentEventTime = NoTimestamp;
|
|
|
|
while (lanes > 0) {
|
|
var index = pickArbitraryLaneIndex(lanes);
|
|
var lane = 1 << index;
|
|
var eventTime = eventTimes[index];
|
|
|
|
if (eventTime > mostRecentEventTime) {
|
|
mostRecentEventTime = eventTime;
|
|
}
|
|
|
|
lanes &= ~lane;
|
|
}
|
|
|
|
return mostRecentEventTime;
|
|
}
|
|
|
|
function computeExpirationTime(lane, currentTime) {
|
|
switch (lane) {
|
|
case SyncHydrationLane:
|
|
case SyncLane:
|
|
case InputContinuousHydrationLane:
|
|
case InputContinuousLane:
|
|
// User interactions should expire slightly more quickly.
|
|
//
|
|
// NOTE: This is set to the corresponding constant as in Scheduler.js.
|
|
// When we made it larger, a product metric in www regressed, suggesting
|
|
// there's a user interaction that's being starved by a series of
|
|
// synchronous updates. If that theory is correct, the proper solution is
|
|
// to fix the starvation. However, this scenario supports the idea that
|
|
// expiration times are an important safeguard when starvation
|
|
// does happen.
|
|
return currentTime + 250;
|
|
|
|
case DefaultHydrationLane:
|
|
case DefaultLane:
|
|
case TransitionHydrationLane:
|
|
case TransitionLane1:
|
|
case TransitionLane2:
|
|
case TransitionLane3:
|
|
case TransitionLane4:
|
|
case TransitionLane5:
|
|
case TransitionLane6:
|
|
case TransitionLane7:
|
|
case TransitionLane8:
|
|
case TransitionLane9:
|
|
case TransitionLane10:
|
|
case TransitionLane11:
|
|
case TransitionLane12:
|
|
case TransitionLane13:
|
|
case TransitionLane14:
|
|
case TransitionLane15:
|
|
case TransitionLane16:
|
|
return currentTime + 5000;
|
|
|
|
case RetryLane1:
|
|
case RetryLane2:
|
|
case RetryLane3:
|
|
case RetryLane4:
|
|
// TODO: Retries should be allowed to expire if they are CPU bound for
|
|
// too long, but when I made this change it caused a spike in browser
|
|
// crashes. There must be some other underlying bug; not super urgent but
|
|
// ideally should figure out why and fix it. Unfortunately we don't have
|
|
// a repro for the crashes, only detected via production metrics.
|
|
return NoTimestamp;
|
|
|
|
case SelectiveHydrationLane:
|
|
case IdleHydrationLane:
|
|
case IdleLane:
|
|
case OffscreenLane:
|
|
// Anything idle priority or lower should never expire.
|
|
return NoTimestamp;
|
|
|
|
default:
|
|
{
|
|
error("Should have found matching lanes. This is a bug in React.");
|
|
}
|
|
|
|
return NoTimestamp;
|
|
}
|
|
}
|
|
|
|
function markStarvedLanesAsExpired(root, currentTime) {
|
|
// TODO: This gets called every time we yield. We can optimize by storing
|
|
// the earliest expiration time on the root. Then use that to quickly bail out
|
|
// of this function.
|
|
var pendingLanes = root.pendingLanes;
|
|
var suspendedLanes = root.suspendedLanes;
|
|
var pingedLanes = root.pingedLanes;
|
|
var expirationTimes = root.expirationTimes; // Iterate through the pending lanes and check if we've reached their
|
|
// expiration time. If so, we'll assume the update is being starved and mark
|
|
// it as expired to force it to finish.
|
|
//
|
|
// We exclude retry lanes because those must always be time sliced, in order
|
|
// to unwrap uncached promises.
|
|
// TODO: Write a test for this
|
|
|
|
var lanes = pendingLanes & ~RetryLanes;
|
|
|
|
while (lanes > 0) {
|
|
var index = pickArbitraryLaneIndex(lanes);
|
|
var lane = 1 << index;
|
|
var expirationTime = expirationTimes[index];
|
|
|
|
if (expirationTime === NoTimestamp) {
|
|
// Found a pending lane with no expiration time. If it's not suspended, or
|
|
// if it's pinged, assume it's CPU-bound. Compute a new expiration time
|
|
// using the current time.
|
|
if (
|
|
(lane & suspendedLanes) === NoLanes ||
|
|
(lane & pingedLanes) !== NoLanes
|
|
) {
|
|
// Assumes timestamps are monotonically increasing.
|
|
expirationTimes[index] = computeExpirationTime(lane, currentTime);
|
|
}
|
|
} else if (expirationTime <= currentTime) {
|
|
// This lane expired
|
|
root.expiredLanes |= lane;
|
|
}
|
|
|
|
lanes &= ~lane;
|
|
}
|
|
} // This returns the highest priority pending lanes regardless of whether they
|
|
// are suspended.
|
|
|
|
function getHighestPriorityPendingLanes(root) {
|
|
return getHighestPriorityLanes(root.pendingLanes);
|
|
}
|
|
function getLanesToRetrySynchronouslyOnError(root, originallyAttemptedLanes) {
|
|
if (root.errorRecoveryDisabledLanes & originallyAttemptedLanes) {
|
|
// The error recovery mechanism is disabled until these lanes are cleared.
|
|
return NoLanes;
|
|
}
|
|
|
|
var everythingButOffscreen = root.pendingLanes & ~OffscreenLane;
|
|
|
|
if (everythingButOffscreen !== NoLanes) {
|
|
return everythingButOffscreen;
|
|
}
|
|
|
|
if (everythingButOffscreen & OffscreenLane) {
|
|
return OffscreenLane;
|
|
}
|
|
|
|
return NoLanes;
|
|
}
|
|
function includesSyncLane(lanes) {
|
|
return (lanes & (SyncLane | SyncHydrationLane)) !== NoLanes;
|
|
}
|
|
function includesNonIdleWork(lanes) {
|
|
return (lanes & NonIdleLanes) !== NoLanes;
|
|
}
|
|
function includesOnlyRetries(lanes) {
|
|
return (lanes & RetryLanes) === lanes;
|
|
}
|
|
function includesOnlyNonUrgentLanes(lanes) {
|
|
// TODO: Should hydration lanes be included here? This function is only
|
|
// used in `updateDeferredValueImpl`.
|
|
var UrgentLanes = SyncLane | InputContinuousLane | DefaultLane;
|
|
return (lanes & UrgentLanes) === NoLanes;
|
|
}
|
|
function includesOnlyTransitions(lanes) {
|
|
return (lanes & TransitionLanes) === lanes;
|
|
}
|
|
function includesBlockingLane(root, lanes) {
|
|
if ((root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode) {
|
|
// Concurrent updates by default always use time slicing.
|
|
return false;
|
|
}
|
|
|
|
var SyncDefaultLanes =
|
|
InputContinuousHydrationLane |
|
|
InputContinuousLane |
|
|
DefaultHydrationLane |
|
|
DefaultLane;
|
|
return (lanes & SyncDefaultLanes) !== NoLanes;
|
|
}
|
|
function includesExpiredLane(root, lanes) {
|
|
// This is a separate check from includesBlockingLane because a lane can
|
|
// expire after a render has already started.
|
|
return (lanes & root.expiredLanes) !== NoLanes;
|
|
}
|
|
function isTransitionLane(lane) {
|
|
return (lane & TransitionLanes) !== NoLanes;
|
|
}
|
|
function claimNextTransitionLane() {
|
|
// Cycle through the lanes, assigning each new transition to the next lane.
|
|
// In most cases, this means every transition gets its own lane, until we
|
|
// run out of lanes and cycle back to the beginning.
|
|
var lane = nextTransitionLane;
|
|
nextTransitionLane <<= 1;
|
|
|
|
if ((nextTransitionLane & TransitionLanes) === NoLanes) {
|
|
nextTransitionLane = TransitionLane1;
|
|
}
|
|
|
|
return lane;
|
|
}
|
|
function claimNextRetryLane() {
|
|
var lane = nextRetryLane;
|
|
nextRetryLane <<= 1;
|
|
|
|
if ((nextRetryLane & RetryLanes) === NoLanes) {
|
|
nextRetryLane = RetryLane1;
|
|
}
|
|
|
|
return lane;
|
|
}
|
|
function getHighestPriorityLane(lanes) {
|
|
return lanes & -lanes;
|
|
}
|
|
function pickArbitraryLane(lanes) {
|
|
// This wrapper function gets inlined. Only exists so to communicate that it
|
|
// doesn't matter which bit is selected; you can pick any bit without
|
|
// affecting the algorithms where its used. Here I'm using
|
|
// getHighestPriorityLane because it requires the fewest operations.
|
|
return getHighestPriorityLane(lanes);
|
|
}
|
|
|
|
function pickArbitraryLaneIndex(lanes) {
|
|
return 31 - clz32(lanes);
|
|
}
|
|
|
|
function laneToIndex(lane) {
|
|
return pickArbitraryLaneIndex(lane);
|
|
}
|
|
|
|
function includesSomeLane(a, b) {
|
|
return (a & b) !== NoLanes;
|
|
}
|
|
function isSubsetOfLanes(set, subset) {
|
|
return (set & subset) === subset;
|
|
}
|
|
function mergeLanes(a, b) {
|
|
return a | b;
|
|
}
|
|
function removeLanes(set, subset) {
|
|
return set & ~subset;
|
|
}
|
|
function intersectLanes(a, b) {
|
|
return a & b;
|
|
} // Seems redundant, but it changes the type from a single lane (used for
|
|
// updates) to a group of lanes (used for flushing work).
|
|
|
|
function laneToLanes(lane) {
|
|
return lane;
|
|
}
|
|
function higherPriorityLane(a, b) {
|
|
// This works because the bit ranges decrease in priority as you go left.
|
|
return a !== NoLane && a < b ? a : b;
|
|
}
|
|
function createLaneMap(initial) {
|
|
// Intentionally pushing one by one.
|
|
// https://v8.dev/blog/elements-kinds#avoid-creating-holes
|
|
var laneMap = [];
|
|
|
|
for (var i = 0; i < TotalLanes; i++) {
|
|
laneMap.push(initial);
|
|
}
|
|
|
|
return laneMap;
|
|
}
|
|
function markRootUpdated(root, updateLane, eventTime) {
|
|
root.pendingLanes |= updateLane; // If there are any suspended transitions, it's possible this new update
|
|
// could unblock them. Clear the suspended lanes so that we can try rendering
|
|
// them again.
|
|
//
|
|
// TODO: We really only need to unsuspend only lanes that are in the
|
|
// `subtreeLanes` of the updated fiber, or the update lanes of the return
|
|
// path. This would exclude suspended updates in an unrelated sibling tree,
|
|
// since there's no way for this update to unblock it.
|
|
//
|
|
// We don't do this if the incoming update is idle, because we never process
|
|
// idle updates until after all the regular updates have finished; there's no
|
|
// way it could unblock a transition.
|
|
|
|
if (updateLane !== IdleLane) {
|
|
root.suspendedLanes = NoLanes;
|
|
root.pingedLanes = NoLanes;
|
|
}
|
|
|
|
var eventTimes = root.eventTimes;
|
|
var index = laneToIndex(updateLane); // We can always overwrite an existing timestamp because we prefer the most
|
|
// recent event, and we assume time is monotonically increasing.
|
|
|
|
eventTimes[index] = eventTime;
|
|
}
|
|
function markRootSuspended(root, suspendedLanes) {
|
|
root.suspendedLanes |= suspendedLanes;
|
|
root.pingedLanes &= ~suspendedLanes; // The suspended lanes are no longer CPU-bound. Clear their expiration times.
|
|
|
|
var expirationTimes = root.expirationTimes;
|
|
var lanes = suspendedLanes;
|
|
|
|
while (lanes > 0) {
|
|
var index = pickArbitraryLaneIndex(lanes);
|
|
var lane = 1 << index;
|
|
expirationTimes[index] = NoTimestamp;
|
|
lanes &= ~lane;
|
|
}
|
|
}
|
|
function markRootPinged(root, pingedLanes) {
|
|
root.pingedLanes |= root.suspendedLanes & pingedLanes;
|
|
}
|
|
function markRootMutableRead(root, updateLane) {
|
|
root.mutableReadLanes |= updateLane & root.pendingLanes;
|
|
}
|
|
function markRootFinished(root, remainingLanes) {
|
|
var noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
|
|
root.pendingLanes = remainingLanes; // Let's try everything again
|
|
|
|
root.suspendedLanes = NoLanes;
|
|
root.pingedLanes = NoLanes;
|
|
root.expiredLanes &= remainingLanes;
|
|
root.mutableReadLanes &= remainingLanes;
|
|
root.entangledLanes &= remainingLanes;
|
|
root.errorRecoveryDisabledLanes &= remainingLanes;
|
|
var entanglements = root.entanglements;
|
|
var eventTimes = root.eventTimes;
|
|
var expirationTimes = root.expirationTimes;
|
|
var hiddenUpdates = root.hiddenUpdates; // Clear the lanes that no longer have pending work
|
|
|
|
var lanes = noLongerPendingLanes;
|
|
|
|
while (lanes > 0) {
|
|
var index = pickArbitraryLaneIndex(lanes);
|
|
var lane = 1 << index;
|
|
entanglements[index] = NoLanes;
|
|
eventTimes[index] = NoTimestamp;
|
|
expirationTimes[index] = NoTimestamp;
|
|
var hiddenUpdatesForLane = hiddenUpdates[index];
|
|
|
|
if (hiddenUpdatesForLane !== null) {
|
|
hiddenUpdates[index] = null; // "Hidden" updates are updates that were made to a hidden component. They
|
|
// have special logic associated with them because they may be entangled
|
|
// with updates that occur outside that tree. But once the outer tree
|
|
// commits, they behave like regular updates.
|
|
|
|
for (var i = 0; i < hiddenUpdatesForLane.length; i++) {
|
|
var update = hiddenUpdatesForLane[i];
|
|
|
|
if (update !== null) {
|
|
update.lane &= ~OffscreenLane;
|
|
}
|
|
}
|
|
}
|
|
|
|
lanes &= ~lane;
|
|
}
|
|
}
|
|
function markRootEntangled(root, entangledLanes) {
|
|
// In addition to entangling each of the given lanes with each other, we also
|
|
// have to consider _transitive_ entanglements. For each lane that is already
|
|
// entangled with *any* of the given lanes, that lane is now transitively
|
|
// entangled with *all* the given lanes.
|
|
//
|
|
// Translated: If C is entangled with A, then entangling A with B also
|
|
// entangles C with B.
|
|
//
|
|
// If this is hard to grasp, it might help to intentionally break this
|
|
// function and look at the tests that fail in ReactTransition-test.js. Try
|
|
// commenting out one of the conditions below.
|
|
var rootEntangledLanes = (root.entangledLanes |= entangledLanes);
|
|
var entanglements = root.entanglements;
|
|
var lanes = rootEntangledLanes;
|
|
|
|
while (lanes) {
|
|
var index = pickArbitraryLaneIndex(lanes);
|
|
var lane = 1 << index;
|
|
|
|
if (
|
|
// Is this one of the newly entangled lanes?
|
|
(lane & entangledLanes) | // Is this lane transitively entangled with the newly entangled lanes?
|
|
(entanglements[index] & entangledLanes)
|
|
) {
|
|
entanglements[index] |= entangledLanes;
|
|
}
|
|
|
|
lanes &= ~lane;
|
|
}
|
|
}
|
|
function markHiddenUpdate(root, update, lane) {
|
|
var index = laneToIndex(lane);
|
|
var hiddenUpdates = root.hiddenUpdates;
|
|
var hiddenUpdatesForLane = hiddenUpdates[index];
|
|
|
|
if (hiddenUpdatesForLane === null) {
|
|
hiddenUpdates[index] = [update];
|
|
} else {
|
|
hiddenUpdatesForLane.push(update);
|
|
}
|
|
|
|
update.lane = lane | OffscreenLane;
|
|
}
|
|
function getBumpedLaneForHydration(root, renderLanes) {
|
|
var renderLane = getHighestPriorityLane(renderLanes);
|
|
var lane;
|
|
|
|
if ((renderLane & SyncUpdateLanes) !== NoLane) {
|
|
lane = SyncHydrationLane;
|
|
} else {
|
|
switch (renderLane) {
|
|
case SyncLane:
|
|
lane = SyncHydrationLane;
|
|
break;
|
|
|
|
case InputContinuousLane:
|
|
lane = InputContinuousHydrationLane;
|
|
break;
|
|
|
|
case DefaultLane:
|
|
lane = DefaultHydrationLane;
|
|
break;
|
|
|
|
case TransitionLane1:
|
|
case TransitionLane2:
|
|
case TransitionLane3:
|
|
case TransitionLane4:
|
|
case TransitionLane5:
|
|
case TransitionLane6:
|
|
case TransitionLane7:
|
|
case TransitionLane8:
|
|
case TransitionLane9:
|
|
case TransitionLane10:
|
|
case TransitionLane11:
|
|
case TransitionLane12:
|
|
case TransitionLane13:
|
|
case TransitionLane14:
|
|
case TransitionLane15:
|
|
case TransitionLane16:
|
|
case RetryLane1:
|
|
case RetryLane2:
|
|
case RetryLane3:
|
|
case RetryLane4:
|
|
lane = TransitionHydrationLane;
|
|
break;
|
|
|
|
case IdleLane:
|
|
lane = IdleHydrationLane;
|
|
break;
|
|
|
|
default:
|
|
// Everything else is already either a hydration lane, or shouldn't
|
|
// be retried at a hydration lane.
|
|
lane = NoLane;
|
|
break;
|
|
}
|
|
} // Check if the lane we chose is suspended. If so, that indicates that we
|
|
// already attempted and failed to hydrate at that level. Also check if we're
|
|
// already rendering that lane, which is rare but could happen.
|
|
|
|
if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) {
|
|
// Give up trying to hydrate and fall back to client render.
|
|
return NoLane;
|
|
}
|
|
|
|
return lane;
|
|
}
|
|
function getTransitionsForLanes(root, lanes) {
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
var DiscreteEventPriority = SyncLane;
|
|
var ContinuousEventPriority = InputContinuousLane;
|
|
var DefaultEventPriority = DefaultLane;
|
|
var IdleEventPriority = IdleLane;
|
|
var currentUpdatePriority = NoLane;
|
|
function getCurrentUpdatePriority() {
|
|
return currentUpdatePriority;
|
|
}
|
|
function setCurrentUpdatePriority(newPriority) {
|
|
currentUpdatePriority = newPriority;
|
|
}
|
|
function runWithPriority(priority, fn) {
|
|
var previousPriority = currentUpdatePriority;
|
|
|
|
try {
|
|
currentUpdatePriority = priority;
|
|
return fn();
|
|
} finally {
|
|
currentUpdatePriority = previousPriority;
|
|
}
|
|
}
|
|
function higherEventPriority(a, b) {
|
|
return a !== 0 && a < b ? a : b;
|
|
}
|
|
function lowerEventPriority(a, b) {
|
|
return a === 0 || a > b ? a : b;
|
|
}
|
|
function isHigherEventPriority(a, b) {
|
|
return a !== 0 && a < b;
|
|
}
|
|
function lanesToEventPriority(lanes) {
|
|
var lane = getHighestPriorityLane(lanes);
|
|
|
|
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
|
|
return DiscreteEventPriority;
|
|
}
|
|
|
|
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
|
|
return ContinuousEventPriority;
|
|
}
|
|
|
|
if (includesNonIdleWork(lane)) {
|
|
return DefaultEventPriority;
|
|
}
|
|
|
|
return IdleEventPriority;
|
|
}
|
|
|
|
// This is imported by the event replaying implementation in React DOM. It's
|
|
// in a separate file to break a circular dependency between the renderer and
|
|
// the reconciler.
|
|
function isRootDehydrated(root) {
|
|
var currentState = root.current.memoizedState;
|
|
return currentState.isDehydrated;
|
|
}
|
|
|
|
var _attemptSynchronousHydration;
|
|
|
|
function setAttemptSynchronousHydration(fn) {
|
|
_attemptSynchronousHydration = fn;
|
|
}
|
|
function attemptSynchronousHydration(fiber) {
|
|
_attemptSynchronousHydration(fiber);
|
|
}
|
|
var attemptContinuousHydration;
|
|
function setAttemptContinuousHydration(fn) {
|
|
attemptContinuousHydration = fn;
|
|
}
|
|
var attemptHydrationAtCurrentPriority;
|
|
function setAttemptHydrationAtCurrentPriority(fn) {
|
|
attemptHydrationAtCurrentPriority = fn;
|
|
}
|
|
var getCurrentUpdatePriority$1;
|
|
function setGetCurrentUpdatePriority(fn) {
|
|
getCurrentUpdatePriority$1 = fn;
|
|
}
|
|
var attemptHydrationAtPriority;
|
|
function setAttemptHydrationAtPriority(fn) {
|
|
attemptHydrationAtPriority = fn;
|
|
} // TODO: Upgrade this definition once we're on a newer version of Flow that
|
|
// has this definition built-in.
|
|
|
|
var hasScheduledReplayAttempt = false; // The queue of discrete events to be replayed.
|
|
|
|
var queuedDiscreteEvents = []; // Indicates if any continuous event targets are non-null for early bailout.
|
|
// if the last target was dehydrated.
|
|
|
|
var queuedFocus = null;
|
|
var queuedDrag = null;
|
|
var queuedMouse = null; // For pointer events there can be one latest event per pointerId.
|
|
|
|
var queuedPointers = new Map();
|
|
var queuedPointerCaptures = new Map(); // We could consider replaying selectionchange and touchmoves too.
|
|
|
|
var queuedExplicitHydrationTargets = [];
|
|
var discreteReplayableEvents = [
|
|
"mousedown",
|
|
"mouseup",
|
|
"touchcancel",
|
|
"touchend",
|
|
"touchstart",
|
|
"auxclick",
|
|
"dblclick",
|
|
"pointercancel",
|
|
"pointerdown",
|
|
"pointerup",
|
|
"dragend",
|
|
"dragstart",
|
|
"drop",
|
|
"compositionend",
|
|
"compositionstart",
|
|
"keydown",
|
|
"keypress",
|
|
"keyup",
|
|
"input",
|
|
"textInput", // Intentionally camelCase
|
|
"copy",
|
|
"cut",
|
|
"paste",
|
|
"click",
|
|
"change",
|
|
"contextmenu",
|
|
"reset",
|
|
"submit"
|
|
];
|
|
function isDiscreteEventThatRequiresHydration(eventType) {
|
|
return discreteReplayableEvents.indexOf(eventType) > -1;
|
|
}
|
|
|
|
function createQueuedReplayableEvent(
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
) {
|
|
return {
|
|
blockedOn: blockedOn,
|
|
domEventName: domEventName,
|
|
eventSystemFlags: eventSystemFlags,
|
|
nativeEvent: nativeEvent,
|
|
targetContainers: [targetContainer]
|
|
};
|
|
}
|
|
|
|
function clearIfContinuousEvent(domEventName, nativeEvent) {
|
|
switch (domEventName) {
|
|
case "focusin":
|
|
case "focusout":
|
|
queuedFocus = null;
|
|
break;
|
|
|
|
case "dragenter":
|
|
case "dragleave":
|
|
queuedDrag = null;
|
|
break;
|
|
|
|
case "mouseover":
|
|
case "mouseout":
|
|
queuedMouse = null;
|
|
break;
|
|
|
|
case "pointerover":
|
|
case "pointerout": {
|
|
var pointerId = nativeEvent.pointerId;
|
|
queuedPointers.delete(pointerId);
|
|
break;
|
|
}
|
|
|
|
case "gotpointercapture":
|
|
case "lostpointercapture": {
|
|
var _pointerId = nativeEvent.pointerId;
|
|
queuedPointerCaptures.delete(_pointerId);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function accumulateOrCreateContinuousQueuedReplayableEvent(
|
|
existingQueuedEvent,
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
) {
|
|
if (
|
|
existingQueuedEvent === null ||
|
|
existingQueuedEvent.nativeEvent !== nativeEvent
|
|
) {
|
|
var queuedEvent = createQueuedReplayableEvent(
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
);
|
|
|
|
if (blockedOn !== null) {
|
|
var fiber = getInstanceFromNode$1(blockedOn);
|
|
|
|
if (fiber !== null) {
|
|
// Attempt to increase the priority of this target.
|
|
attemptContinuousHydration(fiber);
|
|
}
|
|
}
|
|
|
|
return queuedEvent;
|
|
} // If we have already queued this exact event, then it's because
|
|
// the different event systems have different DOM event listeners.
|
|
// We can accumulate the flags, and the targetContainers, and
|
|
// store a single event to be replayed.
|
|
|
|
existingQueuedEvent.eventSystemFlags |= eventSystemFlags;
|
|
var targetContainers = existingQueuedEvent.targetContainers;
|
|
|
|
if (
|
|
targetContainer !== null &&
|
|
targetContainers.indexOf(targetContainer) === -1
|
|
) {
|
|
targetContainers.push(targetContainer);
|
|
}
|
|
|
|
return existingQueuedEvent;
|
|
}
|
|
|
|
function queueIfContinuousEvent(
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
) {
|
|
// These set relatedTarget to null because the replayed event will be treated as if we
|
|
// moved from outside the window (no target) onto the target once it hydrates.
|
|
// Instead of mutating we could clone the event.
|
|
switch (domEventName) {
|
|
case "focusin": {
|
|
var focusEvent = nativeEvent;
|
|
queuedFocus = accumulateOrCreateContinuousQueuedReplayableEvent(
|
|
queuedFocus,
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
focusEvent
|
|
);
|
|
return true;
|
|
}
|
|
|
|
case "dragenter": {
|
|
var dragEvent = nativeEvent;
|
|
queuedDrag = accumulateOrCreateContinuousQueuedReplayableEvent(
|
|
queuedDrag,
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
dragEvent
|
|
);
|
|
return true;
|
|
}
|
|
|
|
case "mouseover": {
|
|
var mouseEvent = nativeEvent;
|
|
queuedMouse = accumulateOrCreateContinuousQueuedReplayableEvent(
|
|
queuedMouse,
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
mouseEvent
|
|
);
|
|
return true;
|
|
}
|
|
|
|
case "pointerover": {
|
|
var pointerEvent = nativeEvent;
|
|
var pointerId = pointerEvent.pointerId;
|
|
queuedPointers.set(
|
|
pointerId,
|
|
accumulateOrCreateContinuousQueuedReplayableEvent(
|
|
queuedPointers.get(pointerId) || null,
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
pointerEvent
|
|
)
|
|
);
|
|
return true;
|
|
}
|
|
|
|
case "gotpointercapture": {
|
|
var _pointerEvent = nativeEvent;
|
|
var _pointerId2 = _pointerEvent.pointerId;
|
|
queuedPointerCaptures.set(
|
|
_pointerId2,
|
|
accumulateOrCreateContinuousQueuedReplayableEvent(
|
|
queuedPointerCaptures.get(_pointerId2) || null,
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
_pointerEvent
|
|
)
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} // Check if this target is unblocked. Returns true if it's unblocked.
|
|
|
|
function attemptExplicitHydrationTarget(queuedTarget) {
|
|
// TODO: This function shares a lot of logic with findInstanceBlockingEvent.
|
|
// Try to unify them. It's a bit tricky since it would require two return
|
|
// values.
|
|
var targetInst = getClosestInstanceFromNode(queuedTarget.target);
|
|
|
|
if (targetInst !== null) {
|
|
var nearestMounted = getNearestMountedFiber(targetInst);
|
|
|
|
if (nearestMounted !== null) {
|
|
var tag = nearestMounted.tag;
|
|
|
|
if (tag === SuspenseComponent) {
|
|
var instance = getSuspenseInstanceFromFiber(nearestMounted);
|
|
|
|
if (instance !== null) {
|
|
// We're blocked on hydrating this boundary.
|
|
// Increase its priority.
|
|
queuedTarget.blockedOn = instance;
|
|
attemptHydrationAtPriority(queuedTarget.priority, function() {
|
|
attemptHydrationAtCurrentPriority(nearestMounted);
|
|
});
|
|
return;
|
|
}
|
|
} else if (tag === HostRoot) {
|
|
var root = nearestMounted.stateNode;
|
|
|
|
if (isRootDehydrated(root)) {
|
|
queuedTarget.blockedOn = getContainerFromFiber(nearestMounted); // We don't currently have a way to increase the priority of
|
|
// a root other than sync.
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
queuedTarget.blockedOn = null;
|
|
}
|
|
|
|
function queueExplicitHydrationTarget(target) {
|
|
// TODO: This will read the priority if it's dispatched by the React
|
|
// event system but not native events. Should read window.event.type, like
|
|
// we do for updates (getCurrentEventPriority).
|
|
var updatePriority = getCurrentUpdatePriority$1();
|
|
var queuedTarget = {
|
|
blockedOn: null,
|
|
target: target,
|
|
priority: updatePriority
|
|
};
|
|
var i = 0;
|
|
|
|
for (; i < queuedExplicitHydrationTargets.length; i++) {
|
|
// Stop once we hit the first target with lower priority than
|
|
if (
|
|
!isHigherEventPriority(
|
|
updatePriority,
|
|
queuedExplicitHydrationTargets[i].priority
|
|
)
|
|
) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
queuedExplicitHydrationTargets.splice(i, 0, queuedTarget);
|
|
|
|
if (i === 0) {
|
|
attemptExplicitHydrationTarget(queuedTarget);
|
|
}
|
|
}
|
|
|
|
function attemptReplayContinuousQueuedEvent(queuedEvent) {
|
|
if (queuedEvent.blockedOn !== null) {
|
|
return false;
|
|
}
|
|
|
|
var targetContainers = queuedEvent.targetContainers;
|
|
|
|
while (targetContainers.length > 0) {
|
|
var targetContainer = targetContainers[0];
|
|
var nextBlockedOn = findInstanceBlockingEvent(
|
|
queuedEvent.domEventName,
|
|
queuedEvent.eventSystemFlags,
|
|
targetContainer,
|
|
queuedEvent.nativeEvent
|
|
);
|
|
|
|
if (nextBlockedOn === null) {
|
|
{
|
|
var nativeEvent = queuedEvent.nativeEvent;
|
|
var nativeEventClone = new nativeEvent.constructor(
|
|
nativeEvent.type,
|
|
nativeEvent
|
|
);
|
|
setReplayingEvent(nativeEventClone);
|
|
nativeEvent.target.dispatchEvent(nativeEventClone);
|
|
resetReplayingEvent();
|
|
}
|
|
} else {
|
|
// We're still blocked. Try again later.
|
|
var fiber = getInstanceFromNode$1(nextBlockedOn);
|
|
|
|
if (fiber !== null) {
|
|
attemptContinuousHydration(fiber);
|
|
}
|
|
|
|
queuedEvent.blockedOn = nextBlockedOn;
|
|
return false;
|
|
} // This target container was successfully dispatched. Try the next.
|
|
|
|
targetContainers.shift();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function attemptReplayContinuousQueuedEventInMap(queuedEvent, key, map) {
|
|
if (attemptReplayContinuousQueuedEvent(queuedEvent)) {
|
|
map.delete(key);
|
|
}
|
|
}
|
|
|
|
function replayUnblockedEvents() {
|
|
hasScheduledReplayAttempt = false;
|
|
|
|
if (queuedFocus !== null && attemptReplayContinuousQueuedEvent(queuedFocus)) {
|
|
queuedFocus = null;
|
|
}
|
|
|
|
if (queuedDrag !== null && attemptReplayContinuousQueuedEvent(queuedDrag)) {
|
|
queuedDrag = null;
|
|
}
|
|
|
|
if (queuedMouse !== null && attemptReplayContinuousQueuedEvent(queuedMouse)) {
|
|
queuedMouse = null;
|
|
}
|
|
|
|
queuedPointers.forEach(attemptReplayContinuousQueuedEventInMap);
|
|
queuedPointerCaptures.forEach(attemptReplayContinuousQueuedEventInMap);
|
|
}
|
|
|
|
function scheduleCallbackIfUnblocked(queuedEvent, unblocked) {
|
|
if (queuedEvent.blockedOn === unblocked) {
|
|
queuedEvent.blockedOn = null;
|
|
|
|
if (!hasScheduledReplayAttempt) {
|
|
hasScheduledReplayAttempt = true; // Schedule a callback to attempt replaying as many events as are
|
|
// now unblocked. This first might not actually be unblocked yet.
|
|
// We could check it early to avoid scheduling an unnecessary callback.
|
|
|
|
Scheduler.unstable_scheduleCallback(
|
|
Scheduler.unstable_NormalPriority,
|
|
replayUnblockedEvents
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function retryIfBlockedOn(unblocked) {
|
|
// Mark anything that was blocked on this as no longer blocked
|
|
// and eligible for a replay.
|
|
if (queuedDiscreteEvents.length > 0) {
|
|
scheduleCallbackIfUnblocked(queuedDiscreteEvents[0], unblocked); // This is a exponential search for each boundary that commits. I think it's
|
|
// worth it because we expect very few discrete events to queue up and once
|
|
// we are actually fully unblocked it will be fast to replay them.
|
|
|
|
for (var i = 1; i < queuedDiscreteEvents.length; i++) {
|
|
var queuedEvent = queuedDiscreteEvents[i];
|
|
|
|
if (queuedEvent.blockedOn === unblocked) {
|
|
queuedEvent.blockedOn = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (queuedFocus !== null) {
|
|
scheduleCallbackIfUnblocked(queuedFocus, unblocked);
|
|
}
|
|
|
|
if (queuedDrag !== null) {
|
|
scheduleCallbackIfUnblocked(queuedDrag, unblocked);
|
|
}
|
|
|
|
if (queuedMouse !== null) {
|
|
scheduleCallbackIfUnblocked(queuedMouse, unblocked);
|
|
}
|
|
|
|
var unblock = function(queuedEvent) {
|
|
return scheduleCallbackIfUnblocked(queuedEvent, unblocked);
|
|
};
|
|
|
|
queuedPointers.forEach(unblock);
|
|
queuedPointerCaptures.forEach(unblock);
|
|
|
|
for (var _i = 0; _i < queuedExplicitHydrationTargets.length; _i++) {
|
|
var queuedTarget = queuedExplicitHydrationTargets[_i];
|
|
|
|
if (queuedTarget.blockedOn === unblocked) {
|
|
queuedTarget.blockedOn = null;
|
|
}
|
|
}
|
|
|
|
while (queuedExplicitHydrationTargets.length > 0) {
|
|
var nextExplicitTarget = queuedExplicitHydrationTargets[0];
|
|
|
|
if (nextExplicitTarget.blockedOn !== null) {
|
|
// We're still blocked.
|
|
break;
|
|
} else {
|
|
attemptExplicitHydrationTarget(nextExplicitTarget);
|
|
|
|
if (nextExplicitTarget.blockedOn === null) {
|
|
// We're unblocked.
|
|
queuedExplicitHydrationTargets.shift();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig; // TODO: can we stop exporting these?
|
|
|
|
var _enabled = true; // This is exported in FB builds for use by legacy FB layer infra.
|
|
// We'd like to remove this but it's not clear if this is safe.
|
|
|
|
function setEnabled(enabled) {
|
|
_enabled = !!enabled;
|
|
}
|
|
function isEnabled() {
|
|
return _enabled;
|
|
}
|
|
function createEventListenerWrapperWithPriority(
|
|
targetContainer,
|
|
domEventName,
|
|
eventSystemFlags
|
|
) {
|
|
var eventPriority = getEventPriority(domEventName);
|
|
var listenerWrapper;
|
|
|
|
switch (eventPriority) {
|
|
case DiscreteEventPriority:
|
|
listenerWrapper = dispatchDiscreteEvent;
|
|
break;
|
|
|
|
case ContinuousEventPriority:
|
|
listenerWrapper = dispatchContinuousEvent;
|
|
break;
|
|
|
|
case DefaultEventPriority:
|
|
default:
|
|
listenerWrapper = dispatchEvent;
|
|
break;
|
|
}
|
|
|
|
return listenerWrapper.bind(
|
|
null,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
);
|
|
}
|
|
|
|
function dispatchDiscreteEvent(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
container,
|
|
nativeEvent
|
|
) {
|
|
var previousPriority = getCurrentUpdatePriority();
|
|
var prevTransition = ReactCurrentBatchConfig.transition;
|
|
ReactCurrentBatchConfig.transition = null;
|
|
|
|
try {
|
|
setCurrentUpdatePriority(DiscreteEventPriority);
|
|
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
|
|
} finally {
|
|
setCurrentUpdatePriority(previousPriority);
|
|
ReactCurrentBatchConfig.transition = prevTransition;
|
|
}
|
|
}
|
|
|
|
function dispatchContinuousEvent(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
container,
|
|
nativeEvent
|
|
) {
|
|
var previousPriority = getCurrentUpdatePriority();
|
|
var prevTransition = ReactCurrentBatchConfig.transition;
|
|
ReactCurrentBatchConfig.transition = null;
|
|
|
|
try {
|
|
setCurrentUpdatePriority(ContinuousEventPriority);
|
|
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
|
|
} finally {
|
|
setCurrentUpdatePriority(previousPriority);
|
|
ReactCurrentBatchConfig.transition = prevTransition;
|
|
}
|
|
}
|
|
|
|
function dispatchEvent(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
) {
|
|
if (!_enabled) {
|
|
return;
|
|
}
|
|
|
|
{
|
|
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
);
|
|
}
|
|
}
|
|
|
|
function dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
) {
|
|
var blockedOn = findInstanceBlockingEvent(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
);
|
|
|
|
if (blockedOn === null) {
|
|
dispatchEventForPluginEventSystem(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
nativeEvent,
|
|
return_targetInst,
|
|
targetContainer
|
|
);
|
|
clearIfContinuousEvent(domEventName, nativeEvent);
|
|
return;
|
|
}
|
|
|
|
if (
|
|
queueIfContinuousEvent(
|
|
blockedOn,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
)
|
|
) {
|
|
nativeEvent.stopPropagation();
|
|
return;
|
|
} // We need to clear only if we didn't queue because
|
|
// queueing is accumulative.
|
|
|
|
clearIfContinuousEvent(domEventName, nativeEvent);
|
|
|
|
if (
|
|
eventSystemFlags & IS_CAPTURE_PHASE &&
|
|
isDiscreteEventThatRequiresHydration(domEventName)
|
|
) {
|
|
while (blockedOn !== null) {
|
|
var fiber = getInstanceFromNode$1(blockedOn);
|
|
|
|
if (fiber !== null) {
|
|
attemptSynchronousHydration(fiber);
|
|
}
|
|
|
|
var nextBlockedOn = findInstanceBlockingEvent(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
);
|
|
|
|
if (nextBlockedOn === null) {
|
|
dispatchEventForPluginEventSystem(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
nativeEvent,
|
|
return_targetInst,
|
|
targetContainer
|
|
);
|
|
}
|
|
|
|
if (nextBlockedOn === blockedOn) {
|
|
break;
|
|
}
|
|
|
|
blockedOn = nextBlockedOn;
|
|
}
|
|
|
|
if (blockedOn !== null) {
|
|
nativeEvent.stopPropagation();
|
|
}
|
|
|
|
return;
|
|
} // This is not replayable so we'll invoke it but without a target,
|
|
// in case the event system needs to trace it.
|
|
|
|
dispatchEventForPluginEventSystem(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
nativeEvent,
|
|
null,
|
|
targetContainer
|
|
);
|
|
}
|
|
|
|
var return_targetInst = null; // Returns a SuspenseInstance or Container if it's blocked.
|
|
// The return_targetInst field above is conceptually part of the return value.
|
|
|
|
function findInstanceBlockingEvent(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
targetContainer,
|
|
nativeEvent
|
|
) {
|
|
// TODO: Warn if _enabled is false.
|
|
return_targetInst = null;
|
|
var nativeEventTarget = getEventTarget(nativeEvent);
|
|
var targetInst = getClosestInstanceFromNode(nativeEventTarget);
|
|
|
|
if (targetInst !== null) {
|
|
var nearestMounted = getNearestMountedFiber(targetInst);
|
|
|
|
if (nearestMounted === null) {
|
|
// This tree has been unmounted already. Dispatch without a target.
|
|
targetInst = null;
|
|
} else {
|
|
var tag = nearestMounted.tag;
|
|
|
|
if (tag === SuspenseComponent) {
|
|
var instance = getSuspenseInstanceFromFiber(nearestMounted);
|
|
|
|
if (instance !== null) {
|
|
// Queue the event to be replayed later. Abort dispatching since we
|
|
// don't want this event dispatched twice through the event system.
|
|
// TODO: If this is the first discrete event in the queue. Schedule an increased
|
|
// priority for this boundary.
|
|
return instance;
|
|
} // This shouldn't happen, something went wrong but to avoid blocking
|
|
// the whole system, dispatch the event without a target.
|
|
// TODO: Warn.
|
|
|
|
targetInst = null;
|
|
} else if (tag === HostRoot) {
|
|
var root = nearestMounted.stateNode;
|
|
|
|
if (isRootDehydrated(root)) {
|
|
// If this happens during a replay something went wrong and it might block
|
|
// the whole system.
|
|
return getContainerFromFiber(nearestMounted);
|
|
}
|
|
|
|
targetInst = null;
|
|
} else if (nearestMounted !== targetInst) {
|
|
// If we get an event (ex: img onload) before committing that
|
|
// component's mount, ignore it for now (that is, treat it as if it was an
|
|
// event on a non-React tree). We might also consider queueing events and
|
|
// dispatching them after the mount.
|
|
targetInst = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return_targetInst = targetInst; // We're not blocked on anything.
|
|
|
|
return null;
|
|
}
|
|
function getEventPriority(domEventName) {
|
|
switch (domEventName) {
|
|
// Used by SimpleEventPlugin:
|
|
case "cancel":
|
|
case "click":
|
|
case "close":
|
|
case "contextmenu":
|
|
case "copy":
|
|
case "cut":
|
|
case "auxclick":
|
|
case "dblclick":
|
|
case "dragend":
|
|
case "dragstart":
|
|
case "drop":
|
|
case "focusin":
|
|
case "focusout":
|
|
case "input":
|
|
case "invalid":
|
|
case "keydown":
|
|
case "keypress":
|
|
case "keyup":
|
|
case "mousedown":
|
|
case "mouseup":
|
|
case "paste":
|
|
case "pause":
|
|
case "play":
|
|
case "pointercancel":
|
|
case "pointerdown":
|
|
case "pointerup":
|
|
case "ratechange":
|
|
case "reset":
|
|
case "resize":
|
|
case "seeked":
|
|
case "submit":
|
|
case "touchcancel":
|
|
case "touchend":
|
|
case "touchstart":
|
|
case "volumechange": // Used by polyfills:
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case "change":
|
|
case "selectionchange":
|
|
case "textInput":
|
|
case "compositionstart":
|
|
case "compositionend":
|
|
case "compositionupdate": // Only enableCreateEventHandleAPI:
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case "beforeblur":
|
|
case "afterblur": // Not used by React but could be by user code:
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case "beforeinput":
|
|
case "blur":
|
|
case "fullscreenchange":
|
|
case "focus":
|
|
case "hashchange":
|
|
case "popstate":
|
|
case "select":
|
|
case "selectstart":
|
|
return DiscreteEventPriority;
|
|
|
|
case "drag":
|
|
case "dragenter":
|
|
case "dragexit":
|
|
case "dragleave":
|
|
case "dragover":
|
|
case "mousemove":
|
|
case "mouseout":
|
|
case "mouseover":
|
|
case "pointermove":
|
|
case "pointerout":
|
|
case "pointerover":
|
|
case "scroll":
|
|
case "toggle":
|
|
case "touchmove":
|
|
case "wheel": // Not used by React but could be by user code:
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case "mouseenter":
|
|
case "mouseleave":
|
|
case "pointerenter":
|
|
case "pointerleave":
|
|
return ContinuousEventPriority;
|
|
|
|
case "message": {
|
|
// We might be in the Scheduler callback.
|
|
// Eventually this mechanism will be replaced by a check
|
|
// of the current priority on the native scheduler.
|
|
var schedulerPriority = getCurrentPriorityLevel();
|
|
|
|
switch (schedulerPriority) {
|
|
case ImmediatePriority:
|
|
return DiscreteEventPriority;
|
|
|
|
case UserBlockingPriority:
|
|
return ContinuousEventPriority;
|
|
|
|
case NormalPriority:
|
|
case LowPriority:
|
|
// TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
|
|
return DefaultEventPriority;
|
|
|
|
case IdlePriority:
|
|
return IdleEventPriority;
|
|
|
|
default:
|
|
return DefaultEventPriority;
|
|
}
|
|
}
|
|
|
|
default:
|
|
return DefaultEventPriority;
|
|
}
|
|
}
|
|
|
|
function addEventBubbleListener(target, eventType, listener) {
|
|
target.addEventListener(eventType, listener, false);
|
|
return listener;
|
|
}
|
|
function addEventCaptureListener(target, eventType, listener) {
|
|
target.addEventListener(eventType, listener, true);
|
|
return listener;
|
|
}
|
|
function addEventCaptureListenerWithPassiveFlag(
|
|
target,
|
|
eventType,
|
|
listener,
|
|
passive
|
|
) {
|
|
target.addEventListener(eventType, listener, {
|
|
capture: true,
|
|
passive: passive
|
|
});
|
|
return listener;
|
|
}
|
|
function addEventBubbleListenerWithPassiveFlag(
|
|
target,
|
|
eventType,
|
|
listener,
|
|
passive
|
|
) {
|
|
target.addEventListener(eventType, listener, {
|
|
passive: passive
|
|
});
|
|
return listener;
|
|
}
|
|
|
|
/**
|
|
* These variables store information about text content of a target node,
|
|
* allowing comparison of content before and after a given event.
|
|
*
|
|
* Identify the node where selection currently begins, then observe
|
|
* both its text content and its current position in the DOM. Since the
|
|
* browser may natively replace the target node during composition, we can
|
|
* use its position to find its replacement.
|
|
*
|
|
*
|
|
*/
|
|
var root = null;
|
|
var startText = null;
|
|
var fallbackText = null;
|
|
function initialize(nativeEventTarget) {
|
|
root = nativeEventTarget;
|
|
startText = getText();
|
|
return true;
|
|
}
|
|
function reset() {
|
|
root = null;
|
|
startText = null;
|
|
fallbackText = null;
|
|
}
|
|
function getData() {
|
|
if (fallbackText) {
|
|
return fallbackText;
|
|
}
|
|
|
|
var start;
|
|
var startValue = startText;
|
|
var startLength = startValue.length;
|
|
var end;
|
|
var endValue = getText();
|
|
var endLength = endValue.length;
|
|
|
|
for (start = 0; start < startLength; start++) {
|
|
if (startValue[start] !== endValue[start]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
var minEnd = startLength - start;
|
|
|
|
for (end = 1; end <= minEnd; end++) {
|
|
if (startValue[startLength - end] !== endValue[endLength - end]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
var sliceTail = end > 1 ? 1 - end : undefined;
|
|
fallbackText = endValue.slice(start, sliceTail);
|
|
return fallbackText;
|
|
}
|
|
function getText() {
|
|
if ("value" in root) {
|
|
return root.value;
|
|
}
|
|
|
|
return root.textContent;
|
|
}
|
|
|
|
/**
|
|
* `charCode` represents the actual "character code" and is safe to use with
|
|
* `String.fromCharCode`. As such, only keys that correspond to printable
|
|
* characters produce a valid `charCode`, the only exception to this is Enter.
|
|
* The Tab-key is considered non-printable and does not have a `charCode`,
|
|
* presumably because it does not produce a tab-character in browsers.
|
|
*
|
|
* @param {object} nativeEvent Native browser event.
|
|
* @return {number} Normalized `charCode` property.
|
|
*/
|
|
function getEventCharCode(nativeEvent) {
|
|
var charCode;
|
|
var keyCode = nativeEvent.keyCode;
|
|
|
|
if ("charCode" in nativeEvent) {
|
|
charCode = nativeEvent.charCode; // FF does not set `charCode` for the Enter-key, check against `keyCode`.
|
|
|
|
if (charCode === 0 && keyCode === 13) {
|
|
charCode = 13;
|
|
}
|
|
} else {
|
|
// IE8 does not implement `charCode`, but `keyCode` has the correct value.
|
|
charCode = keyCode;
|
|
} // IE and Edge (on Windows) and Chrome / Safari (on Windows and Linux)
|
|
// report Enter as charCode 10 when ctrl is pressed.
|
|
|
|
if (charCode === 10) {
|
|
charCode = 13;
|
|
} // Some non-printable keys are reported in `charCode`/`keyCode`, discard them.
|
|
// Must not discard the (non-)printable Enter-key.
|
|
|
|
if (charCode >= 32 || charCode === 13) {
|
|
return charCode;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function functionThatReturnsTrue() {
|
|
return true;
|
|
}
|
|
|
|
function functionThatReturnsFalse() {
|
|
return false;
|
|
} // This is intentionally a factory so that we have different returned constructors.
|
|
// If we had a single constructor, it would be megamorphic and engines would deopt.
|
|
|
|
function createSyntheticEvent(Interface) {
|
|
/**
|
|
* Synthetic events are dispatched by event plugins, typically in response to a
|
|
* top-level event delegation handler.
|
|
*
|
|
* These systems should generally use pooling to reduce the frequency of garbage
|
|
* collection. The system should check `isPersistent` to determine whether the
|
|
* event should be released into the pool after being dispatched. Users that
|
|
* need a persisted event should invoke `persist`.
|
|
*
|
|
* Synthetic events (and subclasses) implement the DOM Level 3 Events API by
|
|
* normalizing browser quirks. Subclasses do not necessarily have to implement a
|
|
* DOM interface; custom application-specific events can also subclass this.
|
|
*/
|
|
// $FlowFixMe[missing-this-annot]
|
|
function SyntheticBaseEvent(
|
|
reactName,
|
|
reactEventType,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
) {
|
|
this._reactName = reactName;
|
|
this._targetInst = targetInst;
|
|
this.type = reactEventType;
|
|
this.nativeEvent = nativeEvent;
|
|
this.target = nativeEventTarget;
|
|
this.currentTarget = null;
|
|
|
|
for (var propName in Interface) {
|
|
if (!Interface.hasOwnProperty(propName)) {
|
|
continue;
|
|
}
|
|
|
|
var normalize = Interface[propName];
|
|
|
|
if (normalize) {
|
|
this[propName] = normalize(nativeEvent);
|
|
} else {
|
|
this[propName] = nativeEvent[propName];
|
|
}
|
|
}
|
|
|
|
var defaultPrevented =
|
|
nativeEvent.defaultPrevented != null
|
|
? nativeEvent.defaultPrevented
|
|
: nativeEvent.returnValue === false;
|
|
|
|
if (defaultPrevented) {
|
|
this.isDefaultPrevented = functionThatReturnsTrue;
|
|
} else {
|
|
this.isDefaultPrevented = functionThatReturnsFalse;
|
|
}
|
|
|
|
this.isPropagationStopped = functionThatReturnsFalse;
|
|
return this;
|
|
} // $FlowFixMe[prop-missing] found when upgrading Flow
|
|
|
|
assign(SyntheticBaseEvent.prototype, {
|
|
// $FlowFixMe[missing-this-annot]
|
|
preventDefault: function() {
|
|
this.defaultPrevented = true;
|
|
var event = this.nativeEvent;
|
|
|
|
if (!event) {
|
|
return;
|
|
}
|
|
|
|
if (event.preventDefault) {
|
|
event.preventDefault(); // $FlowFixMe - flow is not aware of `unknown` in IE
|
|
} else if (typeof event.returnValue !== "unknown") {
|
|
event.returnValue = false;
|
|
}
|
|
|
|
this.isDefaultPrevented = functionThatReturnsTrue;
|
|
},
|
|
// $FlowFixMe[missing-this-annot]
|
|
stopPropagation: function() {
|
|
var event = this.nativeEvent;
|
|
|
|
if (!event) {
|
|
return;
|
|
}
|
|
|
|
if (event.stopPropagation) {
|
|
event.stopPropagation(); // $FlowFixMe - flow is not aware of `unknown` in IE
|
|
} else if (typeof event.cancelBubble !== "unknown") {
|
|
// The ChangeEventPlugin registers a "propertychange" event for
|
|
// IE. This event does not support bubbling or cancelling, and
|
|
// any references to cancelBubble throw "Member not found". A
|
|
// typeof check of "unknown" circumvents this issue (and is also
|
|
// IE specific).
|
|
event.cancelBubble = true;
|
|
}
|
|
|
|
this.isPropagationStopped = functionThatReturnsTrue;
|
|
},
|
|
|
|
/**
|
|
* We release all dispatched `SyntheticEvent`s after each event loop, adding
|
|
* them back into the pool. This allows a way to hold onto a reference that
|
|
* won't be added back into the pool.
|
|
*/
|
|
persist: function() {
|
|
// Modern event system doesn't use pooling.
|
|
},
|
|
|
|
/**
|
|
* Checks if this event should be released back into the pool.
|
|
*
|
|
* @return {boolean} True if this should not be released, false otherwise.
|
|
*/
|
|
isPersistent: functionThatReturnsTrue
|
|
});
|
|
return SyntheticBaseEvent;
|
|
}
|
|
/**
|
|
* @interface Event
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
|
*/
|
|
|
|
var EventInterface = {
|
|
eventPhase: 0,
|
|
bubbles: 0,
|
|
cancelable: 0,
|
|
timeStamp: function(event) {
|
|
return event.timeStamp || Date.now();
|
|
},
|
|
defaultPrevented: 0,
|
|
isTrusted: 0
|
|
};
|
|
var SyntheticEvent = createSyntheticEvent(EventInterface);
|
|
|
|
var UIEventInterface = assign({}, EventInterface, {
|
|
view: 0,
|
|
detail: 0
|
|
});
|
|
|
|
var SyntheticUIEvent = createSyntheticEvent(UIEventInterface);
|
|
var lastMovementX;
|
|
var lastMovementY;
|
|
var lastMouseEvent;
|
|
|
|
function updateMouseMovementPolyfillState(event) {
|
|
if (event !== lastMouseEvent) {
|
|
if (lastMouseEvent && event.type === "mousemove") {
|
|
// $FlowFixMe assuming this is a number
|
|
lastMovementX = event.screenX - lastMouseEvent.screenX; // $FlowFixMe assuming this is a number
|
|
|
|
lastMovementY = event.screenY - lastMouseEvent.screenY;
|
|
} else {
|
|
lastMovementX = 0;
|
|
lastMovementY = 0;
|
|
}
|
|
|
|
lastMouseEvent = event;
|
|
}
|
|
}
|
|
/**
|
|
* @interface MouseEvent
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
|
*/
|
|
|
|
var MouseEventInterface = assign({}, UIEventInterface, {
|
|
screenX: 0,
|
|
screenY: 0,
|
|
clientX: 0,
|
|
clientY: 0,
|
|
pageX: 0,
|
|
pageY: 0,
|
|
ctrlKey: 0,
|
|
shiftKey: 0,
|
|
altKey: 0,
|
|
metaKey: 0,
|
|
getModifierState: getEventModifierState,
|
|
button: 0,
|
|
buttons: 0,
|
|
relatedTarget: function(event) {
|
|
if (event.relatedTarget === undefined)
|
|
return event.fromElement === event.srcElement
|
|
? event.toElement
|
|
: event.fromElement;
|
|
return event.relatedTarget;
|
|
},
|
|
movementX: function(event) {
|
|
if ("movementX" in event) {
|
|
return event.movementX;
|
|
}
|
|
|
|
updateMouseMovementPolyfillState(event);
|
|
return lastMovementX;
|
|
},
|
|
movementY: function(event) {
|
|
if ("movementY" in event) {
|
|
return event.movementY;
|
|
} // Don't need to call updateMouseMovementPolyfillState() here
|
|
// because it's guaranteed to have already run when movementX
|
|
// was copied.
|
|
|
|
return lastMovementY;
|
|
}
|
|
});
|
|
|
|
var SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface);
|
|
/**
|
|
* @interface DragEvent
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
|
*/
|
|
|
|
var DragEventInterface = assign({}, MouseEventInterface, {
|
|
dataTransfer: 0
|
|
});
|
|
|
|
var SyntheticDragEvent = createSyntheticEvent(DragEventInterface);
|
|
/**
|
|
* @interface FocusEvent
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
|
*/
|
|
|
|
var FocusEventInterface = assign({}, UIEventInterface, {
|
|
relatedTarget: 0
|
|
});
|
|
|
|
var SyntheticFocusEvent = createSyntheticEvent(FocusEventInterface);
|
|
/**
|
|
* @interface Event
|
|
* @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
|
|
*/
|
|
|
|
var AnimationEventInterface = assign({}, EventInterface, {
|
|
animationName: 0,
|
|
elapsedTime: 0,
|
|
pseudoElement: 0
|
|
});
|
|
|
|
var SyntheticAnimationEvent = createSyntheticEvent(AnimationEventInterface);
|
|
/**
|
|
* @interface Event
|
|
* @see http://www.w3.org/TR/clipboard-apis/
|
|
*/
|
|
|
|
var ClipboardEventInterface = assign({}, EventInterface, {
|
|
clipboardData: function(event) {
|
|
return "clipboardData" in event
|
|
? event.clipboardData
|
|
: window.clipboardData;
|
|
}
|
|
});
|
|
|
|
var SyntheticClipboardEvent = createSyntheticEvent(ClipboardEventInterface);
|
|
/**
|
|
* @interface Event
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents
|
|
*/
|
|
|
|
var CompositionEventInterface = assign({}, EventInterface, {
|
|
data: 0
|
|
});
|
|
|
|
var SyntheticCompositionEvent = createSyntheticEvent(CompositionEventInterface);
|
|
/**
|
|
* @interface Event
|
|
* @see http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105
|
|
* /#events-inputevents
|
|
*/
|
|
// Happens to share the same list for now.
|
|
|
|
var SyntheticInputEvent = SyntheticCompositionEvent;
|
|
/**
|
|
* Normalization of deprecated HTML5 `key` values
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
|
|
*/
|
|
|
|
var normalizeKey = {
|
|
Esc: "Escape",
|
|
Spacebar: " ",
|
|
Left: "ArrowLeft",
|
|
Up: "ArrowUp",
|
|
Right: "ArrowRight",
|
|
Down: "ArrowDown",
|
|
Del: "Delete",
|
|
Win: "OS",
|
|
Menu: "ContextMenu",
|
|
Apps: "ContextMenu",
|
|
Scroll: "ScrollLock",
|
|
MozPrintableKey: "Unidentified"
|
|
};
|
|
/**
|
|
* Translation from legacy `keyCode` to HTML5 `key`
|
|
* Only special keys supported, all others depend on keyboard layout or browser
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
|
|
*/
|
|
|
|
var translateToKey = {
|
|
"8": "Backspace",
|
|
"9": "Tab",
|
|
"12": "Clear",
|
|
"13": "Enter",
|
|
"16": "Shift",
|
|
"17": "Control",
|
|
"18": "Alt",
|
|
"19": "Pause",
|
|
"20": "CapsLock",
|
|
"27": "Escape",
|
|
"32": " ",
|
|
"33": "PageUp",
|
|
"34": "PageDown",
|
|
"35": "End",
|
|
"36": "Home",
|
|
"37": "ArrowLeft",
|
|
"38": "ArrowUp",
|
|
"39": "ArrowRight",
|
|
"40": "ArrowDown",
|
|
"45": "Insert",
|
|
"46": "Delete",
|
|
"112": "F1",
|
|
"113": "F2",
|
|
"114": "F3",
|
|
"115": "F4",
|
|
"116": "F5",
|
|
"117": "F6",
|
|
"118": "F7",
|
|
"119": "F8",
|
|
"120": "F9",
|
|
"121": "F10",
|
|
"122": "F11",
|
|
"123": "F12",
|
|
"144": "NumLock",
|
|
"145": "ScrollLock",
|
|
"224": "Meta"
|
|
};
|
|
/**
|
|
* @param {object} nativeEvent Native browser event.
|
|
* @return {string} Normalized `key` property.
|
|
*/
|
|
|
|
function getEventKey(nativeEvent) {
|
|
if (nativeEvent.key) {
|
|
// Normalize inconsistent values reported by browsers due to
|
|
// implementations of a working draft specification.
|
|
// FireFox implements `key` but returns `MozPrintableKey` for all
|
|
// printable characters (normalized to `Unidentified`), ignore it.
|
|
var key = normalizeKey[nativeEvent.key] || nativeEvent.key; // $FlowFixMe unable to index with a `mixed` value
|
|
|
|
if (key !== "Unidentified") {
|
|
return key;
|
|
}
|
|
} // Browser does not implement `key`, polyfill as much of it as we can.
|
|
|
|
if (nativeEvent.type === "keypress") {
|
|
var charCode = getEventCharCode(
|
|
// $FlowFixMe unable to narrow to `KeyboardEvent`
|
|
nativeEvent
|
|
); // The enter-key is technically both printable and non-printable and can
|
|
// thus be captured by `keypress`, no other non-printable key should.
|
|
|
|
return charCode === 13 ? "Enter" : String.fromCharCode(charCode);
|
|
}
|
|
|
|
if (nativeEvent.type === "keydown" || nativeEvent.type === "keyup") {
|
|
// While user keyboard layout determines the actual meaning of each
|
|
// `keyCode` value, almost all function keys have a universal value.
|
|
// $FlowFixMe unable to index with a `mixed` value
|
|
return translateToKey[nativeEvent.keyCode] || "Unidentified";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
/**
|
|
* Translation from modifier key to the associated property in the event.
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers
|
|
*/
|
|
|
|
var modifierKeyToProp = {
|
|
Alt: "altKey",
|
|
Control: "ctrlKey",
|
|
Meta: "metaKey",
|
|
Shift: "shiftKey"
|
|
}; // Older browsers (Safari <= 10, iOS Safari <= 10.2) do not support
|
|
// getModifierState. If getModifierState is not supported, we map it to a set of
|
|
// modifier keys exposed by the event. In this case, Lock-keys are not supported.
|
|
// $FlowFixMe[missing-local-annot]
|
|
// $FlowFixMe[missing-this-annot]
|
|
|
|
function modifierStateGetter(keyArg) {
|
|
var syntheticEvent = this;
|
|
var nativeEvent = syntheticEvent.nativeEvent;
|
|
|
|
if (nativeEvent.getModifierState) {
|
|
return nativeEvent.getModifierState(keyArg);
|
|
}
|
|
|
|
var keyProp = modifierKeyToProp[keyArg];
|
|
return keyProp ? !!nativeEvent[keyProp] : false;
|
|
}
|
|
|
|
function getEventModifierState(nativeEvent) {
|
|
return modifierStateGetter;
|
|
}
|
|
/**
|
|
* @interface KeyboardEvent
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
|
*/
|
|
|
|
var KeyboardEventInterface = assign({}, UIEventInterface, {
|
|
key: getEventKey,
|
|
code: 0,
|
|
location: 0,
|
|
ctrlKey: 0,
|
|
shiftKey: 0,
|
|
altKey: 0,
|
|
metaKey: 0,
|
|
repeat: 0,
|
|
locale: 0,
|
|
getModifierState: getEventModifierState,
|
|
// Legacy Interface
|
|
charCode: function(event) {
|
|
// `charCode` is the result of a KeyPress event and represents the value of
|
|
// the actual printable character.
|
|
// KeyPress is deprecated, but its replacement is not yet final and not
|
|
// implemented in any major browser. Only KeyPress has charCode.
|
|
if (event.type === "keypress") {
|
|
return getEventCharCode(
|
|
// $FlowFixMe unable to narrow to `KeyboardEvent`
|
|
event
|
|
);
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
keyCode: function(event) {
|
|
// `keyCode` is the result of a KeyDown/Up event and represents the value of
|
|
// physical keyboard key.
|
|
// The actual meaning of the value depends on the users' keyboard layout
|
|
// which cannot be detected. Assuming that it is a US keyboard layout
|
|
// provides a surprisingly accurate mapping for US and European users.
|
|
// Due to this, it is left to the user to implement at this time.
|
|
if (event.type === "keydown" || event.type === "keyup") {
|
|
return event.keyCode;
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
which: function(event) {
|
|
// `which` is an alias for either `keyCode` or `charCode` depending on the
|
|
// type of the event.
|
|
if (event.type === "keypress") {
|
|
return getEventCharCode(
|
|
// $FlowFixMe unable to narrow to `KeyboardEvent`
|
|
event
|
|
);
|
|
}
|
|
|
|
if (event.type === "keydown" || event.type === "keyup") {
|
|
return event.keyCode;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
var SyntheticKeyboardEvent = createSyntheticEvent(KeyboardEventInterface);
|
|
/**
|
|
* @interface PointerEvent
|
|
* @see http://www.w3.org/TR/pointerevents/
|
|
*/
|
|
|
|
var PointerEventInterface = assign({}, MouseEventInterface, {
|
|
pointerId: 0,
|
|
width: 0,
|
|
height: 0,
|
|
pressure: 0,
|
|
tangentialPressure: 0,
|
|
tiltX: 0,
|
|
tiltY: 0,
|
|
twist: 0,
|
|
pointerType: 0,
|
|
isPrimary: 0
|
|
});
|
|
|
|
var SyntheticPointerEvent = createSyntheticEvent(PointerEventInterface);
|
|
/**
|
|
* @interface TouchEvent
|
|
* @see http://www.w3.org/TR/touch-events/
|
|
*/
|
|
|
|
var TouchEventInterface = assign({}, UIEventInterface, {
|
|
touches: 0,
|
|
targetTouches: 0,
|
|
changedTouches: 0,
|
|
altKey: 0,
|
|
metaKey: 0,
|
|
ctrlKey: 0,
|
|
shiftKey: 0,
|
|
getModifierState: getEventModifierState
|
|
});
|
|
|
|
var SyntheticTouchEvent = createSyntheticEvent(TouchEventInterface);
|
|
/**
|
|
* @interface Event
|
|
* @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent
|
|
*/
|
|
|
|
var TransitionEventInterface = assign({}, EventInterface, {
|
|
propertyName: 0,
|
|
elapsedTime: 0,
|
|
pseudoElement: 0
|
|
});
|
|
|
|
var SyntheticTransitionEvent = createSyntheticEvent(TransitionEventInterface);
|
|
/**
|
|
* @interface WheelEvent
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
|
*/
|
|
|
|
var WheelEventInterface = assign({}, MouseEventInterface, {
|
|
deltaX: function(event) {
|
|
return "deltaX" in event
|
|
? event.deltaX // Fallback to `wheelDeltaX` for Webkit and normalize (right is positive).
|
|
: "wheelDeltaX" in event // $FlowFixMe assuming this is a number
|
|
? -event.wheelDeltaX
|
|
: 0;
|
|
},
|
|
deltaY: function(event) {
|
|
return "deltaY" in event
|
|
? event.deltaY // Fallback to `wheelDeltaY` for Webkit and normalize (down is positive).
|
|
: "wheelDeltaY" in event // $FlowFixMe assuming this is a number
|
|
? -event.wheelDeltaY // Fallback to `wheelDelta` for IE<9 and normalize (down is positive).
|
|
: "wheelDelta" in event // $FlowFixMe assuming this is a number
|
|
? -event.wheelDelta
|
|
: 0;
|
|
},
|
|
deltaZ: 0,
|
|
// Browsers without "deltaMode" is reporting in raw wheel delta where one
|
|
// notch on the scroll is always +/- 120, roughly equivalent to pixels.
|
|
// A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or
|
|
// ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size.
|
|
deltaMode: 0
|
|
});
|
|
|
|
var SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface);
|
|
|
|
var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
|
|
|
|
var START_KEYCODE = 229;
|
|
var canUseCompositionEvent = canUseDOM && "CompositionEvent" in window;
|
|
var documentMode = null;
|
|
|
|
if (canUseDOM && "documentMode" in document) {
|
|
documentMode = document.documentMode;
|
|
} // Webkit offers a very useful `textInput` event that can be used to
|
|
// directly represent `beforeInput`. The IE `textinput` event is not as
|
|
// useful, so we don't use it.
|
|
|
|
var canUseTextInputEvent = canUseDOM && "TextEvent" in window && !documentMode; // In IE9+, we have access to composition events, but the data supplied
|
|
// by the native compositionend event may be incorrect. Japanese ideographic
|
|
// spaces, for instance (\u3000) are not recorded correctly.
|
|
|
|
var useFallbackCompositionData =
|
|
canUseDOM &&
|
|
(!canUseCompositionEvent ||
|
|
(documentMode && documentMode > 8 && documentMode <= 11));
|
|
var SPACEBAR_CODE = 32;
|
|
var SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE);
|
|
|
|
function registerEvents() {
|
|
registerTwoPhaseEvent("onBeforeInput", [
|
|
"compositionend",
|
|
"keypress",
|
|
"textInput",
|
|
"paste"
|
|
]);
|
|
registerTwoPhaseEvent("onCompositionEnd", [
|
|
"compositionend",
|
|
"focusout",
|
|
"keydown",
|
|
"keypress",
|
|
"keyup",
|
|
"mousedown"
|
|
]);
|
|
registerTwoPhaseEvent("onCompositionStart", [
|
|
"compositionstart",
|
|
"focusout",
|
|
"keydown",
|
|
"keypress",
|
|
"keyup",
|
|
"mousedown"
|
|
]);
|
|
registerTwoPhaseEvent("onCompositionUpdate", [
|
|
"compositionupdate",
|
|
"focusout",
|
|
"keydown",
|
|
"keypress",
|
|
"keyup",
|
|
"mousedown"
|
|
]);
|
|
} // Track whether we've ever handled a keypress on the space key.
|
|
|
|
var hasSpaceKeypress = false;
|
|
/**
|
|
* Return whether a native keypress event is assumed to be a command.
|
|
* This is required because Firefox fires `keypress` events for key commands
|
|
* (cut, copy, select-all, etc.) even though no character is inserted.
|
|
*/
|
|
|
|
function isKeypressCommand(nativeEvent) {
|
|
return (
|
|
(nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) && // ctrlKey && altKey is equivalent to AltGr, and is not a command.
|
|
!(nativeEvent.ctrlKey && nativeEvent.altKey)
|
|
);
|
|
}
|
|
/**
|
|
* Translate native top level events into event types.
|
|
*/
|
|
|
|
function getCompositionEventType(domEventName) {
|
|
switch (domEventName) {
|
|
case "compositionstart":
|
|
return "onCompositionStart";
|
|
|
|
case "compositionend":
|
|
return "onCompositionEnd";
|
|
|
|
case "compositionupdate":
|
|
return "onCompositionUpdate";
|
|
}
|
|
}
|
|
/**
|
|
* Does our fallback best-guess model think this event signifies that
|
|
* composition has begun?
|
|
*/
|
|
|
|
function isFallbackCompositionStart(domEventName, nativeEvent) {
|
|
return domEventName === "keydown" && nativeEvent.keyCode === START_KEYCODE;
|
|
}
|
|
/**
|
|
* Does our fallback mode think that this event is the end of composition?
|
|
*/
|
|
|
|
function isFallbackCompositionEnd(domEventName, nativeEvent) {
|
|
switch (domEventName) {
|
|
case "keyup":
|
|
// Command keys insert or clear IME input.
|
|
return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
|
|
|
|
case "keydown":
|
|
// Expect IME keyCode on each keydown. If we get any other
|
|
// code we must have exited earlier.
|
|
return nativeEvent.keyCode !== START_KEYCODE;
|
|
|
|
case "keypress":
|
|
case "mousedown":
|
|
case "focusout":
|
|
// Events are not possible without cancelling IME.
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Google Input Tools provides composition data via a CustomEvent,
|
|
* with the `data` property populated in the `detail` object. If this
|
|
* is available on the event object, use it. If not, this is a plain
|
|
* composition event and we have nothing special to extract.
|
|
*
|
|
* @param {object} nativeEvent
|
|
* @return {?string}
|
|
*/
|
|
|
|
function getDataFromCustomEvent(nativeEvent) {
|
|
var detail = nativeEvent.detail;
|
|
|
|
if (typeof detail === "object" && "data" in detail) {
|
|
return detail.data;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
/**
|
|
* Check if a composition event was triggered by Korean IME.
|
|
* Our fallback mode does not work well with IE's Korean IME,
|
|
* so just use native composition events when Korean IME is used.
|
|
* Although CompositionEvent.locale property is deprecated,
|
|
* it is available in IE, where our fallback mode is enabled.
|
|
*
|
|
* @param {object} nativeEvent
|
|
* @return {boolean}
|
|
*/
|
|
|
|
function isUsingKoreanIME(nativeEvent) {
|
|
return nativeEvent.locale === "ko";
|
|
} // Track the current IME composition status, if any.
|
|
|
|
var isComposing = false;
|
|
/**
|
|
* @return {?object} A SyntheticCompositionEvent.
|
|
*/
|
|
|
|
function extractCompositionEvent(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
) {
|
|
var eventType;
|
|
var fallbackData;
|
|
|
|
if (canUseCompositionEvent) {
|
|
eventType = getCompositionEventType(domEventName);
|
|
} else if (!isComposing) {
|
|
if (isFallbackCompositionStart(domEventName, nativeEvent)) {
|
|
eventType = "onCompositionStart";
|
|
}
|
|
} else if (isFallbackCompositionEnd(domEventName, nativeEvent)) {
|
|
eventType = "onCompositionEnd";
|
|
}
|
|
|
|
if (!eventType) {
|
|
return null;
|
|
}
|
|
|
|
if (useFallbackCompositionData && !isUsingKoreanIME(nativeEvent)) {
|
|
// The current composition is stored statically and must not be
|
|
// overwritten while composition continues.
|
|
if (!isComposing && eventType === "onCompositionStart") {
|
|
isComposing = initialize(nativeEventTarget);
|
|
} else if (eventType === "onCompositionEnd") {
|
|
if (isComposing) {
|
|
fallbackData = getData();
|
|
}
|
|
}
|
|
}
|
|
|
|
var listeners = accumulateTwoPhaseListeners(targetInst, eventType);
|
|
|
|
if (listeners.length > 0) {
|
|
var event = new SyntheticCompositionEvent(
|
|
eventType,
|
|
domEventName,
|
|
null,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
dispatchQueue.push({
|
|
event: event,
|
|
listeners: listeners
|
|
});
|
|
|
|
if (fallbackData) {
|
|
// Inject data generated from fallback path into the synthetic event.
|
|
// This matches the property of native CompositionEventInterface.
|
|
// $FlowFixMe[incompatible-use]
|
|
event.data = fallbackData;
|
|
} else {
|
|
var customData = getDataFromCustomEvent(nativeEvent);
|
|
|
|
if (customData !== null) {
|
|
// $FlowFixMe[incompatible-use]
|
|
event.data = customData;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getNativeBeforeInputChars(domEventName, nativeEvent) {
|
|
switch (domEventName) {
|
|
case "compositionend":
|
|
return getDataFromCustomEvent(nativeEvent);
|
|
|
|
case "keypress":
|
|
/**
|
|
* If native `textInput` events are available, our goal is to make
|
|
* use of them. However, there is a special case: the spacebar key.
|
|
* In Webkit, preventing default on a spacebar `textInput` event
|
|
* cancels character insertion, but it *also* causes the browser
|
|
* to fall back to its default spacebar behavior of scrolling the
|
|
* page.
|
|
*
|
|
* Tracking at:
|
|
* https://code.google.com/p/chromium/issues/detail?id=355103
|
|
*
|
|
* To avoid this issue, use the keypress event as if no `textInput`
|
|
* event is available.
|
|
*/
|
|
var which = nativeEvent.which;
|
|
|
|
if (which !== SPACEBAR_CODE) {
|
|
return null;
|
|
}
|
|
|
|
hasSpaceKeypress = true;
|
|
return SPACEBAR_CHAR;
|
|
|
|
case "textInput":
|
|
// Record the characters to be added to the DOM.
|
|
var chars = nativeEvent.data; // If it's a spacebar character, assume that we have already handled
|
|
// it at the keypress level and bail immediately. Android Chrome
|
|
// doesn't give us keycodes, so we need to ignore it.
|
|
|
|
if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
|
|
return null;
|
|
}
|
|
|
|
return chars;
|
|
|
|
default:
|
|
// For other native event types, do nothing.
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* For browsers that do not provide the `textInput` event, extract the
|
|
* appropriate string to use for SyntheticInputEvent.
|
|
*/
|
|
|
|
function getFallbackBeforeInputChars(domEventName, nativeEvent) {
|
|
// If we are currently composing (IME) and using a fallback to do so,
|
|
// try to extract the composed characters from the fallback object.
|
|
// If composition event is available, we extract a string only at
|
|
// compositionevent, otherwise extract it at fallback events.
|
|
if (isComposing) {
|
|
if (
|
|
domEventName === "compositionend" ||
|
|
(!canUseCompositionEvent &&
|
|
isFallbackCompositionEnd(domEventName, nativeEvent))
|
|
) {
|
|
var chars = getData();
|
|
reset();
|
|
isComposing = false;
|
|
return chars;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
switch (domEventName) {
|
|
case "paste":
|
|
// If a paste event occurs after a keypress, throw out the input
|
|
// chars. Paste events should not lead to BeforeInput events.
|
|
return null;
|
|
|
|
case "keypress":
|
|
/**
|
|
* As of v27, Firefox may fire keypress events even when no character
|
|
* will be inserted. A few possibilities:
|
|
*
|
|
* - `which` is `0`. Arrow keys, Esc key, etc.
|
|
*
|
|
* - `which` is the pressed key code, but no char is available.
|
|
* Ex: 'AltGr + d` in Polish. There is no modified character for
|
|
* this key combination and no character is inserted into the
|
|
* document, but FF fires the keypress for char code `100` anyway.
|
|
* No `input` event will occur.
|
|
*
|
|
* - `which` is the pressed key code, but a command combination is
|
|
* being used. Ex: `Cmd+C`. No character is inserted, and no
|
|
* `input` event will occur.
|
|
*/
|
|
if (!isKeypressCommand(nativeEvent)) {
|
|
// IE fires the `keypress` event when a user types an emoji via
|
|
// Touch keyboard of Windows. In such a case, the `char` property
|
|
// holds an emoji character like `\uD83D\uDE0A`. Because its length
|
|
// is 2, the property `which` does not represent an emoji correctly.
|
|
// In such a case, we directly return the `char` property instead of
|
|
// using `which`.
|
|
if (nativeEvent.char && nativeEvent.char.length > 1) {
|
|
return nativeEvent.char;
|
|
} else if (nativeEvent.which) {
|
|
return String.fromCharCode(nativeEvent.which);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
|
|
case "compositionend":
|
|
return useFallbackCompositionData && !isUsingKoreanIME(nativeEvent)
|
|
? null
|
|
: nativeEvent.data;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* Extract a SyntheticInputEvent for `beforeInput`, based on either native
|
|
* `textInput` or fallback behavior.
|
|
*
|
|
* @return {?object} A SyntheticInputEvent.
|
|
*/
|
|
|
|
function extractBeforeInputEvent(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
) {
|
|
var chars;
|
|
|
|
if (canUseTextInputEvent) {
|
|
chars = getNativeBeforeInputChars(domEventName, nativeEvent);
|
|
} else {
|
|
chars = getFallbackBeforeInputChars(domEventName, nativeEvent);
|
|
} // If no characters are being inserted, no BeforeInput event should
|
|
// be fired.
|
|
|
|
if (!chars) {
|
|
return null;
|
|
}
|
|
|
|
var listeners = accumulateTwoPhaseListeners(targetInst, "onBeforeInput");
|
|
|
|
if (listeners.length > 0) {
|
|
var event = new SyntheticInputEvent(
|
|
"onBeforeInput",
|
|
"beforeinput",
|
|
null,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
dispatchQueue.push({
|
|
event: event,
|
|
listeners: listeners
|
|
}); // $FlowFixMe[incompatible-use]
|
|
|
|
event.data = chars;
|
|
}
|
|
}
|
|
/**
|
|
* Create an `onBeforeInput` event to match
|
|
* http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents.
|
|
*
|
|
* This event plugin is based on the native `textInput` event
|
|
* available in Chrome, Safari, Opera, and IE. This event fires after
|
|
* `onKeyPress` and `onCompositionEnd`, but before `onInput`.
|
|
*
|
|
* `beforeInput` is spec'd but not implemented in any browsers, and
|
|
* the `input` event does not provide any useful information about what has
|
|
* actually been added, contrary to the spec. Thus, `textInput` is the best
|
|
* available event to identify the characters that have actually been inserted
|
|
* into the target node.
|
|
*
|
|
* This plugin is also responsible for emitting `composition` events, thus
|
|
* allowing us to share composition fallback code for both `beforeInput` and
|
|
* `composition` event types.
|
|
*/
|
|
|
|
function extractEvents(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
) {
|
|
extractCompositionEvent(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
extractBeforeInputEvent(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
|
|
*/
|
|
var supportedInputTypes = {
|
|
color: true,
|
|
date: true,
|
|
datetime: true,
|
|
"datetime-local": true,
|
|
email: true,
|
|
month: true,
|
|
number: true,
|
|
password: true,
|
|
range: true,
|
|
search: true,
|
|
tel: true,
|
|
text: true,
|
|
time: true,
|
|
url: true,
|
|
week: true
|
|
};
|
|
|
|
function isTextInputElement(elem) {
|
|
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
|
|
|
|
if (nodeName === "input") {
|
|
return !!supportedInputTypes[elem.type];
|
|
}
|
|
|
|
if (nodeName === "textarea") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if an event is supported in the current execution environment.
|
|
*
|
|
* NOTE: This will not work correctly for non-generic events such as `change`,
|
|
* `reset`, `load`, `error`, and `select`.
|
|
*
|
|
* Borrows from Modernizr.
|
|
*
|
|
* @param {string} eventNameSuffix Event name, e.g. "click".
|
|
* @return {boolean} True if the event is supported.
|
|
* @internal
|
|
* @license Modernizr 3.0.0pre (Custom Build) | MIT
|
|
*/
|
|
|
|
function isEventSupported(eventNameSuffix) {
|
|
if (!canUseDOM) {
|
|
return false;
|
|
}
|
|
|
|
var eventName = "on" + eventNameSuffix;
|
|
var isSupported = eventName in document;
|
|
|
|
if (!isSupported) {
|
|
var element = document.createElement("div");
|
|
element.setAttribute(eventName, "return;");
|
|
isSupported = typeof element[eventName] === "function";
|
|
}
|
|
|
|
return isSupported;
|
|
}
|
|
|
|
function registerEvents$1() {
|
|
registerTwoPhaseEvent("onChange", [
|
|
"change",
|
|
"click",
|
|
"focusin",
|
|
"focusout",
|
|
"input",
|
|
"keydown",
|
|
"keyup",
|
|
"selectionchange"
|
|
]);
|
|
}
|
|
|
|
function createAndAccumulateChangeEvent( // $FlowFixMe[missing-local-annot]
|
|
dispatchQueue,
|
|
inst,
|
|
nativeEvent,
|
|
target
|
|
) {
|
|
// Flag this event loop as needing state restore.
|
|
enqueueStateRestore(target);
|
|
var listeners = accumulateTwoPhaseListeners(inst, "onChange");
|
|
|
|
if (listeners.length > 0) {
|
|
var event = new SyntheticEvent(
|
|
"onChange",
|
|
"change",
|
|
null,
|
|
nativeEvent,
|
|
target
|
|
);
|
|
dispatchQueue.push({
|
|
event: event,
|
|
listeners: listeners
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* For IE shims
|
|
*/
|
|
|
|
var activeElement = null;
|
|
var activeElementInst = null;
|
|
/**
|
|
* SECTION: handle `change` event
|
|
*/
|
|
|
|
function shouldUseChangeEvent(elem) {
|
|
var nodeName = elem.nodeName && elem.nodeName.toLowerCase();
|
|
return (
|
|
nodeName === "select" || (nodeName === "input" && elem.type === "file")
|
|
);
|
|
}
|
|
|
|
function manualDispatchChangeEvent(nativeEvent) {
|
|
var dispatchQueue = [];
|
|
createAndAccumulateChangeEvent(
|
|
dispatchQueue,
|
|
activeElementInst,
|
|
nativeEvent,
|
|
getEventTarget(nativeEvent)
|
|
); // If change and propertychange bubbled, we'd just bind to it like all the
|
|
// other events and have it go through ReactBrowserEventEmitter. Since it
|
|
// doesn't, we manually listen for the events and so we have to enqueue and
|
|
// process the abstract event manually.
|
|
//
|
|
// Batching is necessary here in order to ensure that all event handlers run
|
|
// before the next rerender (including event handlers attached to ancestor
|
|
// elements instead of directly on the input). Without this, controlled
|
|
// components don't work properly in conjunction with event bubbling because
|
|
// the component is rerendered and the value reverted before all the event
|
|
// handlers can run. See https://github.com/facebook/react/issues/708.
|
|
|
|
batchedUpdates(runEventInBatch, dispatchQueue);
|
|
}
|
|
|
|
function runEventInBatch(dispatchQueue) {
|
|
processDispatchQueue(dispatchQueue, 0);
|
|
}
|
|
|
|
function getInstIfValueChanged(targetInst) {
|
|
var targetNode = getNodeFromInstance(targetInst);
|
|
|
|
if (updateValueIfChanged(targetNode)) {
|
|
return targetInst;
|
|
}
|
|
}
|
|
|
|
function getTargetInstForChangeEvent(domEventName, targetInst) {
|
|
if (domEventName === "change") {
|
|
return targetInst;
|
|
}
|
|
}
|
|
/**
|
|
* SECTION: handle `input` event
|
|
*/
|
|
|
|
var isInputEventSupported = false;
|
|
|
|
if (canUseDOM) {
|
|
// IE9 claims to support the input event but fails to trigger it when
|
|
// deleting text, so we ignore its input events.
|
|
isInputEventSupported =
|
|
isEventSupported("input") &&
|
|
(!document.documentMode || document.documentMode > 9);
|
|
}
|
|
/**
|
|
* (For IE <=9) Starts tracking propertychange events on the passed-in element
|
|
* and override the value property so that we can distinguish user events from
|
|
* value changes in JS.
|
|
*/
|
|
|
|
function startWatchingForValueChange(target, targetInst) {
|
|
activeElement = target;
|
|
activeElementInst = targetInst;
|
|
activeElement.attachEvent("onpropertychange", handlePropertyChange);
|
|
}
|
|
/**
|
|
* (For IE <=9) Removes the event listeners from the currently-tracked element,
|
|
* if any exists.
|
|
*/
|
|
|
|
function stopWatchingForValueChange() {
|
|
if (!activeElement) {
|
|
return;
|
|
}
|
|
|
|
activeElement.detachEvent("onpropertychange", handlePropertyChange);
|
|
activeElement = null;
|
|
activeElementInst = null;
|
|
}
|
|
/**
|
|
* (For IE <=9) Handles a propertychange event, sending a `change` event if
|
|
* the value of the active element has changed.
|
|
*/
|
|
// $FlowFixMe[missing-local-annot]
|
|
|
|
function handlePropertyChange(nativeEvent) {
|
|
if (nativeEvent.propertyName !== "value") {
|
|
return;
|
|
}
|
|
|
|
if (getInstIfValueChanged(activeElementInst)) {
|
|
manualDispatchChangeEvent(nativeEvent);
|
|
}
|
|
}
|
|
|
|
function handleEventsForInputEventPolyfill(domEventName, target, targetInst) {
|
|
if (domEventName === "focusin") {
|
|
// In IE9, propertychange fires for most input events but is buggy and
|
|
// doesn't fire when text is deleted, but conveniently, selectionchange
|
|
// appears to fire in all of the remaining cases so we catch those and
|
|
// forward the event if the value has changed
|
|
// In either case, we don't want to call the event handler if the value
|
|
// is changed from JS so we redefine a setter for `.value` that updates
|
|
// our activeElementValue variable, allowing us to ignore those changes
|
|
//
|
|
// stopWatching() should be a noop here but we call it just in case we
|
|
// missed a blur event somehow.
|
|
stopWatchingForValueChange();
|
|
startWatchingForValueChange(target, targetInst);
|
|
} else if (domEventName === "focusout") {
|
|
stopWatchingForValueChange();
|
|
}
|
|
} // For IE8 and IE9.
|
|
|
|
function getTargetInstForInputEventPolyfill(domEventName, targetInst) {
|
|
if (
|
|
domEventName === "selectionchange" ||
|
|
domEventName === "keyup" ||
|
|
domEventName === "keydown"
|
|
) {
|
|
// On the selectionchange event, the target is just document which isn't
|
|
// helpful for us so just check activeElement instead.
|
|
//
|
|
// 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
|
|
// propertychange on the first input event after setting `value` from a
|
|
// script and fires only keydown, keypress, keyup. Catching keyup usually
|
|
// gets it and catching keydown lets us fire an event for the first
|
|
// keystroke if user does a key repeat (it'll be a little delayed: right
|
|
// before the second keystroke). Other input methods (e.g., paste) seem to
|
|
// fire selectionchange normally.
|
|
return getInstIfValueChanged(activeElementInst);
|
|
}
|
|
}
|
|
/**
|
|
* SECTION: handle `click` event
|
|
*/
|
|
|
|
function shouldUseClickEvent(elem) {
|
|
// Use the `click` event to detect changes to checkbox and radio inputs.
|
|
// This approach works across all browsers, whereas `change` does not fire
|
|
// until `blur` in IE8.
|
|
var nodeName = elem.nodeName;
|
|
return (
|
|
nodeName &&
|
|
nodeName.toLowerCase() === "input" &&
|
|
(elem.type === "checkbox" || elem.type === "radio")
|
|
);
|
|
}
|
|
|
|
function getTargetInstForClickEvent(domEventName, targetInst) {
|
|
if (domEventName === "click") {
|
|
return getInstIfValueChanged(targetInst);
|
|
}
|
|
}
|
|
|
|
function getTargetInstForInputOrChangeEvent(domEventName, targetInst) {
|
|
if (domEventName === "input" || domEventName === "change") {
|
|
return getInstIfValueChanged(targetInst);
|
|
}
|
|
}
|
|
|
|
function handleControlledInputBlur(node) {
|
|
var state = node._wrapperState;
|
|
|
|
if (!state || !state.controlled || node.type !== "number") {
|
|
return;
|
|
}
|
|
|
|
{
|
|
// If controlled, assign the value attribute to the current value on blur
|
|
setDefaultValue(node, "number", node.value);
|
|
}
|
|
}
|
|
/**
|
|
* This plugin creates an `onChange` event that normalizes change events
|
|
* across form elements. This event fires at a time when it's possible to
|
|
* change the element's value without seeing a flicker.
|
|
*
|
|
* Supported elements are:
|
|
* - input (see `isTextInputElement`)
|
|
* - textarea
|
|
* - select
|
|
*/
|
|
|
|
function extractEvents$1(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
) {
|
|
var targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
|
|
var getTargetInstFunc, handleEventFunc;
|
|
|
|
if (shouldUseChangeEvent(targetNode)) {
|
|
getTargetInstFunc = getTargetInstForChangeEvent;
|
|
} else if (isTextInputElement(targetNode)) {
|
|
if (isInputEventSupported) {
|
|
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
|
|
} else {
|
|
getTargetInstFunc = getTargetInstForInputEventPolyfill;
|
|
handleEventFunc = handleEventsForInputEventPolyfill;
|
|
}
|
|
} else if (shouldUseClickEvent(targetNode)) {
|
|
getTargetInstFunc = getTargetInstForClickEvent;
|
|
}
|
|
|
|
if (getTargetInstFunc) {
|
|
var inst = getTargetInstFunc(domEventName, targetInst);
|
|
|
|
if (inst) {
|
|
createAndAccumulateChangeEvent(
|
|
dispatchQueue,
|
|
inst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (handleEventFunc) {
|
|
handleEventFunc(domEventName, targetNode, targetInst);
|
|
} // When blurring, set the value attribute for number inputs
|
|
|
|
if (domEventName === "focusout") {
|
|
handleControlledInputBlur(targetNode);
|
|
}
|
|
}
|
|
|
|
function registerEvents$2() {
|
|
registerDirectEvent("onMouseEnter", ["mouseout", "mouseover"]);
|
|
registerDirectEvent("onMouseLeave", ["mouseout", "mouseover"]);
|
|
registerDirectEvent("onPointerEnter", ["pointerout", "pointerover"]);
|
|
registerDirectEvent("onPointerLeave", ["pointerout", "pointerover"]);
|
|
}
|
|
/**
|
|
* For almost every interaction we care about, there will be both a top-level
|
|
* `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
|
|
* we do not extract duplicate events. However, moving the mouse into the
|
|
* browser from outside will not fire a `mouseout` event. In this case, we use
|
|
* the `mouseover` top-level event.
|
|
*/
|
|
|
|
function extractEvents$2(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
) {
|
|
var isOverEvent =
|
|
domEventName === "mouseover" || domEventName === "pointerover";
|
|
var isOutEvent = domEventName === "mouseout" || domEventName === "pointerout";
|
|
|
|
if (isOverEvent && !isReplayingEvent(nativeEvent)) {
|
|
// If this is an over event with a target, we might have already dispatched
|
|
// the event in the out event of the other target. If this is replayed,
|
|
// then it's because we couldn't dispatch against this target previously
|
|
// so we have to do it now instead.
|
|
var related = nativeEvent.relatedTarget || nativeEvent.fromElement;
|
|
|
|
if (related) {
|
|
// If the related node is managed by React, we can assume that we have
|
|
// already dispatched the corresponding events during its mouseout.
|
|
if (
|
|
getClosestInstanceFromNode(related) ||
|
|
isContainerMarkedAsRoot(related)
|
|
) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isOutEvent && !isOverEvent) {
|
|
// Must not be a mouse or pointer in or out - ignoring.
|
|
return;
|
|
}
|
|
|
|
var win; // TODO: why is this nullable in the types but we read from it?
|
|
|
|
if (nativeEventTarget.window === nativeEventTarget) {
|
|
// `nativeEventTarget` is probably a window object.
|
|
win = nativeEventTarget;
|
|
} else {
|
|
// TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.
|
|
var doc = nativeEventTarget.ownerDocument;
|
|
|
|
if (doc) {
|
|
win = doc.defaultView || doc.parentWindow;
|
|
} else {
|
|
win = window;
|
|
}
|
|
}
|
|
|
|
var from;
|
|
var to;
|
|
|
|
if (isOutEvent) {
|
|
var _related = nativeEvent.relatedTarget || nativeEvent.toElement;
|
|
|
|
from = targetInst;
|
|
to = _related ? getClosestInstanceFromNode(_related) : null;
|
|
|
|
if (to !== null) {
|
|
var nearestMounted = getNearestMountedFiber(to);
|
|
var tag = to.tag;
|
|
|
|
if (
|
|
to !== nearestMounted ||
|
|
(tag !== HostComponent && tag !== HostSingleton && tag !== HostText)
|
|
) {
|
|
to = null;
|
|
}
|
|
}
|
|
} else {
|
|
// Moving to a node from outside the window.
|
|
from = null;
|
|
to = targetInst;
|
|
}
|
|
|
|
if (from === to) {
|
|
// Nothing pertains to our managed components.
|
|
return;
|
|
}
|
|
|
|
var SyntheticEventCtor = SyntheticMouseEvent;
|
|
var leaveEventType = "onMouseLeave";
|
|
var enterEventType = "onMouseEnter";
|
|
var eventTypePrefix = "mouse";
|
|
|
|
if (domEventName === "pointerout" || domEventName === "pointerover") {
|
|
SyntheticEventCtor = SyntheticPointerEvent;
|
|
leaveEventType = "onPointerLeave";
|
|
enterEventType = "onPointerEnter";
|
|
eventTypePrefix = "pointer";
|
|
}
|
|
|
|
var fromNode = from == null ? win : getNodeFromInstance(from);
|
|
var toNode = to == null ? win : getNodeFromInstance(to);
|
|
var leave = new SyntheticEventCtor(
|
|
leaveEventType,
|
|
eventTypePrefix + "leave",
|
|
from,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
leave.target = fromNode;
|
|
leave.relatedTarget = toNode;
|
|
var enter = null; // We should only process this nativeEvent if we are processing
|
|
// the first ancestor. Next time, we will ignore the event.
|
|
|
|
var nativeTargetInst = getClosestInstanceFromNode(nativeEventTarget);
|
|
|
|
if (nativeTargetInst === targetInst) {
|
|
var enterEvent = new SyntheticEventCtor(
|
|
enterEventType,
|
|
eventTypePrefix + "enter",
|
|
to,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
enterEvent.target = toNode;
|
|
enterEvent.relatedTarget = fromNode;
|
|
enter = enterEvent;
|
|
}
|
|
|
|
accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to);
|
|
}
|
|
|
|
/**
|
|
* inlined Object.is polyfill to avoid requiring consumers ship their own
|
|
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
|
*/
|
|
function is(x, y) {
|
|
return (
|
|
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
|
|
);
|
|
}
|
|
|
|
var objectIs = typeof Object.is === "function" ? Object.is : is; // $FlowFixMe[method-unbinding]
|
|
|
|
/**
|
|
* Performs equality by iterating through keys on an object and returning false
|
|
* when any key has values which are not strictly equal between the arguments.
|
|
* Returns true when the values of all keys are strictly equal.
|
|
*/
|
|
|
|
function shallowEqual(objA, objB) {
|
|
if (objectIs(objA, objB)) {
|
|
return true;
|
|
}
|
|
|
|
if (
|
|
typeof objA !== "object" ||
|
|
objA === null ||
|
|
typeof objB !== "object" ||
|
|
objB === null
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
var keysA = Object.keys(objA);
|
|
var keysB = Object.keys(objB);
|
|
|
|
if (keysA.length !== keysB.length) {
|
|
return false;
|
|
} // Test for A's keys different from B.
|
|
|
|
for (var i = 0; i < keysA.length; i++) {
|
|
var currentKey = keysA[i];
|
|
|
|
if (
|
|
!hasOwnProperty.call(objB, currentKey) ||
|
|
!objectIs(objA[currentKey], objB[currentKey])
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Given any node return the first leaf node without children.
|
|
*
|
|
* @param {DOMElement|DOMTextNode} node
|
|
* @return {DOMElement|DOMTextNode}
|
|
*/
|
|
|
|
function getLeafNode(node) {
|
|
while (node && node.firstChild) {
|
|
node = node.firstChild;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
/**
|
|
* Get the next sibling within a container. This will walk up the
|
|
* DOM if a node's siblings have been exhausted.
|
|
*
|
|
* @param {DOMElement|DOMTextNode} node
|
|
* @return {?DOMElement|DOMTextNode}
|
|
*/
|
|
|
|
function getSiblingNode(node) {
|
|
while (node) {
|
|
if (node.nextSibling) {
|
|
return node.nextSibling;
|
|
}
|
|
|
|
node = node.parentNode;
|
|
}
|
|
}
|
|
/**
|
|
* Get object describing the nodes which contain characters at offset.
|
|
*
|
|
* @param {DOMElement|DOMTextNode} root
|
|
* @param {number} offset
|
|
* @return {?object}
|
|
*/
|
|
|
|
function getNodeForCharacterOffset(root, offset) {
|
|
var node = getLeafNode(root);
|
|
var nodeStart = 0;
|
|
var nodeEnd = 0;
|
|
|
|
while (node) {
|
|
if (node.nodeType === TEXT_NODE) {
|
|
nodeEnd = nodeStart + node.textContent.length;
|
|
|
|
if (nodeStart <= offset && nodeEnd >= offset) {
|
|
return {
|
|
node: node,
|
|
offset: offset - nodeStart
|
|
};
|
|
}
|
|
|
|
nodeStart = nodeEnd;
|
|
}
|
|
|
|
node = getLeafNode(getSiblingNode(node));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {DOMElement} outerNode
|
|
* @return {?object}
|
|
*/
|
|
|
|
function getOffsets(outerNode) {
|
|
var ownerDocument = outerNode.ownerDocument;
|
|
var win = (ownerDocument && ownerDocument.defaultView) || window;
|
|
var selection = win.getSelection && win.getSelection();
|
|
|
|
if (!selection || selection.rangeCount === 0) {
|
|
return null;
|
|
}
|
|
|
|
var anchorNode = selection.anchorNode,
|
|
anchorOffset = selection.anchorOffset,
|
|
focusNode = selection.focusNode,
|
|
focusOffset = selection.focusOffset; // In Firefox, anchorNode and focusNode can be "anonymous divs", e.g. the
|
|
// up/down buttons on an <input type="number">. Anonymous divs do not seem to
|
|
// expose properties, triggering a "Permission denied error" if any of its
|
|
// properties are accessed. The only seemingly possible way to avoid erroring
|
|
// is to access a property that typically works for non-anonymous divs and
|
|
// catch any error that may otherwise arise. See
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=208427
|
|
|
|
try {
|
|
/* eslint-disable ft-flow/no-unused-expressions */
|
|
anchorNode.nodeType;
|
|
focusNode.nodeType;
|
|
/* eslint-enable ft-flow/no-unused-expressions */
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
|
|
return getModernOffsetsFromPoints(
|
|
outerNode,
|
|
anchorNode,
|
|
anchorOffset,
|
|
focusNode,
|
|
focusOffset
|
|
);
|
|
}
|
|
/**
|
|
* Returns {start, end} where `start` is the character/codepoint index of
|
|
* (anchorNode, anchorOffset) within the textContent of `outerNode`, and
|
|
* `end` is the index of (focusNode, focusOffset).
|
|
*
|
|
* Returns null if you pass in garbage input but we should probably just crash.
|
|
*
|
|
* Exported only for testing.
|
|
*/
|
|
|
|
function getModernOffsetsFromPoints(
|
|
outerNode,
|
|
anchorNode,
|
|
anchorOffset,
|
|
focusNode,
|
|
focusOffset
|
|
) {
|
|
var length = 0;
|
|
var start = -1;
|
|
var end = -1;
|
|
var indexWithinAnchor = 0;
|
|
var indexWithinFocus = 0;
|
|
var node = outerNode;
|
|
var parentNode = null;
|
|
|
|
outer: while (true) {
|
|
var next = null;
|
|
|
|
while (true) {
|
|
if (
|
|
node === anchorNode &&
|
|
(anchorOffset === 0 || node.nodeType === TEXT_NODE)
|
|
) {
|
|
start = length + anchorOffset;
|
|
}
|
|
|
|
if (
|
|
node === focusNode &&
|
|
(focusOffset === 0 || node.nodeType === TEXT_NODE)
|
|
) {
|
|
end = length + focusOffset;
|
|
}
|
|
|
|
if (node.nodeType === TEXT_NODE) {
|
|
length += node.nodeValue.length;
|
|
}
|
|
|
|
if ((next = node.firstChild) === null) {
|
|
break;
|
|
} // Moving from `node` to its first child `next`.
|
|
|
|
parentNode = node;
|
|
node = next;
|
|
}
|
|
|
|
while (true) {
|
|
if (node === outerNode) {
|
|
// If `outerNode` has children, this is always the second time visiting
|
|
// it. If it has no children, this is still the first loop, and the only
|
|
// valid selection is anchorNode and focusNode both equal to this node
|
|
// and both offsets 0, in which case we will have handled above.
|
|
break outer;
|
|
}
|
|
|
|
if (parentNode === anchorNode && ++indexWithinAnchor === anchorOffset) {
|
|
start = length;
|
|
}
|
|
|
|
if (parentNode === focusNode && ++indexWithinFocus === focusOffset) {
|
|
end = length;
|
|
}
|
|
|
|
if ((next = node.nextSibling) !== null) {
|
|
break;
|
|
}
|
|
|
|
node = parentNode;
|
|
parentNode = node.parentNode;
|
|
} // Moving from `node` to its next sibling `next`.
|
|
|
|
node = next;
|
|
}
|
|
|
|
if (start === -1 || end === -1) {
|
|
// This should never happen. (Would happen if the anchor/focus nodes aren't
|
|
// actually inside the passed-in node.)
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
start: start,
|
|
end: end
|
|
};
|
|
}
|
|
/**
|
|
* In modern non-IE browsers, we can support both forward and backward
|
|
* selections.
|
|
*
|
|
* Note: IE10+ supports the Selection object, but it does not support
|
|
* the `extend` method, which means that even in modern IE, it's not possible
|
|
* to programmatically create a backward selection. Thus, for all IE
|
|
* versions, we use the old IE API to create our selections.
|
|
*
|
|
* @param {DOMElement|DOMTextNode} node
|
|
* @param {object} offsets
|
|
*/
|
|
|
|
function setOffsets(node, offsets) {
|
|
var doc = node.ownerDocument || document;
|
|
var win = (doc && doc.defaultView) || window; // Edge fails with "Object expected" in some scenarios.
|
|
// (For instance: TinyMCE editor used in a list component that supports pasting to add more,
|
|
// fails when pasting 100+ items)
|
|
|
|
if (!win.getSelection) {
|
|
return;
|
|
}
|
|
|
|
var selection = win.getSelection();
|
|
var length = node.textContent.length;
|
|
var start = Math.min(offsets.start, length);
|
|
var end = offsets.end === undefined ? start : Math.min(offsets.end, length); // IE 11 uses modern selection, but doesn't support the extend method.
|
|
// Flip backward selections, so we can set with a single range.
|
|
|
|
if (!selection.extend && start > end) {
|
|
var temp = end;
|
|
end = start;
|
|
start = temp;
|
|
}
|
|
|
|
var startMarker = getNodeForCharacterOffset(node, start);
|
|
var endMarker = getNodeForCharacterOffset(node, end);
|
|
|
|
if (startMarker && endMarker) {
|
|
if (
|
|
selection.rangeCount === 1 &&
|
|
selection.anchorNode === startMarker.node &&
|
|
selection.anchorOffset === startMarker.offset &&
|
|
selection.focusNode === endMarker.node &&
|
|
selection.focusOffset === endMarker.offset
|
|
) {
|
|
return;
|
|
}
|
|
|
|
var range = doc.createRange();
|
|
range.setStart(startMarker.node, startMarker.offset);
|
|
selection.removeAllRanges();
|
|
|
|
if (start > end) {
|
|
selection.addRange(range);
|
|
selection.extend(endMarker.node, endMarker.offset);
|
|
} else {
|
|
range.setEnd(endMarker.node, endMarker.offset);
|
|
selection.addRange(range);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isTextNode(node) {
|
|
return node && node.nodeType === TEXT_NODE;
|
|
}
|
|
|
|
function containsNode(outerNode, innerNode) {
|
|
if (!outerNode || !innerNode) {
|
|
return false;
|
|
} else if (outerNode === innerNode) {
|
|
return true;
|
|
} else if (isTextNode(outerNode)) {
|
|
return false;
|
|
} else if (isTextNode(innerNode)) {
|
|
return containsNode(outerNode, innerNode.parentNode);
|
|
} else if ("contains" in outerNode) {
|
|
return outerNode.contains(innerNode);
|
|
} else if (outerNode.compareDocumentPosition) {
|
|
return !!(outerNode.compareDocumentPosition(innerNode) & 16);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isInDocument(node) {
|
|
return (
|
|
node &&
|
|
node.ownerDocument &&
|
|
containsNode(node.ownerDocument.documentElement, node)
|
|
);
|
|
}
|
|
|
|
function isSameOriginFrame(iframe) {
|
|
try {
|
|
// Accessing the contentDocument of a HTMLIframeElement can cause the browser
|
|
// to throw, e.g. if it has a cross-origin src attribute.
|
|
// Safari will show an error in the console when the access results in "Blocked a frame with origin". e.g:
|
|
// iframe.contentDocument.defaultView;
|
|
// A safety way is to access one of the cross origin properties: Window or Location
|
|
// Which might result in "SecurityError" DOM Exception and it is compatible to Safari.
|
|
// https://html.spec.whatwg.org/multipage/browsers.html#integration-with-idl
|
|
return typeof iframe.contentWindow.location.href === "string";
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getActiveElementDeep() {
|
|
var win = window;
|
|
var element = getActiveElement();
|
|
|
|
while (element instanceof win.HTMLIFrameElement) {
|
|
if (isSameOriginFrame(element)) {
|
|
win = element.contentWindow;
|
|
} else {
|
|
return element;
|
|
}
|
|
|
|
element = getActiveElement(win.document);
|
|
}
|
|
|
|
return element;
|
|
}
|
|
/**
|
|
* @ReactInputSelection: React input selection module. Based on Selection.js,
|
|
* but modified to be suitable for react and has a couple of bug fixes (doesn't
|
|
* assume buttons have range selections allowed).
|
|
* Input selection module for React.
|
|
*/
|
|
|
|
/**
|
|
* @hasSelectionCapabilities: we get the element types that support selection
|
|
* from https://html.spec.whatwg.org/#do-not-apply, looking at `selectionStart`
|
|
* and `selectionEnd` rows.
|
|
*/
|
|
|
|
function hasSelectionCapabilities(elem) {
|
|
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
|
|
return (
|
|
nodeName &&
|
|
((nodeName === "input" &&
|
|
(elem.type === "text" ||
|
|
elem.type === "search" ||
|
|
elem.type === "tel" ||
|
|
elem.type === "url" ||
|
|
elem.type === "password")) ||
|
|
nodeName === "textarea" ||
|
|
elem.contentEditable === "true")
|
|
);
|
|
}
|
|
function getSelectionInformation() {
|
|
var focusedElem = getActiveElementDeep();
|
|
return {
|
|
focusedElem: focusedElem,
|
|
selectionRange: hasSelectionCapabilities(focusedElem)
|
|
? getSelection(focusedElem)
|
|
: null
|
|
};
|
|
}
|
|
/**
|
|
* @restoreSelection: If any selection information was potentially lost,
|
|
* restore it. This is useful when performing operations that could remove dom
|
|
* nodes and place them back in, resulting in focus being lost.
|
|
*/
|
|
|
|
function restoreSelection(priorSelectionInformation) {
|
|
var curFocusedElem = getActiveElementDeep();
|
|
var priorFocusedElem = priorSelectionInformation.focusedElem;
|
|
var priorSelectionRange = priorSelectionInformation.selectionRange;
|
|
|
|
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
|
|
if (
|
|
priorSelectionRange !== null &&
|
|
hasSelectionCapabilities(priorFocusedElem)
|
|
) {
|
|
setSelection(priorFocusedElem, priorSelectionRange);
|
|
} // Focusing a node can change the scroll position, which is undesirable
|
|
|
|
var ancestors = [];
|
|
var ancestor = priorFocusedElem;
|
|
|
|
while ((ancestor = ancestor.parentNode)) {
|
|
if (ancestor.nodeType === ELEMENT_NODE) {
|
|
ancestors.push({
|
|
element: ancestor,
|
|
left: ancestor.scrollLeft,
|
|
top: ancestor.scrollTop
|
|
});
|
|
}
|
|
}
|
|
|
|
if (typeof priorFocusedElem.focus === "function") {
|
|
priorFocusedElem.focus();
|
|
}
|
|
|
|
for (var i = 0; i < ancestors.length; i++) {
|
|
var info = ancestors[i];
|
|
info.element.scrollLeft = info.left;
|
|
info.element.scrollTop = info.top;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @getSelection: Gets the selection bounds of a focused textarea, input or
|
|
* contentEditable node.
|
|
* -@input: Look up selection bounds of this input
|
|
* -@return {start: selectionStart, end: selectionEnd}
|
|
*/
|
|
|
|
function getSelection(input) {
|
|
var selection;
|
|
|
|
if ("selectionStart" in input) {
|
|
// Modern browser with input or textarea.
|
|
selection = {
|
|
start: input.selectionStart,
|
|
end: input.selectionEnd
|
|
};
|
|
} else {
|
|
// Content editable or old IE textarea.
|
|
selection = getOffsets(input);
|
|
}
|
|
|
|
return (
|
|
selection || {
|
|
start: 0,
|
|
end: 0
|
|
}
|
|
);
|
|
}
|
|
/**
|
|
* @setSelection: Sets the selection bounds of a textarea or input and focuses
|
|
* the input.
|
|
* -@input Set selection bounds of this input or textarea
|
|
* -@offsets Object of same form that is returned from get*
|
|
*/
|
|
|
|
function setSelection(input, offsets) {
|
|
var start = offsets.start;
|
|
var end = offsets.end;
|
|
|
|
if (end === undefined) {
|
|
end = start;
|
|
}
|
|
|
|
if ("selectionStart" in input) {
|
|
input.selectionStart = start;
|
|
input.selectionEnd = Math.min(end, input.value.length);
|
|
} else {
|
|
setOffsets(input, offsets);
|
|
}
|
|
}
|
|
|
|
var skipSelectionChangeEvent =
|
|
canUseDOM && "documentMode" in document && document.documentMode <= 11;
|
|
|
|
function registerEvents$3() {
|
|
registerTwoPhaseEvent("onSelect", [
|
|
"focusout",
|
|
"contextmenu",
|
|
"dragend",
|
|
"focusin",
|
|
"keydown",
|
|
"keyup",
|
|
"mousedown",
|
|
"mouseup",
|
|
"selectionchange"
|
|
]);
|
|
}
|
|
|
|
var activeElement$1 = null;
|
|
var activeElementInst$1 = null;
|
|
var lastSelection = null;
|
|
var mouseDown = false;
|
|
/**
|
|
* Get an object which is a unique representation of the current selection.
|
|
*
|
|
* The return value will not be consistent across nodes or browsers, but
|
|
* two identical selections on the same node will return identical objects.
|
|
*/
|
|
|
|
function getSelection$1(node) {
|
|
if ("selectionStart" in node && hasSelectionCapabilities(node)) {
|
|
return {
|
|
start: node.selectionStart,
|
|
end: node.selectionEnd
|
|
};
|
|
} else {
|
|
var win = (node.ownerDocument && node.ownerDocument.defaultView) || window;
|
|
var selection = win.getSelection();
|
|
return {
|
|
anchorNode: selection.anchorNode,
|
|
anchorOffset: selection.anchorOffset,
|
|
focusNode: selection.focusNode,
|
|
focusOffset: selection.focusOffset
|
|
};
|
|
}
|
|
}
|
|
/**
|
|
* Get document associated with the event target.
|
|
*/
|
|
|
|
function getEventTargetDocument(eventTarget) {
|
|
return eventTarget.window === eventTarget
|
|
? eventTarget.document
|
|
: eventTarget.nodeType === DOCUMENT_NODE
|
|
? eventTarget
|
|
: eventTarget.ownerDocument;
|
|
}
|
|
/**
|
|
* Poll selection to see whether it's changed.
|
|
*
|
|
* @param {object} nativeEvent
|
|
* @param {object} nativeEventTarget
|
|
* @return {?SyntheticEvent}
|
|
*/
|
|
|
|
function constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget) {
|
|
// Ensure we have the right element, and that the user is not dragging a
|
|
// selection (this matches native `select` event behavior). In HTML5, select
|
|
// fires only on input and textarea thus if there's no focused element we
|
|
// won't dispatch.
|
|
var doc = getEventTargetDocument(nativeEventTarget);
|
|
|
|
if (
|
|
mouseDown ||
|
|
activeElement$1 == null ||
|
|
activeElement$1 !== getActiveElement(doc)
|
|
) {
|
|
return;
|
|
} // Only fire when selection has actually changed.
|
|
|
|
var currentSelection = getSelection$1(activeElement$1);
|
|
|
|
if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
|
|
lastSelection = currentSelection;
|
|
var listeners = accumulateTwoPhaseListeners(
|
|
activeElementInst$1,
|
|
"onSelect"
|
|
);
|
|
|
|
if (listeners.length > 0) {
|
|
var event = new SyntheticEvent(
|
|
"onSelect",
|
|
"select",
|
|
null,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
dispatchQueue.push({
|
|
event: event,
|
|
listeners: listeners
|
|
});
|
|
event.target = activeElement$1;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* This plugin creates an `onSelect` event that normalizes select events
|
|
* across form elements.
|
|
*
|
|
* Supported elements are:
|
|
* - input (see `isTextInputElement`)
|
|
* - textarea
|
|
* - contentEditable
|
|
*
|
|
* This differs from native browser implementations in the following ways:
|
|
* - Fires on contentEditable fields as well as inputs.
|
|
* - Fires for collapsed selection.
|
|
* - Fires after user input.
|
|
*/
|
|
|
|
function extractEvents$3(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
) {
|
|
var targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
|
|
|
|
switch (domEventName) {
|
|
// Track the input node that has focus.
|
|
case "focusin":
|
|
if (
|
|
isTextInputElement(targetNode) ||
|
|
targetNode.contentEditable === "true"
|
|
) {
|
|
activeElement$1 = targetNode;
|
|
activeElementInst$1 = targetInst;
|
|
lastSelection = null;
|
|
}
|
|
|
|
break;
|
|
|
|
case "focusout":
|
|
activeElement$1 = null;
|
|
activeElementInst$1 = null;
|
|
lastSelection = null;
|
|
break;
|
|
// Don't fire the event while the user is dragging. This matches the
|
|
// semantics of the native select event.
|
|
|
|
case "mousedown":
|
|
mouseDown = true;
|
|
break;
|
|
|
|
case "contextmenu":
|
|
case "mouseup":
|
|
case "dragend":
|
|
mouseDown = false;
|
|
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
|
|
break;
|
|
// Chrome and IE fire non-standard event when selection is changed (and
|
|
// sometimes when it hasn't). IE's event fires out of order with respect
|
|
// to key and input events on deletion, so we discard it.
|
|
//
|
|
// Firefox doesn't support selectionchange, so check selection status
|
|
// after each key entry. The selection changes after keydown and before
|
|
// keyup, but we check on keydown as well in the case of holding down a
|
|
// key, when multiple keydown events are fired but only one keyup is.
|
|
// This is also our approach for IE handling, for the reason above.
|
|
|
|
case "selectionchange":
|
|
if (skipSelectionChangeEvent) {
|
|
break;
|
|
}
|
|
|
|
// falls through
|
|
|
|
case "keydown":
|
|
case "keyup":
|
|
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a mapping of standard vendor prefixes using the defined style property and event name.
|
|
*
|
|
* @param {string} styleProp
|
|
* @param {string} eventName
|
|
* @returns {object}
|
|
*/
|
|
|
|
function makePrefixMap(styleProp, eventName) {
|
|
var prefixes = {};
|
|
prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
|
|
prefixes["Webkit" + styleProp] = "webkit" + eventName;
|
|
prefixes["Moz" + styleProp] = "moz" + eventName;
|
|
return prefixes;
|
|
}
|
|
/**
|
|
* A list of event names to a configurable list of vendor prefixes.
|
|
*/
|
|
|
|
var vendorPrefixes = {
|
|
animationend: makePrefixMap("Animation", "AnimationEnd"),
|
|
animationiteration: makePrefixMap("Animation", "AnimationIteration"),
|
|
animationstart: makePrefixMap("Animation", "AnimationStart"),
|
|
transitionend: makePrefixMap("Transition", "TransitionEnd")
|
|
};
|
|
/**
|
|
* Event names that have already been detected and prefixed (if applicable).
|
|
*/
|
|
|
|
var prefixedEventNames = {};
|
|
/**
|
|
* Element to check for prefixes on.
|
|
*/
|
|
|
|
var style = {};
|
|
/**
|
|
* Bootstrap if a DOM exists.
|
|
*/
|
|
|
|
if (canUseDOM) {
|
|
style = document.createElement("div").style; // On some platforms, in particular some releases of Android 4.x,
|
|
// the un-prefixed "animation" and "transition" properties are defined on the
|
|
// style object but the events that fire will still be prefixed, so we need
|
|
// to check if the un-prefixed events are usable, and if not remove them from the map.
|
|
|
|
if (!("AnimationEvent" in window)) {
|
|
delete vendorPrefixes.animationend.animation;
|
|
delete vendorPrefixes.animationiteration.animation;
|
|
delete vendorPrefixes.animationstart.animation;
|
|
} // Same as above
|
|
|
|
if (!("TransitionEvent" in window)) {
|
|
delete vendorPrefixes.transitionend.transition;
|
|
}
|
|
}
|
|
/**
|
|
* Attempts to determine the correct vendor prefixed event name.
|
|
*
|
|
* @param {string} eventName
|
|
* @returns {string}
|
|
*/
|
|
|
|
function getVendorPrefixedEventName(eventName) {
|
|
if (prefixedEventNames[eventName]) {
|
|
return prefixedEventNames[eventName];
|
|
} else if (!vendorPrefixes[eventName]) {
|
|
return eventName;
|
|
}
|
|
|
|
var prefixMap = vendorPrefixes[eventName];
|
|
|
|
for (var styleProp in prefixMap) {
|
|
if (prefixMap.hasOwnProperty(styleProp) && styleProp in style) {
|
|
return (prefixedEventNames[eventName] = prefixMap[styleProp]);
|
|
}
|
|
}
|
|
|
|
return eventName;
|
|
}
|
|
|
|
var ANIMATION_END = getVendorPrefixedEventName("animationend");
|
|
var ANIMATION_ITERATION = getVendorPrefixedEventName("animationiteration");
|
|
var ANIMATION_START = getVendorPrefixedEventName("animationstart");
|
|
var TRANSITION_END = getVendorPrefixedEventName("transitionend");
|
|
|
|
var topLevelEventsToReactNames = new Map(); // NOTE: Capitalization is important in this list!
|
|
//
|
|
// E.g. it needs "pointerDown", not "pointerdown".
|
|
// This is because we derive both React name ("onPointerDown")
|
|
// and DOM name ("pointerdown") from the same list.
|
|
//
|
|
// Exceptions that don't match this convention are listed separately.
|
|
//
|
|
// prettier-ignore
|
|
|
|
var simpleEventPluginEvents = ['abort', 'auxClick', 'cancel', 'canPlay', 'canPlayThrough', 'click', 'close', 'contextMenu', 'copy', 'cut', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'gotPointerCapture', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'lostPointerCapture', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'pointerCancel', 'pointerDown', 'pointerMove', 'pointerOut', 'pointerOver', 'pointerUp', 'progress', 'rateChange', 'reset', 'resize', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'touchCancel', 'touchEnd', 'touchStart', 'volumeChange', 'scroll', 'toggle', 'touchMove', 'waiting', 'wheel'];
|
|
|
|
{
|
|
// Special case: these two events don't have on* React handler
|
|
// and are only accessible via the createEventHandle API.
|
|
topLevelEventsToReactNames.set("beforeblur", null);
|
|
topLevelEventsToReactNames.set("afterblur", null);
|
|
}
|
|
|
|
function registerSimpleEvent(domEventName, reactName) {
|
|
topLevelEventsToReactNames.set(domEventName, reactName);
|
|
registerTwoPhaseEvent(reactName, [domEventName]);
|
|
}
|
|
|
|
function registerSimpleEvents() {
|
|
for (var i = 0; i < simpleEventPluginEvents.length; i++) {
|
|
var eventName = simpleEventPluginEvents[i];
|
|
var domEventName = eventName.toLowerCase();
|
|
var capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1);
|
|
registerSimpleEvent(domEventName, "on" + capitalizedEvent);
|
|
} // Special cases where event names don't match.
|
|
|
|
registerSimpleEvent(ANIMATION_END, "onAnimationEnd");
|
|
registerSimpleEvent(ANIMATION_ITERATION, "onAnimationIteration");
|
|
registerSimpleEvent(ANIMATION_START, "onAnimationStart");
|
|
registerSimpleEvent("dblclick", "onDoubleClick");
|
|
registerSimpleEvent("focusin", "onFocus");
|
|
registerSimpleEvent("focusout", "onBlur");
|
|
registerSimpleEvent(TRANSITION_END, "onTransitionEnd");
|
|
}
|
|
|
|
function extractEvents$4(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
) {
|
|
var reactName = topLevelEventsToReactNames.get(domEventName);
|
|
|
|
if (reactName === undefined) {
|
|
return;
|
|
}
|
|
|
|
var SyntheticEventCtor = SyntheticEvent;
|
|
var reactEventType = domEventName;
|
|
|
|
switch (domEventName) {
|
|
case "keypress":
|
|
// Firefox creates a keypress event for function keys too. This removes
|
|
// the unwanted keypress events. Enter is however both printable and
|
|
// non-printable. One would expect Tab to be as well (but it isn't).
|
|
if (getEventCharCode(nativeEvent) === 0) {
|
|
return;
|
|
}
|
|
|
|
/* falls through */
|
|
|
|
case "keydown":
|
|
case "keyup":
|
|
SyntheticEventCtor = SyntheticKeyboardEvent;
|
|
break;
|
|
|
|
case "focusin":
|
|
reactEventType = "focus";
|
|
SyntheticEventCtor = SyntheticFocusEvent;
|
|
break;
|
|
|
|
case "focusout":
|
|
reactEventType = "blur";
|
|
SyntheticEventCtor = SyntheticFocusEvent;
|
|
break;
|
|
|
|
case "beforeblur":
|
|
case "afterblur":
|
|
SyntheticEventCtor = SyntheticFocusEvent;
|
|
break;
|
|
|
|
case "click":
|
|
// Firefox creates a click event on right mouse clicks. This removes the
|
|
// unwanted click events.
|
|
if (nativeEvent.button === 2) {
|
|
return;
|
|
}
|
|
|
|
/* falls through */
|
|
|
|
case "auxclick":
|
|
case "dblclick":
|
|
case "mousedown":
|
|
case "mousemove":
|
|
case "mouseup": // TODO: Disabled elements should not respond to mouse events
|
|
|
|
/* falls through */
|
|
|
|
case "mouseout":
|
|
case "mouseover":
|
|
case "contextmenu":
|
|
SyntheticEventCtor = SyntheticMouseEvent;
|
|
break;
|
|
|
|
case "drag":
|
|
case "dragend":
|
|
case "dragenter":
|
|
case "dragexit":
|
|
case "dragleave":
|
|
case "dragover":
|
|
case "dragstart":
|
|
case "drop":
|
|
SyntheticEventCtor = SyntheticDragEvent;
|
|
break;
|
|
|
|
case "touchcancel":
|
|
case "touchend":
|
|
case "touchmove":
|
|
case "touchstart":
|
|
SyntheticEventCtor = SyntheticTouchEvent;
|
|
break;
|
|
|
|
case ANIMATION_END:
|
|
case ANIMATION_ITERATION:
|
|
case ANIMATION_START:
|
|
SyntheticEventCtor = SyntheticAnimationEvent;
|
|
break;
|
|
|
|
case TRANSITION_END:
|
|
SyntheticEventCtor = SyntheticTransitionEvent;
|
|
break;
|
|
|
|
case "scroll":
|
|
SyntheticEventCtor = SyntheticUIEvent;
|
|
break;
|
|
|
|
case "wheel":
|
|
SyntheticEventCtor = SyntheticWheelEvent;
|
|
break;
|
|
|
|
case "copy":
|
|
case "cut":
|
|
case "paste":
|
|
SyntheticEventCtor = SyntheticClipboardEvent;
|
|
break;
|
|
|
|
case "gotpointercapture":
|
|
case "lostpointercapture":
|
|
case "pointercancel":
|
|
case "pointerdown":
|
|
case "pointermove":
|
|
case "pointerout":
|
|
case "pointerover":
|
|
case "pointerup":
|
|
SyntheticEventCtor = SyntheticPointerEvent;
|
|
break;
|
|
}
|
|
|
|
var inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
|
|
|
|
if (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) {
|
|
var listeners = accumulateEventHandleNonManagedNodeListeners(
|
|
// TODO: this cast may not make sense for events like
|
|
// "focus" where React listens to e.g. "focusin".
|
|
reactEventType,
|
|
targetContainer,
|
|
inCapturePhase
|
|
);
|
|
|
|
if (listeners.length > 0) {
|
|
// Intentionally create event lazily.
|
|
var event = new SyntheticEventCtor(
|
|
reactName,
|
|
reactEventType,
|
|
null,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
dispatchQueue.push({
|
|
event: event,
|
|
listeners: listeners
|
|
});
|
|
}
|
|
} else {
|
|
// Some events don't bubble in the browser.
|
|
// In the past, React has always bubbled them, but this can be surprising.
|
|
// We're going to try aligning closer to the browser behavior by not bubbling
|
|
// them in React either. We'll start by not bubbling onScroll, and then expand.
|
|
var accumulateTargetOnly =
|
|
!inCapturePhase && // TODO: ideally, we'd eventually add all events from
|
|
// nonDelegatedEvents list in DOMPluginEventSystem.
|
|
// Then we can remove this special list.
|
|
// This is a breaking change that can wait until React 18.
|
|
domEventName === "scroll";
|
|
|
|
var _listeners = accumulateSinglePhaseListeners(
|
|
targetInst,
|
|
reactName,
|
|
nativeEvent.type,
|
|
inCapturePhase,
|
|
accumulateTargetOnly,
|
|
nativeEvent
|
|
);
|
|
|
|
if (_listeners.length > 0) {
|
|
// Intentionally create event lazily.
|
|
var _event = new SyntheticEventCtor(
|
|
reactName,
|
|
reactEventType,
|
|
null,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
|
|
dispatchQueue.push({
|
|
event: _event,
|
|
listeners: _listeners
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
registerSimpleEvents();
|
|
registerEvents$2();
|
|
registerEvents$1();
|
|
registerEvents$3();
|
|
registerEvents();
|
|
|
|
function extractEvents$5(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
) {
|
|
// TODO: we should remove the concept of a "SimpleEventPlugin".
|
|
// This is the basic functionality of the event system. All
|
|
// the other plugins are essentially polyfills. So the plugin
|
|
// should probably be inlined somewhere and have its logic
|
|
// be core the to event system. This would potentially allow
|
|
// us to ship builds of React without the polyfilled plugins below.
|
|
extractEvents$4(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
);
|
|
var shouldProcessPolyfillPlugins =
|
|
(eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0; // We don't process these events unless we are in the
|
|
// event's native "bubble" phase, which means that we're
|
|
// not in the capture phase. That's because we emulate
|
|
// the capture phase here still. This is a trade-off,
|
|
// because in an ideal world we would not emulate and use
|
|
// the phases properly, like we do with the SimpleEvent
|
|
// plugin. However, the plugins below either expect
|
|
// emulation (EnterLeave) or use state localized to that
|
|
// plugin (BeforeInput, Change, Select). The state in
|
|
// these modules complicates things, as you'll essentially
|
|
// get the case where the capture phase event might change
|
|
// state, only for the following bubble event to come in
|
|
// later and not trigger anything as the state now
|
|
// invalidates the heuristics of the event plugin. We
|
|
// could alter all these plugins to work in such ways, but
|
|
// that might cause other unknown side-effects that we
|
|
// can't foresee right now.
|
|
|
|
if (shouldProcessPolyfillPlugins) {
|
|
extractEvents$2(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
extractEvents$1(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
extractEvents$3(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
extractEvents(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget
|
|
);
|
|
}
|
|
} // List of events that need to be individually attached to media elements.
|
|
|
|
var mediaEventTypes = [
|
|
"abort",
|
|
"canplay",
|
|
"canplaythrough",
|
|
"durationchange",
|
|
"emptied",
|
|
"encrypted",
|
|
"ended",
|
|
"error",
|
|
"loadeddata",
|
|
"loadedmetadata",
|
|
"loadstart",
|
|
"pause",
|
|
"play",
|
|
"playing",
|
|
"progress",
|
|
"ratechange",
|
|
"resize",
|
|
"seeked",
|
|
"seeking",
|
|
"stalled",
|
|
"suspend",
|
|
"timeupdate",
|
|
"volumechange",
|
|
"waiting"
|
|
]; // We should not delegate these events to the container, but rather
|
|
// set them on the actual target element itself. This is primarily
|
|
// because these events do not consistently bubble in the DOM.
|
|
|
|
var nonDelegatedEvents = new Set(
|
|
["cancel", "close", "invalid", "load", "scroll", "toggle"].concat(
|
|
mediaEventTypes
|
|
)
|
|
);
|
|
|
|
function executeDispatch(event, listener, currentTarget) {
|
|
var type = event.type || "unknown-event";
|
|
event.currentTarget = currentTarget;
|
|
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
|
|
event.currentTarget = null;
|
|
}
|
|
|
|
function processDispatchQueueItemsInOrder(
|
|
event,
|
|
dispatchListeners,
|
|
inCapturePhase
|
|
) {
|
|
var previousInstance;
|
|
|
|
if (inCapturePhase) {
|
|
for (var i = dispatchListeners.length - 1; i >= 0; i--) {
|
|
var _dispatchListeners$i = dispatchListeners[i],
|
|
instance = _dispatchListeners$i.instance,
|
|
currentTarget = _dispatchListeners$i.currentTarget,
|
|
listener = _dispatchListeners$i.listener;
|
|
|
|
if (instance !== previousInstance && event.isPropagationStopped()) {
|
|
return;
|
|
}
|
|
|
|
executeDispatch(event, listener, currentTarget);
|
|
previousInstance = instance;
|
|
}
|
|
} else {
|
|
for (var _i = 0; _i < dispatchListeners.length; _i++) {
|
|
var _dispatchListeners$_i = dispatchListeners[_i],
|
|
_instance = _dispatchListeners$_i.instance,
|
|
_currentTarget = _dispatchListeners$_i.currentTarget,
|
|
_listener = _dispatchListeners$_i.listener;
|
|
|
|
if (_instance !== previousInstance && event.isPropagationStopped()) {
|
|
return;
|
|
}
|
|
|
|
executeDispatch(event, _listener, _currentTarget);
|
|
previousInstance = _instance;
|
|
}
|
|
}
|
|
}
|
|
|
|
function processDispatchQueue(dispatchQueue, eventSystemFlags) {
|
|
var inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
|
|
|
|
for (var i = 0; i < dispatchQueue.length; i++) {
|
|
var _dispatchQueue$i = dispatchQueue[i],
|
|
event = _dispatchQueue$i.event,
|
|
listeners = _dispatchQueue$i.listeners;
|
|
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling.
|
|
} // This would be a good time to rethrow if any of the event handlers threw.
|
|
|
|
rethrowCaughtError();
|
|
}
|
|
|
|
function dispatchEventsForPlugins(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
nativeEvent,
|
|
targetInst,
|
|
targetContainer
|
|
) {
|
|
var nativeEventTarget = getEventTarget(nativeEvent);
|
|
var dispatchQueue = [];
|
|
extractEvents$5(
|
|
dispatchQueue,
|
|
domEventName,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
eventSystemFlags,
|
|
targetContainer
|
|
);
|
|
processDispatchQueue(dispatchQueue, eventSystemFlags);
|
|
}
|
|
|
|
function listenToNonDelegatedEvent(domEventName, targetElement) {
|
|
{
|
|
if (!nonDelegatedEvents.has(domEventName)) {
|
|
error(
|
|
'Did not expect a listenToNonDelegatedEvent() call for "%s". ' +
|
|
"This is a bug in React. Please file an issue.",
|
|
domEventName
|
|
);
|
|
}
|
|
}
|
|
|
|
var isCapturePhaseListener = false;
|
|
var listenerSet = getEventListenerSet(targetElement);
|
|
var listenerSetKey = getListenerSetKey(domEventName, isCapturePhaseListener);
|
|
|
|
if (!listenerSet.has(listenerSetKey)) {
|
|
addTrappedEventListener(
|
|
targetElement,
|
|
domEventName,
|
|
IS_NON_DELEGATED,
|
|
isCapturePhaseListener
|
|
);
|
|
listenerSet.add(listenerSetKey);
|
|
}
|
|
}
|
|
function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
|
|
{
|
|
if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) {
|
|
error(
|
|
'Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' +
|
|
"This is a bug in React. Please file an issue.",
|
|
domEventName
|
|
);
|
|
}
|
|
}
|
|
|
|
var eventSystemFlags = 0;
|
|
|
|
if (isCapturePhaseListener) {
|
|
eventSystemFlags |= IS_CAPTURE_PHASE;
|
|
}
|
|
|
|
addTrappedEventListener(
|
|
target,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
isCapturePhaseListener
|
|
);
|
|
} // This is only used by createEventHandle when the
|
|
// target is not a DOM element. E.g. window.
|
|
|
|
function listenToNativeEventForNonManagedEventTarget(
|
|
domEventName,
|
|
isCapturePhaseListener,
|
|
target
|
|
) {
|
|
var eventSystemFlags = IS_EVENT_HANDLE_NON_MANAGED_NODE;
|
|
var listenerSet = getEventListenerSet(target);
|
|
var listenerSetKey = getListenerSetKey(domEventName, isCapturePhaseListener);
|
|
|
|
if (!listenerSet.has(listenerSetKey)) {
|
|
if (isCapturePhaseListener) {
|
|
eventSystemFlags |= IS_CAPTURE_PHASE;
|
|
}
|
|
|
|
addTrappedEventListener(
|
|
target,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
isCapturePhaseListener
|
|
);
|
|
listenerSet.add(listenerSetKey);
|
|
}
|
|
}
|
|
var listeningMarker =
|
|
"_reactListening" +
|
|
Math.random()
|
|
.toString(36)
|
|
.slice(2);
|
|
function listenToAllSupportedEvents(rootContainerElement) {
|
|
if (!rootContainerElement[listeningMarker]) {
|
|
rootContainerElement[listeningMarker] = true;
|
|
allNativeEvents.forEach(function(domEventName) {
|
|
// We handle selectionchange separately because it
|
|
// doesn't bubble and needs to be on the document.
|
|
if (domEventName !== "selectionchange") {
|
|
if (!nonDelegatedEvents.has(domEventName)) {
|
|
listenToNativeEvent(domEventName, false, rootContainerElement);
|
|
}
|
|
|
|
listenToNativeEvent(domEventName, true, rootContainerElement);
|
|
}
|
|
});
|
|
var ownerDocument =
|
|
rootContainerElement.nodeType === DOCUMENT_NODE
|
|
? rootContainerElement
|
|
: rootContainerElement.ownerDocument;
|
|
|
|
if (ownerDocument !== null) {
|
|
// The selectionchange event also needs deduplication
|
|
// but it is attached to the document.
|
|
if (!ownerDocument[listeningMarker]) {
|
|
ownerDocument[listeningMarker] = true;
|
|
listenToNativeEvent("selectionchange", false, ownerDocument);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function addTrappedEventListener(
|
|
targetContainer,
|
|
domEventName,
|
|
eventSystemFlags,
|
|
isCapturePhaseListener,
|
|
isDeferredListenerForLegacyFBSupport
|
|
) {
|
|
var listener = createEventListenerWrapperWithPriority(
|
|
targetContainer,
|
|
domEventName,
|
|
eventSystemFlags
|
|
); // If passive option is not supported, then the event will be
|
|
// active and not passive.
|
|
|
|
var isPassiveListener = undefined;
|
|
|
|
if (passiveBrowserEventsSupported) {
|
|
// Browsers introduced an intervention, making these events
|
|
// passive by default on document. React doesn't bind them
|
|
// to document anymore, but changing this now would undo
|
|
// the performance wins from the change. So we emulate
|
|
// the existing behavior manually on the roots now.
|
|
// https://github.com/facebook/react/issues/19651
|
|
if (
|
|
domEventName === "touchstart" ||
|
|
domEventName === "touchmove" ||
|
|
domEventName === "wheel"
|
|
) {
|
|
isPassiveListener = true;
|
|
}
|
|
}
|
|
|
|
targetContainer = targetContainer;
|
|
var unsubscribeListener; // When legacyFBSupport is enabled, it's for when we
|
|
|
|
if (isCapturePhaseListener) {
|
|
if (isPassiveListener !== undefined) {
|
|
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
|
|
targetContainer,
|
|
domEventName,
|
|
listener,
|
|
isPassiveListener
|
|
);
|
|
} else {
|
|
unsubscribeListener = addEventCaptureListener(
|
|
targetContainer,
|
|
domEventName,
|
|
listener
|
|
);
|
|
}
|
|
} else {
|
|
if (isPassiveListener !== undefined) {
|
|
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
|
|
targetContainer,
|
|
domEventName,
|
|
listener,
|
|
isPassiveListener
|
|
);
|
|
} else {
|
|
unsubscribeListener = addEventBubbleListener(
|
|
targetContainer,
|
|
domEventName,
|
|
listener
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isMatchingRootContainer(grandContainer, targetContainer) {
|
|
return (
|
|
grandContainer === targetContainer ||
|
|
(grandContainer.nodeType === COMMENT_NODE &&
|
|
grandContainer.parentNode === targetContainer)
|
|
);
|
|
}
|
|
|
|
function dispatchEventForPluginEventSystem(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
nativeEvent,
|
|
targetInst,
|
|
targetContainer
|
|
) {
|
|
var ancestorInst = targetInst;
|
|
|
|
if (
|
|
(eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
|
|
(eventSystemFlags & IS_NON_DELEGATED) === 0
|
|
) {
|
|
var targetContainerNode = targetContainer; // If we are using the legacy FB support flag, we
|
|
|
|
if (targetInst !== null) {
|
|
// The below logic attempts to work out if we need to change
|
|
// the target fiber to a different ancestor. We had similar logic
|
|
// in the legacy event system, except the big difference between
|
|
// systems is that the modern event system now has an event listener
|
|
// attached to each React Root and React Portal Root. Together,
|
|
// the DOM nodes representing these roots are the "rootContainer".
|
|
// To figure out which ancestor instance we should use, we traverse
|
|
// up the fiber tree from the target instance and attempt to find
|
|
// root boundaries that match that of our current "rootContainer".
|
|
// If we find that "rootContainer", we find the parent fiber
|
|
// sub-tree for that root and make that our ancestor instance.
|
|
var node = targetInst;
|
|
|
|
mainLoop: while (true) {
|
|
if (node === null) {
|
|
return;
|
|
}
|
|
|
|
var nodeTag = node.tag;
|
|
|
|
if (nodeTag === HostRoot || nodeTag === HostPortal) {
|
|
var container = node.stateNode.containerInfo;
|
|
|
|
if (isMatchingRootContainer(container, targetContainerNode)) {
|
|
break;
|
|
}
|
|
|
|
if (nodeTag === HostPortal) {
|
|
// The target is a portal, but it's not the rootContainer we're looking for.
|
|
// Normally portals handle their own events all the way down to the root.
|
|
// So we should be able to stop now. However, we don't know if this portal
|
|
// was part of *our* root.
|
|
var grandNode = node.return;
|
|
|
|
while (grandNode !== null) {
|
|
var grandTag = grandNode.tag;
|
|
|
|
if (grandTag === HostRoot || grandTag === HostPortal) {
|
|
var grandContainer = grandNode.stateNode.containerInfo;
|
|
|
|
if (
|
|
isMatchingRootContainer(grandContainer, targetContainerNode)
|
|
) {
|
|
// This is the rootContainer we're looking for and we found it as
|
|
// a parent of the Portal. That means we can ignore it because the
|
|
// Portal will bubble through to us.
|
|
return;
|
|
}
|
|
}
|
|
|
|
grandNode = grandNode.return;
|
|
}
|
|
} // Now we need to find it's corresponding host fiber in the other
|
|
// tree. To do this we can use getClosestInstanceFromNode, but we
|
|
// need to validate that the fiber is a host instance, otherwise
|
|
// we need to traverse up through the DOM till we find the correct
|
|
// node that is from the other tree.
|
|
|
|
while (container !== null) {
|
|
var parentNode = getClosestInstanceFromNode(container);
|
|
|
|
if (parentNode === null) {
|
|
return;
|
|
}
|
|
|
|
var parentTag = parentNode.tag;
|
|
|
|
if (
|
|
parentTag === HostComponent ||
|
|
parentTag === HostText ||
|
|
parentTag === HostResource ||
|
|
parentTag === HostSingleton
|
|
) {
|
|
node = ancestorInst = parentNode;
|
|
continue mainLoop;
|
|
}
|
|
|
|
container = container.parentNode;
|
|
}
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
}
|
|
}
|
|
|
|
batchedUpdates(function() {
|
|
return dispatchEventsForPlugins(
|
|
domEventName,
|
|
eventSystemFlags,
|
|
nativeEvent,
|
|
ancestorInst,
|
|
targetContainer
|
|
);
|
|
});
|
|
}
|
|
|
|
function createDispatchListener(instance, listener, currentTarget) {
|
|
return {
|
|
instance: instance,
|
|
listener: listener,
|
|
currentTarget: currentTarget
|
|
};
|
|
}
|
|
|
|
function accumulateSinglePhaseListeners(
|
|
targetFiber,
|
|
reactName,
|
|
nativeEventType,
|
|
inCapturePhase,
|
|
accumulateTargetOnly,
|
|
nativeEvent
|
|
) {
|
|
var captureName = reactName !== null ? reactName + "Capture" : null;
|
|
var reactEventName = inCapturePhase ? captureName : reactName;
|
|
var listeners = [];
|
|
var instance = targetFiber;
|
|
var lastHostComponent = null; // Accumulate all instances and listeners via the target -> root path.
|
|
|
|
while (instance !== null) {
|
|
var _instance2 = instance,
|
|
stateNode = _instance2.stateNode,
|
|
tag = _instance2.tag; // Handle listeners that are on HostComponents (i.e. <div>)
|
|
|
|
if (
|
|
(tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton) &&
|
|
stateNode !== null
|
|
) {
|
|
lastHostComponent = stateNode; // createEventHandle listeners
|
|
|
|
{
|
|
var eventHandlerListeners = getEventHandlerListeners(lastHostComponent);
|
|
|
|
if (eventHandlerListeners !== null) {
|
|
eventHandlerListeners.forEach(function(entry) {
|
|
if (
|
|
entry.type === nativeEventType &&
|
|
entry.capture === inCapturePhase
|
|
) {
|
|
listeners.push(
|
|
createDispatchListener(
|
|
instance,
|
|
entry.callback,
|
|
lastHostComponent
|
|
)
|
|
);
|
|
}
|
|
});
|
|
}
|
|
} // Standard React on* listeners, i.e. onClick or onClickCapture
|
|
|
|
if (reactEventName !== null) {
|
|
var listener = getListener(instance, reactEventName);
|
|
|
|
if (listener != null) {
|
|
listeners.push(
|
|
createDispatchListener(instance, listener, lastHostComponent)
|
|
);
|
|
}
|
|
}
|
|
} else if (
|
|
tag === ScopeComponent &&
|
|
lastHostComponent !== null &&
|
|
stateNode !== null
|
|
) {
|
|
// Scopes
|
|
var reactScopeInstance = stateNode;
|
|
|
|
var _eventHandlerListeners = getEventHandlerListeners(reactScopeInstance);
|
|
|
|
if (_eventHandlerListeners !== null) {
|
|
_eventHandlerListeners.forEach(function(entry) {
|
|
if (
|
|
entry.type === nativeEventType &&
|
|
entry.capture === inCapturePhase
|
|
) {
|
|
listeners.push(
|
|
createDispatchListener(
|
|
instance,
|
|
entry.callback,
|
|
lastHostComponent
|
|
)
|
|
);
|
|
}
|
|
});
|
|
}
|
|
} // If we are only accumulating events for the target, then we don't
|
|
// continue to propagate through the React fiber tree to find other
|
|
// listeners.
|
|
|
|
if (accumulateTargetOnly) {
|
|
break;
|
|
} // If we are processing the onBeforeBlur event, then we need to take
|
|
// into consideration that part of the React tree might have been hidden
|
|
// or deleted (as we're invoking this event during commit). We can find
|
|
// this out by checking if intercept fiber set on the event matches the
|
|
// current instance fiber. In which case, we should clear all existing
|
|
// listeners.
|
|
|
|
if (nativeEvent.type === "beforeblur") {
|
|
// $FlowFixMe[prop-missing] internal field
|
|
var detachedInterceptFiber = nativeEvent._detachedInterceptFiber;
|
|
|
|
if (
|
|
detachedInterceptFiber !== null &&
|
|
(detachedInterceptFiber === instance ||
|
|
detachedInterceptFiber === instance.alternate)
|
|
) {
|
|
listeners = [];
|
|
}
|
|
}
|
|
|
|
instance = instance.return;
|
|
}
|
|
|
|
return listeners;
|
|
} // We should only use this function for:
|
|
// - BeforeInputEventPlugin
|
|
// - ChangeEventPlugin
|
|
// - SelectEventPlugin
|
|
// This is because we only process these plugins
|
|
// in the bubble phase, so we need to accumulate two
|
|
// phase event listeners (via emulation).
|
|
|
|
function accumulateTwoPhaseListeners(targetFiber, reactName) {
|
|
var captureName = reactName + "Capture";
|
|
var listeners = [];
|
|
var instance = targetFiber; // Accumulate all instances and listeners via the target -> root path.
|
|
|
|
while (instance !== null) {
|
|
var _instance3 = instance,
|
|
stateNode = _instance3.stateNode,
|
|
tag = _instance3.tag; // Handle listeners that are on HostComponents (i.e. <div>)
|
|
|
|
if (
|
|
(tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton) &&
|
|
stateNode !== null
|
|
) {
|
|
var currentTarget = stateNode;
|
|
var captureListener = getListener(instance, captureName);
|
|
|
|
if (captureListener != null) {
|
|
listeners.unshift(
|
|
createDispatchListener(instance, captureListener, currentTarget)
|
|
);
|
|
}
|
|
|
|
var bubbleListener = getListener(instance, reactName);
|
|
|
|
if (bubbleListener != null) {
|
|
listeners.push(
|
|
createDispatchListener(instance, bubbleListener, currentTarget)
|
|
);
|
|
}
|
|
}
|
|
|
|
instance = instance.return;
|
|
}
|
|
|
|
return listeners;
|
|
}
|
|
|
|
function getParent(inst) {
|
|
if (inst === null) {
|
|
return null;
|
|
}
|
|
|
|
do {
|
|
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
inst = inst.return; // TODO: If this is a HostRoot we might want to bail out.
|
|
// That is depending on if we want nested subtrees (layers) to bubble
|
|
// events to their parent. We could also go through parentNode on the
|
|
// host node but that wouldn't work for React Native and doesn't let us
|
|
// do the portal feature.
|
|
} while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);
|
|
|
|
if (inst) {
|
|
return inst;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
/**
|
|
* Return the lowest common ancestor of A and B, or null if they are in
|
|
* different trees.
|
|
*/
|
|
|
|
function getLowestCommonAncestor(instA, instB) {
|
|
var nodeA = instA;
|
|
var nodeB = instB;
|
|
var depthA = 0;
|
|
|
|
for (var tempA = nodeA; tempA; tempA = getParent(tempA)) {
|
|
depthA++;
|
|
}
|
|
|
|
var depthB = 0;
|
|
|
|
for (var tempB = nodeB; tempB; tempB = getParent(tempB)) {
|
|
depthB++;
|
|
} // If A is deeper, crawl up.
|
|
|
|
while (depthA - depthB > 0) {
|
|
nodeA = getParent(nodeA);
|
|
depthA--;
|
|
} // If B is deeper, crawl up.
|
|
|
|
while (depthB - depthA > 0) {
|
|
nodeB = getParent(nodeB);
|
|
depthB--;
|
|
} // Walk in lockstep until we find a match.
|
|
|
|
var depth = depthA;
|
|
|
|
while (depth--) {
|
|
if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) {
|
|
return nodeA;
|
|
}
|
|
|
|
nodeA = getParent(nodeA);
|
|
nodeB = getParent(nodeB);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function accumulateEnterLeaveListenersForEvent(
|
|
dispatchQueue,
|
|
event,
|
|
target,
|
|
common,
|
|
inCapturePhase
|
|
) {
|
|
var registrationName = event._reactName;
|
|
var listeners = [];
|
|
var instance = target;
|
|
|
|
while (instance !== null) {
|
|
if (instance === common) {
|
|
break;
|
|
}
|
|
|
|
var _instance4 = instance,
|
|
alternate = _instance4.alternate,
|
|
stateNode = _instance4.stateNode,
|
|
tag = _instance4.tag;
|
|
|
|
if (alternate !== null && alternate === common) {
|
|
break;
|
|
}
|
|
|
|
if (
|
|
(tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton) &&
|
|
stateNode !== null
|
|
) {
|
|
var currentTarget = stateNode;
|
|
|
|
if (inCapturePhase) {
|
|
var captureListener = getListener(instance, registrationName);
|
|
|
|
if (captureListener != null) {
|
|
listeners.unshift(
|
|
createDispatchListener(instance, captureListener, currentTarget)
|
|
);
|
|
}
|
|
} else if (!inCapturePhase) {
|
|
var bubbleListener = getListener(instance, registrationName);
|
|
|
|
if (bubbleListener != null) {
|
|
listeners.push(
|
|
createDispatchListener(instance, bubbleListener, currentTarget)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
instance = instance.return;
|
|
}
|
|
|
|
if (listeners.length !== 0) {
|
|
dispatchQueue.push({
|
|
event: event,
|
|
listeners: listeners
|
|
});
|
|
}
|
|
} // We should only use this function for:
|
|
// - EnterLeaveEventPlugin
|
|
// This is because we only process this plugin
|
|
// in the bubble phase, so we need to accumulate two
|
|
// phase event listeners.
|
|
|
|
function accumulateEnterLeaveTwoPhaseListeners(
|
|
dispatchQueue,
|
|
leaveEvent,
|
|
enterEvent,
|
|
from,
|
|
to
|
|
) {
|
|
var common = from && to ? getLowestCommonAncestor(from, to) : null;
|
|
|
|
if (from !== null) {
|
|
accumulateEnterLeaveListenersForEvent(
|
|
dispatchQueue,
|
|
leaveEvent,
|
|
from,
|
|
common,
|
|
false
|
|
);
|
|
}
|
|
|
|
if (to !== null && enterEvent !== null) {
|
|
accumulateEnterLeaveListenersForEvent(
|
|
dispatchQueue,
|
|
enterEvent,
|
|
to,
|
|
common,
|
|
true
|
|
);
|
|
}
|
|
}
|
|
function accumulateEventHandleNonManagedNodeListeners(
|
|
reactEventType,
|
|
currentTarget,
|
|
inCapturePhase
|
|
) {
|
|
var listeners = [];
|
|
var eventListeners = getEventHandlerListeners(currentTarget);
|
|
|
|
if (eventListeners !== null) {
|
|
eventListeners.forEach(function(entry) {
|
|
if (entry.type === reactEventType && entry.capture === inCapturePhase) {
|
|
listeners.push(
|
|
createDispatchListener(null, entry.callback, currentTarget)
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
return listeners;
|
|
}
|
|
function getListenerSetKey(domEventName, capture) {
|
|
return domEventName + "__" + (capture ? "capture" : "bubble");
|
|
}
|
|
|
|
var didWarnInvalidHydration = false;
|
|
var DANGEROUSLY_SET_INNER_HTML = "dangerouslySetInnerHTML";
|
|
var SUPPRESS_CONTENT_EDITABLE_WARNING = "suppressContentEditableWarning";
|
|
var SUPPRESS_HYDRATION_WARNING = "suppressHydrationWarning";
|
|
var AUTOFOCUS = "autoFocus";
|
|
var CHILDREN = "children";
|
|
var STYLE = "style";
|
|
var HTML$1 = "__html";
|
|
var warnedUnknownTags;
|
|
var validatePropertiesInDevelopment;
|
|
var warnForPropDifference;
|
|
var warnForExtraAttributes;
|
|
var warnForInvalidEventListener;
|
|
var canDiffStyleForHydrationWarning;
|
|
var normalizeHTML;
|
|
|
|
{
|
|
warnedUnknownTags = {
|
|
// There are working polyfills for <dialog>. Let people use it.
|
|
dialog: true,
|
|
// Electron ships a custom <webview> tag to display external web content in
|
|
// an isolated frame and process.
|
|
// This tag is not present in non Electron environments such as JSDom which
|
|
// is often used for testing purposes.
|
|
// @see https://electronjs.org/docs/api/webview-tag
|
|
webview: true
|
|
};
|
|
|
|
validatePropertiesInDevelopment = function(type, props) {
|
|
validateProperties(type, props);
|
|
validateProperties$1(type, props);
|
|
validateProperties$2(type, props, {
|
|
registrationNameDependencies: registrationNameDependencies,
|
|
possibleRegistrationNames: possibleRegistrationNames
|
|
});
|
|
}; // IE 11 parses & normalizes the style attribute as opposed to other
|
|
// browsers. It adds spaces and sorts the properties in some
|
|
// non-alphabetical order. Handling that would require sorting CSS
|
|
// properties in the client & server versions or applying
|
|
// `expectedStyle` to a temporary DOM node to read its `style` attribute
|
|
// normalized. Since it only affects IE, we're skipping style warnings
|
|
// in that browser completely in favor of doing all that work.
|
|
// See https://github.com/facebook/react/issues/11807
|
|
|
|
canDiffStyleForHydrationWarning = canUseDOM && !document.documentMode;
|
|
|
|
warnForPropDifference = function(propName, serverValue, clientValue) {
|
|
if (didWarnInvalidHydration) {
|
|
return;
|
|
}
|
|
|
|
var normalizedClientValue = normalizeMarkupForTextOrAttribute(clientValue);
|
|
var normalizedServerValue = normalizeMarkupForTextOrAttribute(serverValue);
|
|
|
|
if (normalizedServerValue === normalizedClientValue) {
|
|
return;
|
|
}
|
|
|
|
didWarnInvalidHydration = true;
|
|
|
|
error(
|
|
"Prop `%s` did not match. Server: %s Client: %s",
|
|
propName,
|
|
JSON.stringify(normalizedServerValue),
|
|
JSON.stringify(normalizedClientValue)
|
|
);
|
|
};
|
|
|
|
warnForExtraAttributes = function(attributeNames) {
|
|
if (didWarnInvalidHydration) {
|
|
return;
|
|
}
|
|
|
|
didWarnInvalidHydration = true;
|
|
var names = [];
|
|
attributeNames.forEach(function(name) {
|
|
names.push(name);
|
|
});
|
|
|
|
error("Extra attributes from the server: %s", names);
|
|
};
|
|
|
|
warnForInvalidEventListener = function(registrationName, listener) {
|
|
if (listener === false) {
|
|
error(
|
|
"Expected `%s` listener to be a function, instead got `false`.\n\n" +
|
|
"If you used to conditionally omit it with %s={condition && value}, " +
|
|
"pass %s={condition ? value : undefined} instead.",
|
|
registrationName,
|
|
registrationName,
|
|
registrationName
|
|
);
|
|
} else {
|
|
error(
|
|
"Expected `%s` listener to be a function, instead got a value of `%s` type.",
|
|
registrationName,
|
|
typeof listener
|
|
);
|
|
}
|
|
}; // Parse the HTML and read it back to normalize the HTML string so that it
|
|
// can be used for comparison.
|
|
|
|
normalizeHTML = function(parent, html) {
|
|
// We could have created a separate document here to avoid
|
|
// re-initializing custom elements if they exist. But this breaks
|
|
// how <noscript> is being handled. So we use the same document.
|
|
// See the discussion in https://github.com/facebook/react/pull/11157.
|
|
var testElement =
|
|
parent.namespaceURI === HTML_NAMESPACE
|
|
? parent.ownerDocument.createElement(parent.tagName)
|
|
: parent.ownerDocument.createElementNS(
|
|
parent.namespaceURI,
|
|
parent.tagName
|
|
);
|
|
testElement.innerHTML = html;
|
|
return testElement.innerHTML;
|
|
};
|
|
} // HTML parsing normalizes CR and CRLF to LF.
|
|
// It also can turn \u0000 into \uFFFD inside attributes.
|
|
// https://www.w3.org/TR/html5/single-page.html#preprocessing-the-input-stream
|
|
// If we have a mismatch, it might be caused by that.
|
|
// We will still patch up in this case but not fire the warning.
|
|
|
|
var NORMALIZE_NEWLINES_REGEX = /\r\n?/g;
|
|
var NORMALIZE_NULL_AND_REPLACEMENT_REGEX = /\u0000|\uFFFD/g;
|
|
|
|
function normalizeMarkupForTextOrAttribute(markup) {
|
|
{
|
|
checkHtmlStringCoercion(markup);
|
|
}
|
|
|
|
var markupString = typeof markup === "string" ? markup : "" + markup;
|
|
return markupString
|
|
.replace(NORMALIZE_NEWLINES_REGEX, "\n")
|
|
.replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, "");
|
|
}
|
|
|
|
function checkForUnmatchedText(
|
|
serverText,
|
|
clientText,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
) {
|
|
var normalizedClientText = normalizeMarkupForTextOrAttribute(clientText);
|
|
var normalizedServerText = normalizeMarkupForTextOrAttribute(serverText);
|
|
|
|
if (normalizedServerText === normalizedClientText) {
|
|
return;
|
|
}
|
|
|
|
if (shouldWarnDev) {
|
|
{
|
|
if (!didWarnInvalidHydration) {
|
|
didWarnInvalidHydration = true;
|
|
|
|
error(
|
|
'Text content did not match. Server: "%s" Client: "%s"',
|
|
normalizedServerText,
|
|
normalizedClientText
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) {
|
|
// In concurrent roots, we throw when there's a text mismatch and revert to
|
|
// client rendering, up to the nearest Suspense boundary.
|
|
throw new Error("Text content does not match server-rendered HTML.");
|
|
}
|
|
}
|
|
function getOwnerDocumentFromRootContainer(rootContainerElement) {
|
|
return rootContainerElement.nodeType === DOCUMENT_NODE
|
|
? rootContainerElement
|
|
: rootContainerElement.ownerDocument;
|
|
}
|
|
|
|
function noop() {}
|
|
|
|
function trapClickOnNonInteractiveElement(node) {
|
|
// Mobile Safari does not fire properly bubble click events on
|
|
// non-interactive elements, which means delegated click listeners do not
|
|
// fire. The workaround for this bug involves attaching an empty click
|
|
// listener on the target node.
|
|
// https://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
|
|
// Just set it using the onclick property so that we don't have to manage any
|
|
// bookkeeping for it. Not sure if we need to clear it when the listener is
|
|
// removed.
|
|
// TODO: Only do this for the relevant Safaris maybe?
|
|
node.onclick = noop;
|
|
}
|
|
|
|
function setInitialDOMProperties(
|
|
tag,
|
|
domElement,
|
|
nextProps,
|
|
isCustomComponentTag
|
|
) {
|
|
for (var propKey in nextProps) {
|
|
if (!nextProps.hasOwnProperty(propKey)) {
|
|
continue;
|
|
}
|
|
|
|
var nextProp = nextProps[propKey];
|
|
|
|
if (propKey === STYLE) {
|
|
{
|
|
if (nextProp) {
|
|
// Freeze the next style object so that we can assume it won't be
|
|
// mutated. We have already warned for this in the past.
|
|
Object.freeze(nextProp);
|
|
}
|
|
} // Relies on `updateStylesByID` not mutating `styleUpdates`.
|
|
|
|
setValueForStyles(domElement, nextProp);
|
|
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
|
|
var nextHtml = nextProp ? nextProp[HTML$1] : undefined;
|
|
|
|
if (nextHtml != null) {
|
|
setInnerHTML(domElement, nextHtml);
|
|
}
|
|
} else if (propKey === CHILDREN) {
|
|
if (typeof nextProp === "string") {
|
|
// Avoid setting initial textContent when the text is empty. In IE11 setting
|
|
// textContent on a <textarea> will cause the placeholder to not
|
|
// show within the <textarea> until it has been focused and blurred again.
|
|
// https://github.com/facebook/react/issues/6731#issuecomment-254874553
|
|
var canSetTextContent =
|
|
tag !== "body" && (tag !== "textarea" || nextProp !== "");
|
|
|
|
if (canSetTextContent) {
|
|
setTextContent(domElement, nextProp);
|
|
}
|
|
} else if (typeof nextProp === "number") {
|
|
var _canSetTextContent = tag !== "body";
|
|
|
|
if (_canSetTextContent) {
|
|
setTextContent(domElement, "" + nextProp);
|
|
}
|
|
}
|
|
} else if (
|
|
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
|
|
propKey === SUPPRESS_HYDRATION_WARNING
|
|
);
|
|
else if (propKey === AUTOFOCUS);
|
|
else if (registrationNameDependencies.hasOwnProperty(propKey)) {
|
|
if (nextProp != null) {
|
|
if (typeof nextProp !== "function") {
|
|
warnForInvalidEventListener(propKey, nextProp);
|
|
}
|
|
|
|
if (propKey === "onScroll") {
|
|
listenToNonDelegatedEvent("scroll", domElement);
|
|
}
|
|
}
|
|
} else if (nextProp != null) {
|
|
setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateDOMProperties(
|
|
domElement,
|
|
updatePayload,
|
|
wasCustomComponentTag,
|
|
isCustomComponentTag
|
|
) {
|
|
// TODO: Handle wasCustomComponentTag
|
|
for (var i = 0; i < updatePayload.length; i += 2) {
|
|
var propKey = updatePayload[i];
|
|
var propValue = updatePayload[i + 1];
|
|
|
|
if (propKey === STYLE) {
|
|
setValueForStyles(domElement, propValue);
|
|
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
|
|
setInnerHTML(domElement, propValue);
|
|
} else if (propKey === CHILDREN) {
|
|
setTextContent(domElement, propValue);
|
|
} else {
|
|
setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
|
|
}
|
|
}
|
|
}
|
|
|
|
function createElement(type, props, rootContainerElement, parentNamespace) {
|
|
var isCustomComponentTag; // We create tags in the namespace of their parent container, except HTML
|
|
// tags get no namespace.
|
|
|
|
var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement);
|
|
var domElement;
|
|
var namespaceURI = parentNamespace;
|
|
|
|
if (namespaceURI === HTML_NAMESPACE) {
|
|
namespaceURI = getIntrinsicNamespace(type);
|
|
}
|
|
|
|
if (namespaceURI === HTML_NAMESPACE) {
|
|
{
|
|
isCustomComponentTag = isCustomComponent(type, props); // Should this check be gated by parent namespace? Not sure we want to
|
|
// allow <SVG> or <mATH>.
|
|
|
|
if (!isCustomComponentTag && type !== type.toLowerCase()) {
|
|
error(
|
|
"<%s /> is using incorrect casing. " +
|
|
"Use PascalCase for React components, " +
|
|
"or lowercase for HTML elements.",
|
|
type
|
|
);
|
|
}
|
|
}
|
|
|
|
if (type === "script") {
|
|
// Create the script via .innerHTML so its "parser-inserted" flag is
|
|
// set to true and it does not execute
|
|
var div = ownerDocument.createElement("div");
|
|
|
|
div.innerHTML = "<script><" + "/script>"; // eslint-disable-line
|
|
// This is guaranteed to yield a script element.
|
|
|
|
var firstChild = div.firstChild;
|
|
domElement = div.removeChild(firstChild);
|
|
} else if (typeof props.is === "string") {
|
|
domElement = ownerDocument.createElement(type, {
|
|
is: props.is
|
|
});
|
|
} else {
|
|
// Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
|
|
// See discussion in https://github.com/facebook/react/pull/6896
|
|
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
|
|
domElement = ownerDocument.createElement(type); // Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple` and `size`
|
|
// attributes on `select`s needs to be added before `option`s are inserted.
|
|
// This prevents:
|
|
// - a bug where the `select` does not scroll to the correct option because singular
|
|
// `select` elements automatically pick the first item #13222
|
|
// - a bug where the `select` set the first item as selected despite the `size` attribute #14239
|
|
// See https://github.com/facebook/react/issues/13222
|
|
// and https://github.com/facebook/react/issues/14239
|
|
|
|
if (type === "select") {
|
|
var node = domElement;
|
|
|
|
if (props.multiple) {
|
|
node.multiple = true;
|
|
} else if (props.size) {
|
|
// Setting a size greater than 1 causes a select to behave like `multiple=true`, where
|
|
// it is possible that no option is selected.
|
|
//
|
|
// This is only necessary when a select in "single selection mode".
|
|
node.size = props.size;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
domElement = ownerDocument.createElementNS(namespaceURI, type);
|
|
}
|
|
|
|
{
|
|
if (namespaceURI === HTML_NAMESPACE) {
|
|
if (
|
|
!isCustomComponentTag && // $FlowFixMe[method-unbinding]
|
|
Object.prototype.toString.call(domElement) ===
|
|
"[object HTMLUnknownElement]" &&
|
|
!hasOwnProperty.call(warnedUnknownTags, type)
|
|
) {
|
|
warnedUnknownTags[type] = true;
|
|
|
|
error(
|
|
"The tag <%s> is unrecognized in this browser. " +
|
|
"If you meant to render a React component, start its name with " +
|
|
"an uppercase letter.",
|
|
type
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return domElement;
|
|
}
|
|
function createTextNode(text, rootContainerElement) {
|
|
return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
|
|
text
|
|
);
|
|
}
|
|
function setInitialProperties(domElement, tag, rawProps) {
|
|
var isCustomComponentTag = isCustomComponent(tag, rawProps);
|
|
|
|
{
|
|
validatePropertiesInDevelopment(tag, rawProps);
|
|
} // TODO: Make sure that we check isMounted before firing any of these events.
|
|
|
|
var props;
|
|
|
|
switch (tag) {
|
|
case "dialog":
|
|
listenToNonDelegatedEvent("cancel", domElement);
|
|
listenToNonDelegatedEvent("close", domElement);
|
|
props = rawProps;
|
|
break;
|
|
|
|
case "iframe":
|
|
case "object":
|
|
case "embed":
|
|
// We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the load event.
|
|
listenToNonDelegatedEvent("load", domElement);
|
|
props = rawProps;
|
|
break;
|
|
|
|
case "video":
|
|
case "audio":
|
|
// We listen to these events in case to ensure emulated bubble
|
|
// listeners still fire for all the media events.
|
|
for (var i = 0; i < mediaEventTypes.length; i++) {
|
|
listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
|
|
}
|
|
|
|
props = rawProps;
|
|
break;
|
|
|
|
case "source":
|
|
// We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the error event.
|
|
listenToNonDelegatedEvent("error", domElement);
|
|
props = rawProps;
|
|
break;
|
|
|
|
case "img":
|
|
case "image":
|
|
case "link":
|
|
// We listen to these events in case to ensure emulated bubble
|
|
// listeners still fire for error and load events.
|
|
listenToNonDelegatedEvent("error", domElement);
|
|
listenToNonDelegatedEvent("load", domElement);
|
|
props = rawProps;
|
|
break;
|
|
|
|
case "details":
|
|
// We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the toggle event.
|
|
listenToNonDelegatedEvent("toggle", domElement);
|
|
props = rawProps;
|
|
break;
|
|
|
|
case "input":
|
|
initWrapperState(domElement, rawProps);
|
|
props = getHostProps(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the invalid event.
|
|
|
|
listenToNonDelegatedEvent("invalid", domElement);
|
|
break;
|
|
|
|
case "option":
|
|
validateProps(domElement, rawProps);
|
|
props = rawProps;
|
|
break;
|
|
|
|
case "select":
|
|
initWrapperState$1(domElement, rawProps);
|
|
props = getHostProps$1(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the invalid event.
|
|
|
|
listenToNonDelegatedEvent("invalid", domElement);
|
|
break;
|
|
|
|
case "textarea":
|
|
initWrapperState$2(domElement, rawProps);
|
|
props = getHostProps$2(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the invalid event.
|
|
|
|
listenToNonDelegatedEvent("invalid", domElement);
|
|
break;
|
|
|
|
default:
|
|
props = rawProps;
|
|
}
|
|
|
|
assertValidProps(tag, props);
|
|
setInitialDOMProperties(tag, domElement, props, isCustomComponentTag);
|
|
|
|
switch (tag) {
|
|
case "input":
|
|
// TODO: Make sure we check if this is still unmounted or do any clean
|
|
// up necessary since we never stop tracking anymore.
|
|
track(domElement);
|
|
postMountWrapper(domElement, rawProps, false);
|
|
break;
|
|
|
|
case "textarea":
|
|
// TODO: Make sure we check if this is still unmounted or do any clean
|
|
// up necessary since we never stop tracking anymore.
|
|
track(domElement);
|
|
postMountWrapper$3(domElement);
|
|
break;
|
|
|
|
case "option":
|
|
postMountWrapper$1(domElement, rawProps);
|
|
break;
|
|
|
|
case "select":
|
|
postMountWrapper$2(domElement, rawProps);
|
|
break;
|
|
|
|
default:
|
|
if (typeof props.onClick === "function") {
|
|
// TODO: This cast may not be sound for SVG, MathML or custom elements.
|
|
trapClickOnNonInteractiveElement(domElement);
|
|
}
|
|
|
|
break;
|
|
}
|
|
} // Calculate the diff between the two objects.
|
|
|
|
function diffProperties(domElement, tag, lastRawProps, nextRawProps) {
|
|
{
|
|
validatePropertiesInDevelopment(tag, nextRawProps);
|
|
}
|
|
|
|
var updatePayload = null;
|
|
var lastProps;
|
|
var nextProps;
|
|
|
|
switch (tag) {
|
|
case "input":
|
|
lastProps = getHostProps(domElement, lastRawProps);
|
|
nextProps = getHostProps(domElement, nextRawProps);
|
|
updatePayload = [];
|
|
break;
|
|
|
|
case "select":
|
|
lastProps = getHostProps$1(domElement, lastRawProps);
|
|
nextProps = getHostProps$1(domElement, nextRawProps);
|
|
updatePayload = [];
|
|
break;
|
|
|
|
case "textarea":
|
|
lastProps = getHostProps$2(domElement, lastRawProps);
|
|
nextProps = getHostProps$2(domElement, nextRawProps);
|
|
updatePayload = [];
|
|
break;
|
|
|
|
default:
|
|
lastProps = lastRawProps;
|
|
nextProps = nextRawProps;
|
|
|
|
if (
|
|
typeof lastProps.onClick !== "function" &&
|
|
typeof nextProps.onClick === "function"
|
|
) {
|
|
// TODO: This cast may not be sound for SVG, MathML or custom elements.
|
|
trapClickOnNonInteractiveElement(domElement);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
assertValidProps(tag, nextProps);
|
|
var propKey;
|
|
var styleName;
|
|
var styleUpdates = null;
|
|
|
|
for (propKey in lastProps) {
|
|
if (
|
|
nextProps.hasOwnProperty(propKey) ||
|
|
!lastProps.hasOwnProperty(propKey) ||
|
|
lastProps[propKey] == null
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (propKey === STYLE) {
|
|
var lastStyle = lastProps[propKey];
|
|
|
|
for (styleName in lastStyle) {
|
|
if (lastStyle.hasOwnProperty(styleName)) {
|
|
if (!styleUpdates) {
|
|
styleUpdates = {};
|
|
}
|
|
|
|
styleUpdates[styleName] = "";
|
|
}
|
|
}
|
|
} else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN);
|
|
else if (
|
|
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
|
|
propKey === SUPPRESS_HYDRATION_WARNING
|
|
);
|
|
else if (propKey === AUTOFOCUS);
|
|
else if (registrationNameDependencies.hasOwnProperty(propKey)) {
|
|
// This is a special case. If any listener updates we need to ensure
|
|
// that the "current" fiber pointer gets updated so we need a commit
|
|
// to update this element.
|
|
if (!updatePayload) {
|
|
updatePayload = [];
|
|
}
|
|
} else {
|
|
// For all other deleted properties we add it to the queue. We use
|
|
// the allowed property list in the commit phase instead.
|
|
(updatePayload = updatePayload || []).push(propKey, null);
|
|
}
|
|
}
|
|
|
|
for (propKey in nextProps) {
|
|
var nextProp = nextProps[propKey];
|
|
var lastProp = lastProps != null ? lastProps[propKey] : undefined;
|
|
|
|
if (
|
|
!nextProps.hasOwnProperty(propKey) ||
|
|
nextProp === lastProp ||
|
|
(nextProp == null && lastProp == null)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (propKey === STYLE) {
|
|
{
|
|
if (nextProp) {
|
|
// Freeze the next style object so that we can assume it won't be
|
|
// mutated. We have already warned for this in the past.
|
|
Object.freeze(nextProp);
|
|
}
|
|
}
|
|
|
|
if (lastProp) {
|
|
// Unset styles on `lastProp` but not on `nextProp`.
|
|
for (styleName in lastProp) {
|
|
if (
|
|
lastProp.hasOwnProperty(styleName) &&
|
|
(!nextProp || !nextProp.hasOwnProperty(styleName))
|
|
) {
|
|
if (!styleUpdates) {
|
|
styleUpdates = {};
|
|
}
|
|
|
|
styleUpdates[styleName] = "";
|
|
}
|
|
} // Update styles that changed since `lastProp`.
|
|
|
|
for (styleName in nextProp) {
|
|
if (
|
|
nextProp.hasOwnProperty(styleName) &&
|
|
lastProp[styleName] !== nextProp[styleName]
|
|
) {
|
|
if (!styleUpdates) {
|
|
styleUpdates = {};
|
|
}
|
|
|
|
styleUpdates[styleName] = nextProp[styleName];
|
|
}
|
|
}
|
|
} else {
|
|
// Relies on `updateStylesByID` not mutating `styleUpdates`.
|
|
if (!styleUpdates) {
|
|
if (!updatePayload) {
|
|
updatePayload = [];
|
|
}
|
|
|
|
updatePayload.push(propKey, styleUpdates);
|
|
}
|
|
|
|
styleUpdates = nextProp;
|
|
}
|
|
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
|
|
var nextHtml = nextProp ? nextProp[HTML$1] : undefined;
|
|
var lastHtml = lastProp ? lastProp[HTML$1] : undefined;
|
|
|
|
if (nextHtml != null) {
|
|
if (lastHtml !== nextHtml) {
|
|
(updatePayload = updatePayload || []).push(propKey, nextHtml);
|
|
}
|
|
}
|
|
} else if (propKey === CHILDREN) {
|
|
if (typeof nextProp === "string" || typeof nextProp === "number") {
|
|
(updatePayload = updatePayload || []).push(propKey, "" + nextProp);
|
|
}
|
|
} else if (
|
|
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
|
|
propKey === SUPPRESS_HYDRATION_WARNING
|
|
);
|
|
else if (registrationNameDependencies.hasOwnProperty(propKey)) {
|
|
if (nextProp != null) {
|
|
// We eagerly listen to this even though we haven't committed yet.
|
|
if (typeof nextProp !== "function") {
|
|
warnForInvalidEventListener(propKey, nextProp);
|
|
}
|
|
|
|
if (propKey === "onScroll") {
|
|
listenToNonDelegatedEvent("scroll", domElement);
|
|
}
|
|
}
|
|
|
|
if (!updatePayload && lastProp !== nextProp) {
|
|
// This is a special case. If any listener updates we need to ensure
|
|
// that the "current" props pointer gets updated so we need a commit
|
|
// to update this element.
|
|
updatePayload = [];
|
|
}
|
|
} else {
|
|
// For any other property we always add it to the queue and then we
|
|
// filter it out using the allowed property list during the commit.
|
|
(updatePayload = updatePayload || []).push(propKey, nextProp);
|
|
}
|
|
}
|
|
|
|
if (styleUpdates) {
|
|
{
|
|
validateShorthandPropertyCollisionInDev(styleUpdates, nextProps[STYLE]);
|
|
}
|
|
|
|
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
|
|
}
|
|
|
|
return updatePayload;
|
|
} // Apply the diff.
|
|
|
|
function updateProperties(
|
|
domElement,
|
|
updatePayload,
|
|
tag,
|
|
lastRawProps,
|
|
nextRawProps
|
|
) {
|
|
// Update checked *before* name.
|
|
// In the middle of an update, it is possible to have multiple checked.
|
|
// When a checked radio tries to change name, browser makes another radio's checked false.
|
|
if (
|
|
tag === "input" &&
|
|
nextRawProps.type === "radio" &&
|
|
nextRawProps.name != null
|
|
) {
|
|
updateChecked(domElement, nextRawProps);
|
|
}
|
|
|
|
var wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
|
|
var isCustomComponentTag = isCustomComponent(tag, nextRawProps); // Apply the diff.
|
|
|
|
updateDOMProperties(
|
|
domElement,
|
|
updatePayload,
|
|
wasCustomComponentTag,
|
|
isCustomComponentTag
|
|
); // TODO: Ensure that an update gets scheduled if any of the special props
|
|
// changed.
|
|
|
|
switch (tag) {
|
|
case "input":
|
|
// Update the wrapper around inputs *after* updating props. This has to
|
|
// happen after `updateDOMProperties`. Otherwise HTML5 input validations
|
|
// raise warnings and prevent the new value from being assigned.
|
|
updateWrapper(domElement, nextRawProps);
|
|
break;
|
|
|
|
case "textarea":
|
|
updateWrapper$1(domElement, nextRawProps);
|
|
break;
|
|
|
|
case "select":
|
|
// <select> value update needs to occur after <option> children
|
|
// reconciliation
|
|
postUpdateWrapper(domElement, nextRawProps);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function getPossibleStandardName(propName) {
|
|
{
|
|
var lowerCasedName = propName.toLowerCase();
|
|
|
|
if (!possibleStandardNames.hasOwnProperty(lowerCasedName)) {
|
|
return null;
|
|
}
|
|
|
|
return possibleStandardNames[lowerCasedName] || null;
|
|
}
|
|
}
|
|
|
|
function diffHydratedProperties(
|
|
domElement,
|
|
tag,
|
|
rawProps,
|
|
parentNamespace,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
) {
|
|
var isCustomComponentTag;
|
|
var extraAttributeNames;
|
|
|
|
{
|
|
isCustomComponentTag = isCustomComponent(tag, rawProps);
|
|
validatePropertiesInDevelopment(tag, rawProps);
|
|
} // TODO: Make sure that we check isMounted before firing any of these events.
|
|
|
|
switch (tag) {
|
|
case "dialog":
|
|
listenToNonDelegatedEvent("cancel", domElement);
|
|
listenToNonDelegatedEvent("close", domElement);
|
|
break;
|
|
|
|
case "iframe":
|
|
case "object":
|
|
case "embed":
|
|
// We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the load event.
|
|
listenToNonDelegatedEvent("load", domElement);
|
|
break;
|
|
|
|
case "video":
|
|
case "audio":
|
|
// We listen to these events in case to ensure emulated bubble
|
|
// listeners still fire for all the media events.
|
|
for (var i = 0; i < mediaEventTypes.length; i++) {
|
|
listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
|
|
}
|
|
|
|
break;
|
|
|
|
case "source":
|
|
// We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the error event.
|
|
listenToNonDelegatedEvent("error", domElement);
|
|
break;
|
|
|
|
case "img":
|
|
case "image":
|
|
case "link":
|
|
// We listen to these events in case to ensure emulated bubble
|
|
// listeners still fire for error and load events.
|
|
listenToNonDelegatedEvent("error", domElement);
|
|
listenToNonDelegatedEvent("load", domElement);
|
|
break;
|
|
|
|
case "details":
|
|
// We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the toggle event.
|
|
listenToNonDelegatedEvent("toggle", domElement);
|
|
break;
|
|
|
|
case "input":
|
|
initWrapperState(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the invalid event.
|
|
|
|
listenToNonDelegatedEvent("invalid", domElement);
|
|
break;
|
|
|
|
case "option":
|
|
validateProps(domElement, rawProps);
|
|
break;
|
|
|
|
case "select":
|
|
initWrapperState$1(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the invalid event.
|
|
|
|
listenToNonDelegatedEvent("invalid", domElement);
|
|
break;
|
|
|
|
case "textarea":
|
|
initWrapperState$2(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
|
|
// listeners still fire for the invalid event.
|
|
|
|
listenToNonDelegatedEvent("invalid", domElement);
|
|
break;
|
|
}
|
|
|
|
assertValidProps(tag, rawProps);
|
|
|
|
{
|
|
extraAttributeNames = new Set();
|
|
var attributes = domElement.attributes;
|
|
|
|
for (var _i = 0; _i < attributes.length; _i++) {
|
|
var name = attributes[_i].name.toLowerCase();
|
|
|
|
switch (name) {
|
|
// Controlled attributes are not validated
|
|
// TODO: Only ignore them on controlled tags.
|
|
case "value":
|
|
break;
|
|
|
|
case "checked":
|
|
break;
|
|
|
|
case "selected":
|
|
break;
|
|
|
|
default:
|
|
// Intentionally use the original name.
|
|
// See discussion in https://github.com/facebook/react/pull/10676.
|
|
extraAttributeNames.add(attributes[_i].name);
|
|
}
|
|
}
|
|
}
|
|
|
|
var updatePayload = null;
|
|
|
|
for (var propKey in rawProps) {
|
|
if (!rawProps.hasOwnProperty(propKey)) {
|
|
continue;
|
|
}
|
|
|
|
var nextProp = rawProps[propKey];
|
|
|
|
if (propKey === CHILDREN) {
|
|
// For text content children we compare against textContent. This
|
|
// might match additional HTML that is hidden when we read it using
|
|
// textContent. E.g. "foo" will match "f<span>oo</span>" but that still
|
|
// satisfies our requirement. Our requirement is not to produce perfect
|
|
// HTML and attributes. Ideally we should preserve structure but it's
|
|
// ok not to if the visible content is still enough to indicate what
|
|
// even listeners these nodes might be wired up to.
|
|
// TODO: Warn if there is more than a single textNode as a child.
|
|
// TODO: Should we use domElement.firstChild.nodeValue to compare?
|
|
if (typeof nextProp === "string") {
|
|
if (domElement.textContent !== nextProp) {
|
|
if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
|
checkForUnmatchedText(
|
|
domElement.textContent,
|
|
nextProp,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
);
|
|
}
|
|
|
|
updatePayload = [CHILDREN, nextProp];
|
|
}
|
|
} else if (typeof nextProp === "number") {
|
|
if (domElement.textContent !== "" + nextProp) {
|
|
if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
|
checkForUnmatchedText(
|
|
domElement.textContent,
|
|
nextProp,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
);
|
|
}
|
|
|
|
updatePayload = [CHILDREN, "" + nextProp];
|
|
}
|
|
}
|
|
} else if (registrationNameDependencies.hasOwnProperty(propKey)) {
|
|
if (nextProp != null) {
|
|
if (typeof nextProp !== "function") {
|
|
warnForInvalidEventListener(propKey, nextProp);
|
|
}
|
|
|
|
if (propKey === "onScroll") {
|
|
listenToNonDelegatedEvent("scroll", domElement);
|
|
}
|
|
}
|
|
} else if (
|
|
shouldWarnDev &&
|
|
true && // Convince Flow we've calculated it (it's DEV-only in this method.)
|
|
typeof isCustomComponentTag === "boolean"
|
|
) {
|
|
// Validate that the properties correspond to their expected values.
|
|
var serverValue = void 0;
|
|
var propertyInfo =
|
|
isCustomComponentTag && enableCustomElementPropertySupport
|
|
? null
|
|
: getPropertyInfo(propKey);
|
|
|
|
if (rawProps[SUPPRESS_HYDRATION_WARNING] === true);
|
|
else if (
|
|
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
|
|
propKey === SUPPRESS_HYDRATION_WARNING || // Controlled attributes are not validated
|
|
// TODO: Only ignore them on controlled tags.
|
|
propKey === "value" ||
|
|
propKey === "checked" ||
|
|
propKey === "selected"
|
|
);
|
|
else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
|
|
var serverHTML = domElement.innerHTML;
|
|
var nextHtml = nextProp ? nextProp[HTML$1] : undefined;
|
|
|
|
if (nextHtml != null) {
|
|
var expectedHTML = normalizeHTML(domElement, nextHtml);
|
|
|
|
if (expectedHTML !== serverHTML) {
|
|
warnForPropDifference(propKey, serverHTML, expectedHTML);
|
|
}
|
|
}
|
|
} else if (propKey === STYLE) {
|
|
// $FlowFixMe - Should be inferred as not undefined.
|
|
extraAttributeNames.delete(propKey);
|
|
|
|
if (canDiffStyleForHydrationWarning) {
|
|
var expectedStyle = createDangerousStringForStyles(nextProp);
|
|
serverValue = domElement.getAttribute("style");
|
|
|
|
if (expectedStyle !== serverValue) {
|
|
warnForPropDifference(propKey, serverValue, expectedStyle);
|
|
}
|
|
}
|
|
} else if (isCustomComponentTag && !enableCustomElementPropertySupport) {
|
|
// $FlowFixMe - Should be inferred as not undefined.
|
|
extraAttributeNames.delete(propKey.toLowerCase());
|
|
serverValue = getValueForAttribute(domElement, propKey, nextProp);
|
|
|
|
if (nextProp !== serverValue) {
|
|
warnForPropDifference(propKey, serverValue, nextProp);
|
|
}
|
|
} else if (
|
|
!shouldIgnoreAttribute(propKey, propertyInfo, isCustomComponentTag) &&
|
|
!shouldRemoveAttribute(
|
|
propKey,
|
|
nextProp,
|
|
propertyInfo,
|
|
isCustomComponentTag
|
|
)
|
|
) {
|
|
var isMismatchDueToBadCasing = false;
|
|
|
|
if (propertyInfo !== null) {
|
|
// $FlowFixMe - Should be inferred as not undefined.
|
|
extraAttributeNames.delete(propertyInfo.attributeName);
|
|
serverValue = getValueForProperty(
|
|
domElement,
|
|
propKey,
|
|
nextProp,
|
|
propertyInfo
|
|
);
|
|
} else {
|
|
var ownNamespace = parentNamespace;
|
|
|
|
if (ownNamespace === HTML_NAMESPACE) {
|
|
ownNamespace = getIntrinsicNamespace(tag);
|
|
}
|
|
|
|
if (ownNamespace === HTML_NAMESPACE) {
|
|
// $FlowFixMe - Should be inferred as not undefined.
|
|
extraAttributeNames.delete(propKey.toLowerCase());
|
|
} else {
|
|
var standardName = getPossibleStandardName(propKey);
|
|
|
|
if (standardName !== null && standardName !== propKey) {
|
|
// If an SVG prop is supplied with bad casing, it will
|
|
// be successfully parsed from HTML, but will produce a mismatch
|
|
// (and would be incorrectly rendered on the client).
|
|
// However, we already warn about bad casing elsewhere.
|
|
// So we'll skip the misleading extra mismatch warning in this case.
|
|
isMismatchDueToBadCasing = true; // $FlowFixMe - Should be inferred as not undefined.
|
|
|
|
extraAttributeNames.delete(standardName);
|
|
} // $FlowFixMe - Should be inferred as not undefined.
|
|
|
|
extraAttributeNames.delete(propKey);
|
|
}
|
|
|
|
serverValue = getValueForAttribute(domElement, propKey, nextProp);
|
|
}
|
|
|
|
var dontWarnCustomElement = enableCustomElementPropertySupport;
|
|
|
|
if (
|
|
!dontWarnCustomElement &&
|
|
nextProp !== serverValue &&
|
|
!isMismatchDueToBadCasing
|
|
) {
|
|
warnForPropDifference(propKey, serverValue, nextProp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
if (shouldWarnDev) {
|
|
if (
|
|
// $FlowFixMe - Should be inferred as not undefined.
|
|
extraAttributeNames.size > 0 &&
|
|
rawProps[SUPPRESS_HYDRATION_WARNING] !== true
|
|
) {
|
|
// $FlowFixMe - Should be inferred as not undefined.
|
|
warnForExtraAttributes(extraAttributeNames);
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (tag) {
|
|
case "input":
|
|
// TODO: Make sure we check if this is still unmounted or do any clean
|
|
// up necessary since we never stop tracking anymore.
|
|
track(domElement);
|
|
postMountWrapper(domElement, rawProps, true);
|
|
break;
|
|
|
|
case "textarea":
|
|
// TODO: Make sure we check if this is still unmounted or do any clean
|
|
// up necessary since we never stop tracking anymore.
|
|
track(domElement);
|
|
postMountWrapper$3(domElement);
|
|
break;
|
|
|
|
case "select":
|
|
case "option":
|
|
// For input and textarea we current always set the value property at
|
|
// post mount to force it to diverge from attributes. However, for
|
|
// option and select we don't quite do the same thing and select
|
|
// is not resilient to the DOM state changing so we don't do that here.
|
|
// TODO: Consider not doing this for input and textarea.
|
|
break;
|
|
|
|
default:
|
|
if (typeof rawProps.onClick === "function") {
|
|
// TODO: This cast may not be sound for SVG, MathML or custom elements.
|
|
trapClickOnNonInteractiveElement(domElement);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return updatePayload;
|
|
}
|
|
function diffHydratedText(textNode, text, isConcurrentMode) {
|
|
var isDifferent = textNode.nodeValue !== text;
|
|
return isDifferent;
|
|
}
|
|
function warnForDeletedHydratableElement(parentNode, child) {
|
|
{
|
|
if (didWarnInvalidHydration) {
|
|
return;
|
|
}
|
|
|
|
didWarnInvalidHydration = true;
|
|
|
|
error(
|
|
"Did not expect server HTML to contain a <%s> in <%s>.",
|
|
child.nodeName.toLowerCase(),
|
|
parentNode.nodeName.toLowerCase()
|
|
);
|
|
}
|
|
}
|
|
function warnForDeletedHydratableText(parentNode, child) {
|
|
{
|
|
if (didWarnInvalidHydration) {
|
|
return;
|
|
}
|
|
|
|
didWarnInvalidHydration = true;
|
|
|
|
error(
|
|
'Did not expect server HTML to contain the text node "%s" in <%s>.',
|
|
child.nodeValue,
|
|
parentNode.nodeName.toLowerCase()
|
|
);
|
|
}
|
|
}
|
|
function warnForInsertedHydratedElement(parentNode, tag, props) {
|
|
{
|
|
if (didWarnInvalidHydration) {
|
|
return;
|
|
}
|
|
|
|
didWarnInvalidHydration = true;
|
|
|
|
error(
|
|
"Expected server HTML to contain a matching <%s> in <%s>.",
|
|
tag,
|
|
parentNode.nodeName.toLowerCase()
|
|
);
|
|
}
|
|
}
|
|
function warnForInsertedHydratedText(parentNode, text) {
|
|
{
|
|
if (text === "") {
|
|
// We expect to insert empty text nodes since they're not represented in
|
|
// the HTML.
|
|
// TODO: Remove this special case if we can just avoid inserting empty
|
|
// text nodes.
|
|
return;
|
|
}
|
|
|
|
if (didWarnInvalidHydration) {
|
|
return;
|
|
}
|
|
|
|
didWarnInvalidHydration = true;
|
|
|
|
error(
|
|
'Expected server HTML to contain a matching text node for "%s" in <%s>.',
|
|
text,
|
|
parentNode.nodeName.toLowerCase()
|
|
);
|
|
}
|
|
}
|
|
function restoreControlledState$3(domElement, tag, props) {
|
|
switch (tag) {
|
|
case "input":
|
|
restoreControlledState(domElement, props);
|
|
return;
|
|
|
|
case "textarea":
|
|
restoreControlledState$2(domElement, props);
|
|
return;
|
|
|
|
case "select":
|
|
restoreControlledState$1(domElement, props);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var validateDOMNesting = function() {};
|
|
|
|
var updatedAncestorInfoDev = function() {};
|
|
|
|
{
|
|
// This validation code was written based on the HTML5 parsing spec:
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope
|
|
//
|
|
// Note: this does not catch all invalid nesting, nor does it try to (as it's
|
|
// not clear what practical benefit doing so provides); instead, we warn only
|
|
// for cases where the parser will give a parse tree differing from what React
|
|
// intended. For example, <b><div></div></b> is invalid but we don't warn
|
|
// because it still parses correctly; we do warn for other cases like nested
|
|
// <p> tags where the beginning of the second element implicitly closes the
|
|
// first, causing a confusing mess.
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#special
|
|
var specialTags = [
|
|
"address",
|
|
"applet",
|
|
"area",
|
|
"article",
|
|
"aside",
|
|
"base",
|
|
"basefont",
|
|
"bgsound",
|
|
"blockquote",
|
|
"body",
|
|
"br",
|
|
"button",
|
|
"caption",
|
|
"center",
|
|
"col",
|
|
"colgroup",
|
|
"dd",
|
|
"details",
|
|
"dir",
|
|
"div",
|
|
"dl",
|
|
"dt",
|
|
"embed",
|
|
"fieldset",
|
|
"figcaption",
|
|
"figure",
|
|
"footer",
|
|
"form",
|
|
"frame",
|
|
"frameset",
|
|
"h1",
|
|
"h2",
|
|
"h3",
|
|
"h4",
|
|
"h5",
|
|
"h6",
|
|
"head",
|
|
"header",
|
|
"hgroup",
|
|
"hr",
|
|
"html",
|
|
"iframe",
|
|
"img",
|
|
"input",
|
|
"isindex",
|
|
"li",
|
|
"link",
|
|
"listing",
|
|
"main",
|
|
"marquee",
|
|
"menu",
|
|
"menuitem",
|
|
"meta",
|
|
"nav",
|
|
"noembed",
|
|
"noframes",
|
|
"noscript",
|
|
"object",
|
|
"ol",
|
|
"p",
|
|
"param",
|
|
"plaintext",
|
|
"pre",
|
|
"script",
|
|
"section",
|
|
"select",
|
|
"source",
|
|
"style",
|
|
"summary",
|
|
"table",
|
|
"tbody",
|
|
"td",
|
|
"template",
|
|
"textarea",
|
|
"tfoot",
|
|
"th",
|
|
"thead",
|
|
"title",
|
|
"tr",
|
|
"track",
|
|
"ul",
|
|
"wbr",
|
|
"xmp"
|
|
]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope
|
|
|
|
var inScopeTags = [
|
|
"applet",
|
|
"caption",
|
|
"html",
|
|
"table",
|
|
"td",
|
|
"th",
|
|
"marquee",
|
|
"object",
|
|
"template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point
|
|
// TODO: Distinguish by namespace here -- for <title>, including it here
|
|
// errs on the side of fewer warnings
|
|
"foreignObject",
|
|
"desc",
|
|
"title"
|
|
]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope
|
|
|
|
var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags
|
|
|
|
var impliedEndTags = [
|
|
"dd",
|
|
"dt",
|
|
"li",
|
|
"option",
|
|
"optgroup",
|
|
"p",
|
|
"rp",
|
|
"rt"
|
|
];
|
|
var emptyAncestorInfoDev = {
|
|
current: null,
|
|
formTag: null,
|
|
aTagInScope: null,
|
|
buttonTagInScope: null,
|
|
nobrTagInScope: null,
|
|
pTagInButtonScope: null,
|
|
listItemTagAutoclosing: null,
|
|
dlItemTagAutoclosing: null,
|
|
containerTagInScope: null
|
|
};
|
|
|
|
updatedAncestorInfoDev = function(oldInfo, tag) {
|
|
var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev);
|
|
|
|
var info = {
|
|
tag: tag
|
|
};
|
|
|
|
if (inScopeTags.indexOf(tag) !== -1) {
|
|
ancestorInfo.aTagInScope = null;
|
|
ancestorInfo.buttonTagInScope = null;
|
|
ancestorInfo.nobrTagInScope = null;
|
|
}
|
|
|
|
if (buttonScopeTags.indexOf(tag) !== -1) {
|
|
ancestorInfo.pTagInButtonScope = null;
|
|
} // See rules for 'li', 'dd', 'dt' start tags in
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
|
|
|
|
if (
|
|
specialTags.indexOf(tag) !== -1 &&
|
|
tag !== "address" &&
|
|
tag !== "div" &&
|
|
tag !== "p"
|
|
) {
|
|
ancestorInfo.listItemTagAutoclosing = null;
|
|
ancestorInfo.dlItemTagAutoclosing = null;
|
|
}
|
|
|
|
ancestorInfo.current = info;
|
|
|
|
if (tag === "form") {
|
|
ancestorInfo.formTag = info;
|
|
}
|
|
|
|
if (tag === "a") {
|
|
ancestorInfo.aTagInScope = info;
|
|
}
|
|
|
|
if (tag === "button") {
|
|
ancestorInfo.buttonTagInScope = info;
|
|
}
|
|
|
|
if (tag === "nobr") {
|
|
ancestorInfo.nobrTagInScope = info;
|
|
}
|
|
|
|
if (tag === "p") {
|
|
ancestorInfo.pTagInButtonScope = info;
|
|
}
|
|
|
|
if (tag === "li") {
|
|
ancestorInfo.listItemTagAutoclosing = info;
|
|
}
|
|
|
|
if (tag === "dd" || tag === "dt") {
|
|
ancestorInfo.dlItemTagAutoclosing = info;
|
|
}
|
|
|
|
if (tag === "#document" || tag === "html") {
|
|
ancestorInfo.containerTagInScope = null;
|
|
} else if (!ancestorInfo.containerTagInScope) {
|
|
ancestorInfo.containerTagInScope = info;
|
|
}
|
|
|
|
return ancestorInfo;
|
|
};
|
|
/**
|
|
* Returns whether
|
|
*/
|
|
|
|
var isTagValidWithParent = function(tag, parentTag) {
|
|
// First, let's check if we're in an unusual parsing mode...
|
|
switch (parentTag) {
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
|
|
case "select":
|
|
return tag === "option" || tag === "optgroup" || tag === "#text";
|
|
|
|
case "optgroup":
|
|
return tag === "option" || tag === "#text";
|
|
// Strictly speaking, seeing an <option> doesn't mean we're in a <select>
|
|
// but
|
|
|
|
case "option":
|
|
return tag === "#text";
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption
|
|
// No special behavior since these rules fall back to "in body" mode for
|
|
// all except special table nodes which cause bad parsing behavior anyway.
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr
|
|
|
|
case "tr":
|
|
return (
|
|
tag === "th" ||
|
|
tag === "td" ||
|
|
tag === "style" ||
|
|
tag === "script" ||
|
|
tag === "template"
|
|
);
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody
|
|
|
|
case "tbody":
|
|
case "thead":
|
|
case "tfoot":
|
|
return (
|
|
tag === "tr" ||
|
|
tag === "style" ||
|
|
tag === "script" ||
|
|
tag === "template"
|
|
);
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup
|
|
|
|
case "colgroup":
|
|
return tag === "col" || tag === "template";
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable
|
|
|
|
case "table":
|
|
return (
|
|
tag === "caption" ||
|
|
tag === "colgroup" ||
|
|
tag === "tbody" ||
|
|
tag === "tfoot" ||
|
|
tag === "thead" ||
|
|
tag === "style" ||
|
|
tag === "script" ||
|
|
tag === "template"
|
|
);
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead
|
|
|
|
case "head":
|
|
return (
|
|
tag === "base" ||
|
|
tag === "basefont" ||
|
|
tag === "bgsound" ||
|
|
tag === "link" ||
|
|
tag === "meta" ||
|
|
tag === "title" ||
|
|
tag === "noscript" ||
|
|
tag === "noframes" ||
|
|
tag === "style" ||
|
|
tag === "script" ||
|
|
tag === "template"
|
|
);
|
|
// https://html.spec.whatwg.org/multipage/semantics.html#the-html-element
|
|
|
|
case "html":
|
|
return tag === "head" || tag === "body" || tag === "frameset";
|
|
|
|
case "frameset":
|
|
return tag === "frame";
|
|
|
|
case "#document":
|
|
return tag === "html";
|
|
} // Probably in the "in body" parsing mode, so we outlaw only tag combos
|
|
// where the parsing rules cause implicit opens or closes to be added.
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
|
|
|
|
switch (tag) {
|
|
case "h1":
|
|
case "h2":
|
|
case "h3":
|
|
case "h4":
|
|
case "h5":
|
|
case "h6":
|
|
return (
|
|
parentTag !== "h1" &&
|
|
parentTag !== "h2" &&
|
|
parentTag !== "h3" &&
|
|
parentTag !== "h4" &&
|
|
parentTag !== "h5" &&
|
|
parentTag !== "h6"
|
|
);
|
|
|
|
case "rp":
|
|
case "rt":
|
|
return impliedEndTags.indexOf(parentTag) === -1;
|
|
|
|
case "body":
|
|
case "caption":
|
|
case "col":
|
|
case "colgroup":
|
|
case "frameset":
|
|
case "frame":
|
|
case "head":
|
|
case "html":
|
|
case "tbody":
|
|
case "td":
|
|
case "tfoot":
|
|
case "th":
|
|
case "thead":
|
|
case "tr":
|
|
// These tags are only valid with a few parents that have special child
|
|
// parsing rules -- if we're down here, then none of those matched and
|
|
// so we allow it only if we don't know what the parent is, as all other
|
|
// cases are invalid.
|
|
return parentTag == null;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
/**
|
|
* Returns whether
|
|
*/
|
|
|
|
var findInvalidAncestorForTag = function(tag, ancestorInfo) {
|
|
switch (tag) {
|
|
case "address":
|
|
case "article":
|
|
case "aside":
|
|
case "blockquote":
|
|
case "center":
|
|
case "details":
|
|
case "dialog":
|
|
case "dir":
|
|
case "div":
|
|
case "dl":
|
|
case "fieldset":
|
|
case "figcaption":
|
|
case "figure":
|
|
case "footer":
|
|
case "header":
|
|
case "hgroup":
|
|
case "main":
|
|
case "menu":
|
|
case "nav":
|
|
case "ol":
|
|
case "p":
|
|
case "section":
|
|
case "summary":
|
|
case "ul":
|
|
case "pre":
|
|
case "listing":
|
|
case "table":
|
|
case "hr":
|
|
case "xmp":
|
|
case "h1":
|
|
case "h2":
|
|
case "h3":
|
|
case "h4":
|
|
case "h5":
|
|
case "h6":
|
|
return ancestorInfo.pTagInButtonScope;
|
|
|
|
case "form":
|
|
return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope;
|
|
|
|
case "li":
|
|
return ancestorInfo.listItemTagAutoclosing;
|
|
|
|
case "dd":
|
|
case "dt":
|
|
return ancestorInfo.dlItemTagAutoclosing;
|
|
|
|
case "button":
|
|
return ancestorInfo.buttonTagInScope;
|
|
|
|
case "a":
|
|
// Spec says something about storing a list of markers, but it sounds
|
|
// equivalent to this check.
|
|
return ancestorInfo.aTagInScope;
|
|
|
|
case "nobr":
|
|
return ancestorInfo.nobrTagInScope;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
var didWarn = {};
|
|
|
|
validateDOMNesting = function(childTag, childText, ancestorInfo) {
|
|
ancestorInfo = ancestorInfo || emptyAncestorInfoDev;
|
|
var parentInfo = ancestorInfo.current;
|
|
var parentTag = parentInfo && parentInfo.tag;
|
|
|
|
if (childText != null) {
|
|
if (childTag != null) {
|
|
error(
|
|
"validateDOMNesting: when childText is passed, childTag should be null"
|
|
);
|
|
}
|
|
|
|
childTag = "#text";
|
|
} else if (childTag == null) {
|
|
error("validateDOMNesting: when childText or childTag must be provided");
|
|
|
|
return;
|
|
}
|
|
|
|
var invalidParent = isTagValidWithParent(childTag, parentTag)
|
|
? null
|
|
: parentInfo;
|
|
var invalidAncestor = invalidParent
|
|
? null
|
|
: findInvalidAncestorForTag(childTag, ancestorInfo);
|
|
var invalidParentOrAncestor = invalidParent || invalidAncestor;
|
|
|
|
if (!invalidParentOrAncestor) {
|
|
return;
|
|
}
|
|
|
|
var ancestorTag = invalidParentOrAncestor.tag;
|
|
var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion
|
|
|
|
if (didWarn[warnKey]) {
|
|
return;
|
|
}
|
|
|
|
didWarn[warnKey] = true;
|
|
var tagDisplayName = childTag;
|
|
var whitespaceInfo = "";
|
|
|
|
if (childTag === "#text") {
|
|
if (childText != null && /\S/.test(childText)) {
|
|
tagDisplayName = "Text nodes";
|
|
} else {
|
|
tagDisplayName = "Whitespace text nodes";
|
|
whitespaceInfo =
|
|
" Make sure you don't have any extra whitespace between tags on " +
|
|
"each line of your source code.";
|
|
}
|
|
} else {
|
|
tagDisplayName = "<" + childTag + ">";
|
|
}
|
|
|
|
if (invalidParent) {
|
|
var info = "";
|
|
|
|
if (ancestorTag === "table" && childTag === "tr") {
|
|
info +=
|
|
" Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " +
|
|
"the browser.";
|
|
}
|
|
|
|
error(
|
|
"validateDOMNesting(...): %s cannot appear as a child of <%s>.%s%s",
|
|
tagDisplayName,
|
|
ancestorTag,
|
|
whitespaceInfo,
|
|
info
|
|
);
|
|
} else {
|
|
error(
|
|
"validateDOMNesting(...): %s cannot appear as a descendant of " +
|
|
"<%s>.",
|
|
tagDisplayName,
|
|
ancestorTag
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
var ReactDOMSharedInternals = Internals;
|
|
|
|
function warnOnMissingHrefAndRel(pendingProps, currentProps) {
|
|
{
|
|
if (currentProps != null) {
|
|
var originalResourceName =
|
|
typeof currentProps.href === "string"
|
|
? 'Resource with href "' + currentProps.href + '"'
|
|
: "Resource";
|
|
var originalRelStatement = getValueDescriptorExpectingEnumForWarning(
|
|
currentProps.rel
|
|
);
|
|
var pendingRel = getValueDescriptorExpectingEnumForWarning(
|
|
pendingProps.rel
|
|
);
|
|
var pendingHref = getValueDescriptorExpectingEnumForWarning(
|
|
pendingProps.href
|
|
);
|
|
|
|
if (typeof pendingProps.rel !== "string") {
|
|
error(
|
|
'A <link> previously rendered as a %s with rel "%s" but was updated with an invalid rel: %s. When a link' +
|
|
" does not have a valid rel prop it is not represented in the DOM. If this is intentional, instead" +
|
|
" do not render the <link> anymore.",
|
|
originalResourceName,
|
|
originalRelStatement,
|
|
pendingRel
|
|
);
|
|
} else if (typeof pendingProps.href !== "string") {
|
|
error(
|
|
"A <link> previously rendered as a %s but was updated with an invalid href prop: %s. When a link" +
|
|
" does not have a valid href prop it is not represented in the DOM. If this is intentional, instead" +
|
|
" do not render the <link> anymore.",
|
|
originalResourceName,
|
|
pendingHref
|
|
);
|
|
}
|
|
} else {
|
|
var _pendingRel = getValueDescriptorExpectingEnumForWarning(
|
|
pendingProps.rel
|
|
);
|
|
|
|
var _pendingHref = getValueDescriptorExpectingEnumForWarning(
|
|
pendingProps.href
|
|
);
|
|
|
|
if (typeof pendingProps.rel !== "string") {
|
|
error(
|
|
"A <link> is rendering with an invalid rel: %s. When a link" +
|
|
" does not have a valid rel prop it is not represented in the DOM. If this is intentional, instead" +
|
|
" do not render the <link> anymore.",
|
|
_pendingRel
|
|
);
|
|
} else if (typeof pendingProps.href !== "string") {
|
|
error(
|
|
"A <link> is rendering with an invalid href: %s. When a link" +
|
|
" does not have a valid href prop it is not represented in the DOM. If this is intentional, instead" +
|
|
" do not render the <link> anymore.",
|
|
_pendingHref
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function validatePreloadResourceDifference(
|
|
originalProps,
|
|
originalImplicit,
|
|
latestProps,
|
|
latestImplicit
|
|
) {
|
|
{
|
|
var href = originalProps.href;
|
|
var originalWarningName = getResourceNameForWarning(
|
|
"preload",
|
|
originalProps,
|
|
originalImplicit
|
|
);
|
|
var latestWarningName = getResourceNameForWarning(
|
|
"preload",
|
|
latestProps,
|
|
latestImplicit
|
|
);
|
|
|
|
if (latestProps.as !== originalProps.as) {
|
|
error(
|
|
'A %s is using the same href "%s" as a %s. This is always an error and React will only keep the first preload' +
|
|
" for any given href, discarding subsequent instances. To fix, find where you are using this href in link" +
|
|
" tags or in calls to ReactDOM.preload() or ReactDOM.preinit() and either make the Resource types agree or" +
|
|
" update the hrefs to be distinct for different Resource types.",
|
|
latestWarningName,
|
|
href,
|
|
originalWarningName
|
|
);
|
|
} else {
|
|
var missingProps = null;
|
|
var extraProps = null;
|
|
var differentProps = null;
|
|
|
|
if (originalProps.media != null && latestProps.media == null) {
|
|
missingProps = missingProps || {};
|
|
missingProps.media = originalProps.media;
|
|
}
|
|
|
|
for (var propName in latestProps) {
|
|
var propValue = latestProps[propName];
|
|
var originalValue = originalProps[propName];
|
|
|
|
if (propValue != null && propValue !== originalValue) {
|
|
if (originalValue == null) {
|
|
extraProps = extraProps || {};
|
|
extraProps[propName] = propValue;
|
|
} else {
|
|
differentProps = differentProps || {};
|
|
differentProps[propName] = {
|
|
original: originalValue,
|
|
latest: propValue
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (missingProps || extraProps || differentProps) {
|
|
warnDifferentProps(
|
|
href,
|
|
"href",
|
|
originalWarningName,
|
|
latestWarningName,
|
|
extraProps,
|
|
missingProps,
|
|
differentProps
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function validateStyleResourceDifference(originalProps, latestProps) {
|
|
{
|
|
var href = originalProps.href; // eslint-disable-next-line no-labels
|
|
|
|
var originalWarningName = getResourceNameForWarning(
|
|
"style",
|
|
originalProps,
|
|
false
|
|
);
|
|
var latestWarningName = getResourceNameForWarning(
|
|
"style",
|
|
latestProps,
|
|
false
|
|
);
|
|
var missingProps = null;
|
|
var extraProps = null;
|
|
var differentProps = null;
|
|
|
|
if (originalProps.media != null && latestProps.media == null) {
|
|
missingProps = missingProps || {};
|
|
missingProps.media = originalProps.media;
|
|
}
|
|
|
|
for (var propName in latestProps) {
|
|
var propValue = latestProps[propName];
|
|
var originalValue = originalProps[propName];
|
|
|
|
if (propValue != null && propValue !== originalValue) {
|
|
propName = propName === "data-precedence" ? "precedence" : propName;
|
|
|
|
if (originalValue == null) {
|
|
extraProps = extraProps || {};
|
|
extraProps[propName] = propValue;
|
|
} else {
|
|
differentProps = differentProps || {};
|
|
differentProps[propName] = {
|
|
original: originalValue,
|
|
latest: propValue
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (missingProps || extraProps || differentProps) {
|
|
warnDifferentProps(
|
|
href,
|
|
"href",
|
|
originalWarningName,
|
|
latestWarningName,
|
|
extraProps,
|
|
missingProps,
|
|
differentProps
|
|
);
|
|
}
|
|
}
|
|
}
|
|
function validateScriptResourceDifference(originalProps, latestProps) {
|
|
{
|
|
var src = originalProps.src; // eslint-disable-next-line no-labels
|
|
|
|
var originalWarningName = getResourceNameForWarning(
|
|
"script",
|
|
originalProps,
|
|
false
|
|
);
|
|
var latestWarningName = getResourceNameForWarning(
|
|
"script",
|
|
latestProps,
|
|
false
|
|
);
|
|
var extraProps = null;
|
|
var differentProps = null;
|
|
|
|
for (var propName in latestProps) {
|
|
var propValue = latestProps[propName];
|
|
var originalValue = originalProps[propName];
|
|
|
|
if (propValue != null && propValue !== originalValue) {
|
|
if (originalValue == null) {
|
|
extraProps = extraProps || {};
|
|
extraProps[propName] = propValue;
|
|
} else {
|
|
differentProps = differentProps || {};
|
|
differentProps[propName] = {
|
|
original: originalValue,
|
|
latest: propValue
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (extraProps || differentProps) {
|
|
warnDifferentProps(
|
|
src,
|
|
"src",
|
|
originalWarningName,
|
|
latestWarningName,
|
|
extraProps,
|
|
null,
|
|
differentProps
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function warnDifferentProps(
|
|
url,
|
|
urlPropKey,
|
|
originalName,
|
|
latestName,
|
|
extraProps,
|
|
missingProps,
|
|
differentProps
|
|
) {
|
|
{
|
|
var juxtaposedNameStatement =
|
|
latestName === originalName
|
|
? "an earlier instance of this Resource"
|
|
: "a " + originalName + " with the same " + urlPropKey;
|
|
var comparisonStatement = "";
|
|
|
|
if (missingProps !== null && typeof missingProps === "object") {
|
|
for (var propName in missingProps) {
|
|
comparisonStatement +=
|
|
"\n " +
|
|
propName +
|
|
': missing or null in latest props, "' +
|
|
missingProps[propName] +
|
|
'" in original props';
|
|
}
|
|
}
|
|
|
|
if (extraProps !== null && typeof extraProps === "object") {
|
|
for (var _propName in extraProps) {
|
|
comparisonStatement +=
|
|
"\n " +
|
|
_propName +
|
|
': "' +
|
|
extraProps[_propName] +
|
|
'" in latest props, missing or null in original props';
|
|
}
|
|
}
|
|
|
|
if (differentProps !== null && typeof differentProps === "object") {
|
|
for (var _propName2 in differentProps) {
|
|
comparisonStatement +=
|
|
"\n " +
|
|
_propName2 +
|
|
': "' +
|
|
differentProps[_propName2].latest +
|
|
'" in latest props, "' +
|
|
differentProps[_propName2].original +
|
|
'" in original props';
|
|
}
|
|
}
|
|
|
|
error(
|
|
'A %s with %s "%s" has props that disagree with those found on %s. Resources always use the props' +
|
|
" that were provided the first time they are encountered so any differences will be ignored. Please" +
|
|
" update Resources that share an %s to have props that agree. The differences are described below.%s",
|
|
latestName,
|
|
urlPropKey,
|
|
url,
|
|
juxtaposedNameStatement,
|
|
urlPropKey,
|
|
comparisonStatement
|
|
);
|
|
}
|
|
}
|
|
|
|
function getResourceNameForWarning(type, props, implicit) {
|
|
{
|
|
switch (type) {
|
|
case "style": {
|
|
return "style Resource";
|
|
}
|
|
|
|
case "script": {
|
|
return "script Resource";
|
|
}
|
|
|
|
case "preload": {
|
|
if (implicit) {
|
|
return "preload for a " + props.as + " Resource";
|
|
}
|
|
|
|
return 'preload Resource (as "' + props.as + '")';
|
|
}
|
|
}
|
|
}
|
|
|
|
return "Resource";
|
|
}
|
|
|
|
function validateURLKeyedUpdatedProps(
|
|
pendingProps,
|
|
currentProps,
|
|
resourceType,
|
|
urlPropKey
|
|
) {
|
|
{
|
|
// This function should never be called if we don't have /srcs so we don't bother considering
|
|
// Whether they are null or undefined
|
|
if (pendingProps[urlPropKey] === currentProps[urlPropKey]) {
|
|
// If we have the same href/src we need all other props to be the same
|
|
var missingProps;
|
|
var extraProps;
|
|
var differentProps;
|
|
var allProps = Array.from(
|
|
new Set(Object.keys(currentProps).concat(Object.keys(pendingProps)))
|
|
);
|
|
|
|
for (var i = 0; i < allProps.length; i++) {
|
|
var propName = allProps[i];
|
|
var pendingValue = pendingProps[propName];
|
|
var currentValue = currentProps[propName];
|
|
|
|
if (
|
|
pendingValue !== currentValue &&
|
|
!(pendingValue == null && currentValue == null)
|
|
) {
|
|
if (pendingValue == null) {
|
|
missingProps = missingProps || {};
|
|
missingProps[propName] = currentValue;
|
|
} else if (currentValue == null) {
|
|
extraProps = extraProps || {};
|
|
extraProps[propName] = pendingValue;
|
|
} else {
|
|
differentProps = differentProps || {};
|
|
differentProps[propName] = {
|
|
original: currentValue,
|
|
latest: pendingValue
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (missingProps || extraProps || differentProps) {
|
|
var latestWarningName = getResourceNameForWarning(
|
|
resourceType,
|
|
currentProps,
|
|
false
|
|
);
|
|
var comparisonStatement = "";
|
|
|
|
if (missingProps !== null && typeof missingProps === "object") {
|
|
for (var _propName3 in missingProps) {
|
|
// $FlowFixMe[incompatible-type]
|
|
comparisonStatement +=
|
|
"\n " +
|
|
_propName3 +
|
|
': missing or null in latest props, "' +
|
|
missingProps[_propName3] +
|
|
'" in original props';
|
|
}
|
|
}
|
|
|
|
if (extraProps !== null && typeof extraProps === "object") {
|
|
for (var _propName4 in extraProps) {
|
|
// $FlowFixMe[incompatible-type]
|
|
comparisonStatement +=
|
|
"\n " +
|
|
_propName4 +
|
|
': "' +
|
|
extraProps[_propName4] +
|
|
'" in latest props, missing or null in original props';
|
|
}
|
|
}
|
|
|
|
if (differentProps !== null && typeof differentProps === "object") {
|
|
for (var _propName5 in differentProps) {
|
|
// $FlowFixMe[incompatible-type]
|
|
comparisonStatement +=
|
|
"\n " +
|
|
_propName5 +
|
|
': "' +
|
|
differentProps[_propName5].latest +
|
|
'" in latest props, "' +
|
|
differentProps[_propName5].original +
|
|
'" in original props';
|
|
}
|
|
}
|
|
|
|
error(
|
|
'A %s with %s "%s" received new props with different values from the props used' +
|
|
" when this Resource was first rendered. React will only use the props provided when" +
|
|
" this resource was first rendered until a new %s is provided. Unlike conventional" +
|
|
" DOM elements, Resources instances do not have a one to one correspondence with Elements" +
|
|
" in the DOM and as such, every instance of a Resource for a single Resource identifier" +
|
|
" (%s) must have props that agree with each other. The differences are described below.%s",
|
|
latestWarningName,
|
|
urlPropKey,
|
|
currentProps[urlPropKey],
|
|
urlPropKey,
|
|
urlPropKey,
|
|
comparisonStatement
|
|
);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
function validateLinkPropsForStyleResource(props) {
|
|
{
|
|
// This should only be called when we know we are opting into Resource semantics (i.e. precedence is not null)
|
|
var href = props.href,
|
|
onLoad = props.onLoad,
|
|
onError = props.onError,
|
|
disabled = props.disabled;
|
|
var allProps = ["onLoad", "onError", "disabled"];
|
|
var includedProps = [];
|
|
if (onLoad) includedProps.push("onLoad");
|
|
if (onError) includedProps.push("onError");
|
|
if (disabled != null) includedProps.push("disabled");
|
|
var allPropsUnionPhrase = propNamesListJoin(allProps, "or");
|
|
var includedPropsPhrase = propNamesListJoin(includedProps, "and");
|
|
includedPropsPhrase += includedProps.length === 1 ? " prop" : " props";
|
|
|
|
if (includedProps.length) {
|
|
error(
|
|
'A link (rel="stylesheet") element with href "%s" has the precedence prop but also included the %s.' +
|
|
" When using %s React will opt out of Resource behavior. If you meant for this" +
|
|
" element to be treated as a Resource remove the %s. Otherwise remove the precedence prop.",
|
|
href,
|
|
includedPropsPhrase,
|
|
allPropsUnionPhrase,
|
|
includedPropsPhrase
|
|
);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function propNamesListJoin(list, combinator) {
|
|
switch (list.length) {
|
|
case 0:
|
|
return "";
|
|
|
|
case 1:
|
|
return list[0];
|
|
|
|
case 2:
|
|
return list[0] + " " + combinator + " " + list[1];
|
|
|
|
default:
|
|
return (
|
|
list.slice(0, -1).join(", ") +
|
|
", " +
|
|
combinator +
|
|
" " +
|
|
list[list.length - 1]
|
|
);
|
|
}
|
|
}
|
|
|
|
function validateLinkPropsForPreloadResource(linkProps) {
|
|
{
|
|
var href = linkProps.href,
|
|
as = linkProps.as;
|
|
|
|
if (as === "font") {
|
|
var name = getResourceNameForWarning("preload", linkProps, false);
|
|
|
|
if (!hasOwnProperty.call(linkProps, "crossOrigin")) {
|
|
error(
|
|
'A %s with href "%s" did not specify the crossOrigin prop. Font preloads must always use' +
|
|
' anonymouse CORS mode. To fix add an empty string, "anonymous", or any other string' +
|
|
' value except "use-credentials" for the crossOrigin prop of all font preloads.',
|
|
name,
|
|
href
|
|
);
|
|
} else if (linkProps.crossOrigin === "use-credentials") {
|
|
error(
|
|
'A %s with href "%s" specified a crossOrigin value of "use-credentials". Font preloads must always use' +
|
|
' anonymouse CORS mode. To fix use an empty string, "anonymous", or any other string' +
|
|
' value except "use-credentials" for the crossOrigin prop of all font preloads.',
|
|
name,
|
|
href
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function validatePreloadArguments(href, options) {
|
|
{
|
|
if (!href || typeof href !== "string") {
|
|
var typeOfArg = getValueDescriptorExpectingObjectForWarning(href);
|
|
|
|
error(
|
|
"ReactDOM.preload() expected the first argument to be a string representing an href but found %s instead.",
|
|
typeOfArg
|
|
);
|
|
} else if (typeof options !== "object" || options === null) {
|
|
var _typeOfArg = getValueDescriptorExpectingObjectForWarning(options);
|
|
|
|
error(
|
|
'ReactDOM.preload() expected the second argument to be an options argument containing at least an "as" property' +
|
|
' specifying the Resource type. It found %s instead. The href for the preload call where this warning originated is "%s".',
|
|
_typeOfArg,
|
|
href
|
|
);
|
|
} else {
|
|
var as = options.as;
|
|
|
|
switch (as) {
|
|
// Font specific validation of options
|
|
case "font": {
|
|
if (options.crossOrigin === "use-credentials") {
|
|
error(
|
|
'ReactDOM.preload() was called with an "as" type of "font" and with a "crossOrigin" option of "use-credentials".' +
|
|
' Fonts preloading must use crossOrigin "anonymous" to be functional. Please update your font preload to omit' +
|
|
' the crossOrigin option or change it to any other value than "use-credentials" (Browsers default all other values' +
|
|
' to anonymous mode). The href for the preload call where this warning originated is "%s"',
|
|
href
|
|
);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case "script":
|
|
case "style": {
|
|
break;
|
|
}
|
|
// We have an invalid as type and need to warn
|
|
|
|
default: {
|
|
var typeOfAs = getValueDescriptorExpectingEnumForWarning(as);
|
|
|
|
error(
|
|
'ReactDOM.preload() expected a valid "as" type in the options (second) argument but found %s instead.' +
|
|
" Please use one of the following valid values instead: %s. The href for the preload call where this" +
|
|
' warning originated is "%s".',
|
|
typeOfAs,
|
|
'"style", "font", or "script"',
|
|
href
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function validatePreinitArguments(href, options) {
|
|
{
|
|
if (!href || typeof href !== "string") {
|
|
var typeOfArg = getValueDescriptorExpectingObjectForWarning(href);
|
|
|
|
error(
|
|
"ReactDOM.preinit() expected the first argument to be a string representing an href but found %s instead.",
|
|
typeOfArg
|
|
);
|
|
} else if (typeof options !== "object" || options === null) {
|
|
var _typeOfArg2 = getValueDescriptorExpectingObjectForWarning(options);
|
|
|
|
error(
|
|
'ReactDOM.preinit() expected the second argument to be an options argument containing at least an "as" property' +
|
|
' specifying the Resource type. It found %s instead. The href for the preload call where this warning originated is "%s".',
|
|
_typeOfArg2,
|
|
href
|
|
);
|
|
} else {
|
|
var as = options.as;
|
|
|
|
switch (as) {
|
|
case "style":
|
|
case "script": {
|
|
break;
|
|
}
|
|
// We have an invalid as type and need to warn
|
|
|
|
default: {
|
|
var typeOfAs = getValueDescriptorExpectingEnumForWarning(as);
|
|
|
|
error(
|
|
'ReactDOM.preinit() expected the second argument to be an options argument containing at least an "as" property' +
|
|
' specifying the Resource type. It found %s instead. Currently, valid resource types for for preinit are "style"' +
|
|
' and "script". The href for the preinit call where this warning originated is "%s".',
|
|
typeOfAs,
|
|
href
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getValueDescriptorExpectingObjectForWarning(thing) {
|
|
return thing === null
|
|
? "null"
|
|
: thing === undefined
|
|
? "undefined"
|
|
: thing === ""
|
|
? "an empty string"
|
|
: 'something with type "' + typeof thing + '"';
|
|
}
|
|
|
|
function getValueDescriptorExpectingEnumForWarning(thing) {
|
|
return thing === null
|
|
? "null"
|
|
: thing === undefined
|
|
? "undefined"
|
|
: thing === ""
|
|
? "an empty string"
|
|
: typeof thing === "string"
|
|
? JSON.stringify(thing)
|
|
: 'something with type "' + typeof thing + '"';
|
|
}
|
|
|
|
var valueStack = [];
|
|
var fiberStack;
|
|
|
|
{
|
|
fiberStack = [];
|
|
}
|
|
|
|
var index = -1;
|
|
|
|
function createCursor(defaultValue) {
|
|
return {
|
|
current: defaultValue
|
|
};
|
|
}
|
|
|
|
function pop(cursor, fiber) {
|
|
if (index < 0) {
|
|
{
|
|
error("Unexpected pop.");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
{
|
|
if (fiber !== fiberStack[index]) {
|
|
error("Unexpected Fiber popped.");
|
|
}
|
|
}
|
|
|
|
cursor.current = valueStack[index];
|
|
valueStack[index] = null;
|
|
|
|
{
|
|
fiberStack[index] = null;
|
|
}
|
|
|
|
index--;
|
|
}
|
|
|
|
function push(cursor, value, fiber) {
|
|
index++;
|
|
valueStack[index] = cursor.current;
|
|
|
|
{
|
|
fiberStack[index] = fiber;
|
|
}
|
|
|
|
cursor.current = value;
|
|
}
|
|
|
|
var contextStackCursor = createCursor(null);
|
|
var contextFiberStackCursor = createCursor(null);
|
|
var rootInstanceStackCursor = createCursor(null);
|
|
|
|
function requiredContext(c) {
|
|
{
|
|
if (c === null) {
|
|
error(
|
|
"Expected host context to exist. This error is likely caused by a bug " +
|
|
"in React. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
function getCurrentRootHostContainer() {
|
|
return rootInstanceStackCursor.current;
|
|
}
|
|
|
|
function getRootHostContainer() {
|
|
var rootInstance = requiredContext(rootInstanceStackCursor.current);
|
|
return rootInstance;
|
|
}
|
|
|
|
function pushHostContainer(fiber, nextRootInstance) {
|
|
// Push current root instance onto the stack;
|
|
// This allows us to reset root when portals are popped.
|
|
push(rootInstanceStackCursor, nextRootInstance, fiber); // Track the context and the Fiber that provided it.
|
|
// This enables us to pop only Fibers that provide unique contexts.
|
|
|
|
push(contextFiberStackCursor, fiber, fiber); // Finally, we need to push the host context to the stack.
|
|
// However, we can't just call getRootHostContext() and push it because
|
|
// we'd have a different number of entries on the stack depending on
|
|
// whether getRootHostContext() throws somewhere in renderer code or not.
|
|
// So we push an empty value first. This lets us safely unwind on errors.
|
|
|
|
push(contextStackCursor, null, fiber);
|
|
var nextRootContext = getRootHostContext(nextRootInstance); // Now that we know this function doesn't throw, replace it.
|
|
|
|
pop(contextStackCursor, fiber);
|
|
push(contextStackCursor, nextRootContext, fiber);
|
|
}
|
|
|
|
function popHostContainer(fiber) {
|
|
pop(contextStackCursor, fiber);
|
|
pop(contextFiberStackCursor, fiber);
|
|
pop(rootInstanceStackCursor, fiber);
|
|
}
|
|
|
|
function getHostContext() {
|
|
var context = requiredContext(contextStackCursor.current);
|
|
return context;
|
|
}
|
|
|
|
function pushHostContext(fiber) {
|
|
var context = requiredContext(contextStackCursor.current);
|
|
var nextContext = getChildHostContext(context, fiber.type); // Don't push this Fiber's context unless it's unique.
|
|
|
|
if (context === nextContext) {
|
|
return;
|
|
} // Track the context and the Fiber that provided it.
|
|
// This enables us to pop only Fibers that provide unique contexts.
|
|
|
|
push(contextFiberStackCursor, fiber, fiber);
|
|
push(contextStackCursor, nextContext, fiber);
|
|
}
|
|
|
|
function popHostContext(fiber) {
|
|
// Do not pop unless this Fiber provided the current context.
|
|
// pushHostContext() only pushes Fibers that provide unique contexts.
|
|
if (contextFiberStackCursor.current !== fiber) {
|
|
return;
|
|
}
|
|
|
|
pop(contextStackCursor, fiber);
|
|
pop(contextFiberStackCursor, fiber);
|
|
}
|
|
|
|
var Dispatcher = ReactDOMSharedInternals.Dispatcher;
|
|
// In the future this may need to change, especially when modules / scripts are supported
|
|
// Brief on purpose due to insertion by script when streaming late boundaries
|
|
// s = Status
|
|
// l = loaded
|
|
// e = errored
|
|
// It is valid to preload even when we aren't actively rendering. For cases where Float functions are
|
|
// called when there is no rendering we track the last used document. It is not safe to insert
|
|
// arbitrary resources into the lastCurrentDocument b/c it may not actually be the document
|
|
// that the resource is meant to apply too (for example stylesheets or scripts). This is only
|
|
// appropriate for resources that don't really have a strict tie to the document itself for example
|
|
// preloads
|
|
|
|
var lastCurrentDocument = null;
|
|
var previousDispatcher = null;
|
|
function prepareToRenderResources(rootContainer) {
|
|
var rootNode = getRootNode(rootContainer);
|
|
lastCurrentDocument = getDocumentFromRoot(rootNode);
|
|
previousDispatcher = Dispatcher.current;
|
|
Dispatcher.current = ReactDOMClientDispatcher;
|
|
}
|
|
function cleanupAfterRenderResources() {
|
|
Dispatcher.current = previousDispatcher;
|
|
previousDispatcher = null;
|
|
} // We want this to be the default dispatcher on ReactDOMSharedInternals but we don't want to mutate
|
|
// internals in Module scope. Instead we export it and Internals will import it. There is already a cycle
|
|
// from Internals -> ReactDOM -> FloatClient -> Internals so this doesn't introduce a new one.
|
|
|
|
var ReactDOMClientDispatcher = {
|
|
preload: preload,
|
|
preinit: preinit
|
|
}; // global maps of Resources
|
|
|
|
var preloadResources = new Map(); // getRootNode is missing from IE and old jsdom versions
|
|
|
|
function getRootNode(container) {
|
|
// $FlowFixMe[method-unbinding]
|
|
return typeof container.getRootNode === "function"
|
|
? /* $FlowFixMe[incompatible-return] Flow types this as returning a `Node`,
|
|
* but it's either a `Document` or `ShadowRoot`. */
|
|
container.getRootNode()
|
|
: container.ownerDocument;
|
|
}
|
|
|
|
function getCurrentResourceRoot() {
|
|
var currentContainer = getCurrentRootHostContainer();
|
|
return currentContainer ? getRootNode(currentContainer) : null;
|
|
} // This resource type constraint can be loosened. It really is everything except PreloadResource
|
|
// because that is the only one that does not have an optional instance type. Expand as needed.
|
|
|
|
function resetInstance(resource) {
|
|
resource.instance = undefined;
|
|
}
|
|
|
|
function clearRootResources(rootContainer) {
|
|
var rootNode = getRootNode(rootContainer);
|
|
var resources = getResourcesFromRoot(rootNode); // We can't actually delete the resource cache because this function is called
|
|
// during commit after we have rendered. Instead we detatch any instances from
|
|
// the Resource object if they are going to be cleared
|
|
// Styles stay put
|
|
// Scripts get reset
|
|
|
|
resources.scripts.forEach(resetInstance); // Head Resources get reset
|
|
|
|
resources.head.forEach(resetInstance); // lastStructuredMeta stays put
|
|
} // Preloads are somewhat special. Even if we don't have the Document
|
|
// used by the root that is rendering a component trying to insert a preload
|
|
// we can still seed the file cache by doing the preload on any document we have
|
|
// access to. We prefer the currentDocument if it exists, we also prefer the
|
|
// lastCurrentDocument if that exists. As a fallback we will use the window.document
|
|
// if available.
|
|
|
|
function getDocumentForPreloads() {
|
|
var root = getCurrentResourceRoot();
|
|
|
|
if (root) {
|
|
return root.ownerDocument || root;
|
|
} else {
|
|
try {
|
|
return lastCurrentDocument || window.document;
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getDocumentFromRoot(root) {
|
|
return root.ownerDocument || root;
|
|
} // --------------------------------------
|
|
// ReactDOM.Preload
|
|
// --------------------------------------
|
|
|
|
function preload(href, options) {
|
|
{
|
|
validatePreloadArguments(href, options);
|
|
}
|
|
|
|
var ownerDocument = getDocumentForPreloads();
|
|
|
|
if (
|
|
typeof href === "string" &&
|
|
href &&
|
|
typeof options === "object" &&
|
|
options !== null &&
|
|
ownerDocument
|
|
) {
|
|
var as = options.as;
|
|
var resource = preloadResources.get(href);
|
|
|
|
if (resource) {
|
|
{
|
|
var originallyImplicit = resource._dev_implicit_construction === true;
|
|
var latestProps = preloadPropsFromPreloadOptions(href, as, options);
|
|
validatePreloadResourceDifference(
|
|
resource.props,
|
|
originallyImplicit,
|
|
latestProps,
|
|
false
|
|
);
|
|
}
|
|
} else {
|
|
var resourceProps = preloadPropsFromPreloadOptions(href, as, options);
|
|
createPreloadResource(ownerDocument, href, resourceProps);
|
|
}
|
|
}
|
|
}
|
|
|
|
function preloadPropsFromPreloadOptions(href, as, options) {
|
|
return {
|
|
href: href,
|
|
rel: "preload",
|
|
as: as,
|
|
crossOrigin: as === "font" ? "" : options.crossOrigin,
|
|
integrity: options.integrity
|
|
};
|
|
} // --------------------------------------
|
|
// ReactDOM.preinit
|
|
// --------------------------------------
|
|
|
|
function preinit(href, options) {
|
|
{
|
|
validatePreinitArguments(href, options);
|
|
}
|
|
|
|
if (
|
|
typeof href === "string" &&
|
|
href &&
|
|
typeof options === "object" &&
|
|
options !== null
|
|
) {
|
|
var resourceRoot = getCurrentResourceRoot();
|
|
var as = options.as;
|
|
|
|
if (!resourceRoot) {
|
|
// We are going to emit a preload as a best effort fallback since this preinit
|
|
// was called outside of a render. Given the passive nature of this fallback
|
|
// we do not warn in dev when props disagree if there happens to already be a
|
|
// matching preload with this href
|
|
var preloadDocument = getDocumentForPreloads();
|
|
|
|
if (preloadDocument) {
|
|
var preloadResource = preloadResources.get(href);
|
|
|
|
if (!preloadResource) {
|
|
var preloadProps = preloadPropsFromPreinitOptions(href, as, options);
|
|
createPreloadResource(preloadDocument, href, preloadProps);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
switch (as) {
|
|
case "style": {
|
|
var styleResources = getResourcesFromRoot(resourceRoot).styles;
|
|
var precedence = options.precedence || "default";
|
|
var resource = styleResources.get(href);
|
|
|
|
if (resource) {
|
|
{
|
|
var latestProps = stylePropsFromPreinitOptions(
|
|
href,
|
|
precedence,
|
|
options
|
|
);
|
|
validateStyleResourceDifference(resource.props, latestProps);
|
|
}
|
|
} else {
|
|
var resourceProps = stylePropsFromPreinitOptions(
|
|
href,
|
|
precedence,
|
|
options
|
|
);
|
|
resource = createStyleResource(
|
|
styleResources,
|
|
resourceRoot,
|
|
href,
|
|
precedence,
|
|
resourceProps
|
|
);
|
|
}
|
|
|
|
acquireResource(resource);
|
|
return;
|
|
}
|
|
|
|
case "script": {
|
|
var src = href;
|
|
var scriptResources = getResourcesFromRoot(resourceRoot).scripts;
|
|
|
|
var _resource = scriptResources.get(src);
|
|
|
|
if (_resource) {
|
|
{
|
|
var _latestProps = scriptPropsFromPreinitOptions(src, options);
|
|
|
|
validateScriptResourceDifference(_resource.props, _latestProps);
|
|
}
|
|
} else {
|
|
var _resourceProps = scriptPropsFromPreinitOptions(src, options);
|
|
|
|
_resource = createScriptResource(
|
|
scriptResources,
|
|
resourceRoot,
|
|
src,
|
|
_resourceProps
|
|
);
|
|
}
|
|
|
|
acquireResource(_resource);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function preloadPropsFromPreinitOptions(href, as, options) {
|
|
return {
|
|
href: href,
|
|
rel: "preload",
|
|
as: as,
|
|
crossOrigin: as === "font" ? "" : options.crossOrigin,
|
|
integrity: options.integrity
|
|
};
|
|
}
|
|
|
|
function stylePropsFromPreinitOptions(href, precedence, options) {
|
|
return {
|
|
rel: "stylesheet",
|
|
href: href,
|
|
"data-precedence": precedence,
|
|
crossOrigin: options.crossOrigin
|
|
};
|
|
}
|
|
|
|
function scriptPropsFromPreinitOptions(src, options) {
|
|
return {
|
|
src: src,
|
|
async: true,
|
|
crossOrigin: options.crossOrigin,
|
|
integrity: options.integrity
|
|
};
|
|
} // --------------------------------------
|
|
// Resources from render
|
|
// --------------------------------------
|
|
|
|
function getTitleKey(child) {
|
|
return "title:" + child;
|
|
} // This function is called in begin work and we should always have a currentDocument set
|
|
|
|
function getResource(type, pendingProps, currentProps) {
|
|
var resourceRoot = getCurrentResourceRoot();
|
|
|
|
if (!resourceRoot) {
|
|
throw new Error(
|
|
'"resourceRoot" was expected to exist. This is a bug in React.'
|
|
);
|
|
}
|
|
|
|
switch (type) {
|
|
case "base": {
|
|
var headRoot = getDocumentFromRoot(resourceRoot);
|
|
var headResources = getResourcesFromRoot(headRoot).head;
|
|
var target = pendingProps.target,
|
|
href = pendingProps.href;
|
|
var matcher = "base";
|
|
matcher +=
|
|
typeof href === "string"
|
|
? '[href="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(href) +
|
|
'"]'
|
|
: ":not([href])";
|
|
matcher +=
|
|
typeof target === "string"
|
|
? '[target="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(target) +
|
|
'"]'
|
|
: ":not([target])";
|
|
var resource = headResources.get(matcher);
|
|
|
|
if (!resource) {
|
|
resource = {
|
|
type: "base",
|
|
matcher: matcher,
|
|
props: assign({}, pendingProps),
|
|
count: 0,
|
|
instance: null,
|
|
root: headRoot
|
|
};
|
|
headResources.set(matcher, resource);
|
|
}
|
|
|
|
return resource;
|
|
}
|
|
|
|
case "meta": {
|
|
var _matcher, propertyString, parentResource;
|
|
|
|
var charSet = pendingProps.charSet,
|
|
content = pendingProps.content,
|
|
httpEquiv = pendingProps.httpEquiv,
|
|
name = pendingProps.name,
|
|
itemProp = pendingProps.itemProp,
|
|
property = pendingProps.property;
|
|
|
|
var _headRoot = getDocumentFromRoot(resourceRoot);
|
|
|
|
var _getResourcesFromRoot = getResourcesFromRoot(_headRoot),
|
|
_headResources = _getResourcesFromRoot.head,
|
|
lastStructuredMeta = _getResourcesFromRoot.lastStructuredMeta;
|
|
|
|
if (typeof charSet === "string") {
|
|
_matcher = "meta[charset]";
|
|
} else if (typeof content === "string") {
|
|
if (typeof httpEquiv === "string") {
|
|
_matcher =
|
|
'meta[http-equiv="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(httpEquiv) +
|
|
'"][content="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(content) +
|
|
'"]';
|
|
} else if (typeof property === "string") {
|
|
propertyString = property;
|
|
_matcher =
|
|
'meta[property="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(property) +
|
|
'"][content="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(content) +
|
|
'"]';
|
|
var parentPropertyPath = property
|
|
.split(":")
|
|
.slice(0, -1)
|
|
.join(":");
|
|
parentResource = lastStructuredMeta.get(parentPropertyPath);
|
|
|
|
if (parentResource) {
|
|
// When using parentResource the matcher is not functional for locating
|
|
// the instance in the DOM but it still serves as a unique key.
|
|
_matcher = parentResource.matcher + _matcher;
|
|
}
|
|
} else if (typeof name === "string") {
|
|
_matcher =
|
|
'meta[name="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(name) +
|
|
'"][content="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(content) +
|
|
'"]';
|
|
} else if (typeof itemProp === "string") {
|
|
_matcher =
|
|
'meta[itemprop="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(itemProp) +
|
|
'"][content="' +
|
|
escapeSelectorAttributeValueInsideDoubleQuotes(content) +
|
|
'"]';
|
|
}
|
|
}
|
|
|
|
if (_matcher) {
|
|
var _resource2 = _headResources.get(_matcher);
|
|
|
|
if (!_resource2) {
|
|
_resource2 = {
|
|
type: "meta",
|
|
matcher: _matcher,
|
|
property: propertyString,
|
|
parentResource: parentResource,
|
|
props: assign({}, pendingProps),
|
|
count: 0,
|
|
instance: null,
|
|
root: _headRoot
|
|
};
|
|
|
|
_headResources.set(_matcher, _resource2);
|
|
}
|
|
|
|
if (typeof _resource2.property === "string") {
|
|
// We cast because flow doesn't know that this resource must be a Meta resource
|
|
lastStructuredMeta.set(_resource2.property, _resource2);
|
|
}
|
|
|
|
return _resource2;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
case "title": {
|
|
var children = pendingProps.children;
|
|
var child;
|
|
|
|
if (Array.isArray(children)) {
|
|
child = children.length === 1 ? children[0] : null;
|
|
} else {
|
|
child = children;
|
|
}
|
|
|
|
if (
|
|
typeof child !== "function" &&
|
|
typeof child !== "symbol" &&
|
|
child !== null &&
|
|
child !== undefined
|
|
) {
|
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
|
var childString = "" + child;
|
|
|
|
var _headRoot2 = getDocumentFromRoot(resourceRoot);
|
|
|
|
var _headResources2 = getResourcesFromRoot(_headRoot2).head;
|
|
var key = getTitleKey(childString);
|
|
|
|
var _resource3 = _headResources2.get(key);
|
|
|
|
if (!_resource3) {
|
|
var titleProps = titlePropsFromRawProps(childString, pendingProps);
|
|
_resource3 = {
|
|
type: "title",
|
|
props: titleProps,
|
|
count: 0,
|
|
instance: null,
|
|
root: _headRoot2
|
|
};
|
|
|
|
_headResources2.set(key, _resource3);
|
|
}
|
|
|
|
return _resource3;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
case "link": {
|
|
var rel = pendingProps.rel;
|
|
|
|
switch (rel) {
|
|
case "stylesheet": {
|
|
var styleResources = getResourcesFromRoot(resourceRoot).styles;
|
|
var didWarn;
|
|
|
|
{
|
|
if (currentProps) {
|
|
didWarn = validateURLKeyedUpdatedProps(
|
|
pendingProps,
|
|
currentProps,
|
|
"style",
|
|
"href"
|
|
);
|
|
}
|
|
|
|
if (!didWarn) {
|
|
didWarn = validateLinkPropsForStyleResource(pendingProps);
|
|
}
|
|
}
|
|
|
|
var precedence = pendingProps.precedence,
|
|
_href = pendingProps.href;
|
|
|
|
if (typeof _href === "string" && typeof precedence === "string") {
|
|
// We've asserted all the specific types for StyleQualifyingProps
|
|
var styleRawProps = pendingProps; // We construct or get an existing resource for the style itself and return it
|
|
|
|
var _resource4 = styleResources.get(_href);
|
|
|
|
if (_resource4) {
|
|
{
|
|
if (!didWarn) {
|
|
var latestProps = stylePropsFromRawProps(styleRawProps);
|
|
|
|
if (_resource4._dev_preload_props) {
|
|
adoptPreloadPropsForStyle(
|
|
latestProps,
|
|
_resource4._dev_preload_props
|
|
);
|
|
}
|
|
|
|
validateStyleResourceDifference(
|
|
_resource4.props,
|
|
latestProps
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
var resourceProps = stylePropsFromRawProps(styleRawProps);
|
|
_resource4 = createStyleResource(
|
|
styleResources,
|
|
resourceRoot,
|
|
_href,
|
|
precedence,
|
|
resourceProps
|
|
);
|
|
immediatelyPreloadStyleResource(_resource4);
|
|
}
|
|
|
|
return _resource4;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
case "preload": {
|
|
{
|
|
validateLinkPropsForPreloadResource(pendingProps);
|
|
}
|
|
|
|
var _href2 = pendingProps.href;
|
|
|
|
if (typeof _href2 === "string") {
|
|
// We've asserted all the specific types for PreloadQualifyingProps
|
|
var preloadRawProps = pendingProps;
|
|
|
|
var _resource5 = preloadResources.get(_href2);
|
|
|
|
if (_resource5) {
|
|
{
|
|
var originallyImplicit =
|
|
_resource5._dev_implicit_construction === true;
|
|
|
|
var _latestProps2 = preloadPropsFromRawProps(preloadRawProps);
|
|
|
|
validatePreloadResourceDifference(
|
|
_resource5.props,
|
|
originallyImplicit,
|
|
_latestProps2,
|
|
false
|
|
);
|
|
}
|
|
} else {
|
|
var _resourceProps2 = preloadPropsFromRawProps(preloadRawProps);
|
|
|
|
_resource5 = createPreloadResource(
|
|
getDocumentFromRoot(resourceRoot),
|
|
_href2,
|
|
_resourceProps2
|
|
);
|
|
}
|
|
|
|
return _resource5;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
default: {
|
|
var _href3 = pendingProps.href,
|
|
sizes = pendingProps.sizes,
|
|
media = pendingProps.media;
|
|
|
|
if (typeof rel === "string" && typeof _href3 === "string") {
|
|
var sizeKey = "::sizes:" + (typeof sizes === "string" ? sizes : "");
|
|
var mediaKey =
|
|
"::media:" + (typeof media === "string" ? media : "");
|
|
|
|
var _key = "rel:" + rel + "::href:" + _href3 + sizeKey + mediaKey;
|
|
|
|
var _headRoot3 = getDocumentFromRoot(resourceRoot);
|
|
|
|
var _headResources3 = getResourcesFromRoot(_headRoot3).head;
|
|
|
|
var _resource6 = _headResources3.get(_key);
|
|
|
|
if (!_resource6) {
|
|
_resource6 = {
|
|
type: "link",
|
|
props: assign({}, pendingProps),
|
|
count: 0,
|
|
instance: null,
|
|
root: _headRoot3
|
|
};
|
|
|
|
_headResources3.set(_key, _resource6);
|
|
}
|
|
|
|
return _resource6;
|
|
}
|
|
|
|
{
|
|
warnOnMissingHrefAndRel(pendingProps, currentProps);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
case "script": {
|
|
var scriptResources = getResourcesFromRoot(resourceRoot).scripts;
|
|
|
|
var _didWarn;
|
|
|
|
{
|
|
if (currentProps) {
|
|
_didWarn = validateURLKeyedUpdatedProps(
|
|
pendingProps,
|
|
currentProps,
|
|
"script",
|
|
"src"
|
|
);
|
|
}
|
|
}
|
|
|
|
var src = pendingProps.src,
|
|
async = pendingProps.async;
|
|
|
|
if (async && typeof src === "string") {
|
|
var scriptRawProps = pendingProps;
|
|
|
|
var _resource7 = scriptResources.get(src);
|
|
|
|
if (_resource7) {
|
|
{
|
|
if (!_didWarn) {
|
|
var _latestProps3 = scriptPropsFromRawProps(scriptRawProps);
|
|
|
|
if (_resource7._dev_preload_props) {
|
|
adoptPreloadPropsForScript(
|
|
_latestProps3,
|
|
_resource7._dev_preload_props
|
|
);
|
|
}
|
|
|
|
validateScriptResourceDifference(_resource7.props, _latestProps3);
|
|
}
|
|
}
|
|
} else {
|
|
var _resourceProps3 = scriptPropsFromRawProps(scriptRawProps);
|
|
|
|
_resource7 = createScriptResource(
|
|
scriptResources,
|
|
resourceRoot,
|
|
src,
|
|
_resourceProps3
|
|
);
|
|
}
|
|
|
|
return _resource7;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
default: {
|
|
throw new Error(
|
|
'getResource encountered a resource type it did not expect: "' +
|
|
type +
|
|
'". this is a bug in React.'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function preloadPropsFromRawProps(rawBorrowedProps) {
|
|
// $FlowFixMe[prop-missing] - recommended fix is to use object spread operator
|
|
return assign({}, rawBorrowedProps);
|
|
}
|
|
|
|
function titlePropsFromRawProps(child, rawProps) {
|
|
var props = assign({}, rawProps);
|
|
|
|
props.children = child;
|
|
return props;
|
|
}
|
|
|
|
function stylePropsFromRawProps(rawProps) {
|
|
// $FlowFixMe[prop-missing] - recommended fix is to use object spread operator
|
|
var props = assign({}, rawProps);
|
|
|
|
props["data-precedence"] = rawProps.precedence;
|
|
props.precedence = null;
|
|
return props;
|
|
}
|
|
|
|
function scriptPropsFromRawProps(rawProps) {
|
|
// $FlowFixMe[prop-missing] - recommended fix is to use object spread operator
|
|
var props = assign({}, rawProps);
|
|
|
|
return props;
|
|
} // --------------------------------------
|
|
// Resource Reconciliation
|
|
// --------------------------------------
|
|
|
|
function acquireResource(resource) {
|
|
switch (resource.type) {
|
|
case "base":
|
|
case "title":
|
|
case "link":
|
|
case "meta": {
|
|
return acquireHeadResource(resource);
|
|
}
|
|
|
|
case "style": {
|
|
return acquireStyleResource(resource);
|
|
}
|
|
|
|
case "script": {
|
|
return acquireScriptResource(resource);
|
|
}
|
|
|
|
case "preload": {
|
|
return resource.instance;
|
|
}
|
|
|
|
default: {
|
|
throw new Error(
|
|
'acquireResource encountered a resource type it did not expect: "' +
|
|
resource.type +
|
|
'". this is a bug in React.'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
function releaseResource(resource) {
|
|
switch (resource.type) {
|
|
case "link":
|
|
case "title":
|
|
case "meta": {
|
|
return releaseHeadResource(resource);
|
|
}
|
|
|
|
case "style": {
|
|
resource.count--;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function releaseHeadResource(resource) {
|
|
if (--resource.count === 0) {
|
|
// the instance will have existed since we acquired it
|
|
var instance = resource.instance;
|
|
var parent = instance.parentNode;
|
|
|
|
if (parent) {
|
|
parent.removeChild(instance);
|
|
}
|
|
|
|
resource.instance = null;
|
|
}
|
|
}
|
|
|
|
function createResourceInstance(type, props, ownerDocument) {
|
|
var element = createElement(type, props, ownerDocument, HTML_NAMESPACE);
|
|
setInitialProperties(element, type, props);
|
|
markNodeAsResource(element);
|
|
return element;
|
|
}
|
|
|
|
function createStyleResource(styleResources, root, href, precedence, props) {
|
|
{
|
|
if (styleResources.has(href)) {
|
|
error(
|
|
"createStyleResource was called when a style Resource matching the same href already exists. This is a bug in React."
|
|
);
|
|
}
|
|
}
|
|
|
|
var limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(href);
|
|
var existingEl = root.querySelector(
|
|
'link[rel="stylesheet"][href="' + limitedEscapedHref + '"]'
|
|
);
|
|
var resource = {
|
|
type: "style",
|
|
count: 0,
|
|
href: href,
|
|
precedence: precedence,
|
|
props: props,
|
|
hint: null,
|
|
preloaded: false,
|
|
loaded: false,
|
|
error: false,
|
|
root: root,
|
|
instance: null
|
|
};
|
|
styleResources.set(href, resource);
|
|
|
|
if (existingEl) {
|
|
// If we have an existing element in the DOM we don't need to preload this resource nor can we
|
|
// adopt props from any preload that might exist already for this resource. We do need to try
|
|
// to reify the Resource loading state the best we can.
|
|
var loadingState = existingEl._p;
|
|
|
|
if (loadingState) {
|
|
switch (loadingState.s) {
|
|
case "l": {
|
|
resource.loaded = true;
|
|
break;
|
|
}
|
|
|
|
case "e": {
|
|
resource.error = true;
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
attachLoadListeners(existingEl, resource);
|
|
}
|
|
}
|
|
} else {
|
|
// This is unfortunately just an assumption. The rationale here is that stylesheets without
|
|
// a loading state must have been flushed in the shell and would have blocked until loading
|
|
// or error. we can't know afterwards which happened for all types of stylesheets (cross origin)
|
|
// for instance) and the techniques for determining if a sheet has loaded that we do have still
|
|
// fail if the sheet loaded zero rules. At the moment we are going to just opt to assume the
|
|
// sheet is loaded if it was flushed in the shell
|
|
resource.loaded = true;
|
|
}
|
|
} else {
|
|
var hint = preloadResources.get(href);
|
|
|
|
if (hint) {
|
|
// $FlowFixMe[incompatible-type]: found when upgrading Flow
|
|
resource.hint = hint; // If a preload for this style Resource already exists there are certain props we want to adopt
|
|
// on the style Resource, primarily focussed on making sure the style network pathways utilize
|
|
// the preload pathways. For instance if you have diffreent crossOrigin attributes for a preload
|
|
// and a stylesheet the stylesheet will make a new request even if the preload had already loaded
|
|
|
|
var preloadProps = hint.props;
|
|
adoptPreloadPropsForStyle(resource.props, hint.props);
|
|
|
|
{
|
|
resource._dev_preload_props = preloadProps;
|
|
}
|
|
}
|
|
}
|
|
|
|
return resource;
|
|
}
|
|
|
|
function adoptPreloadPropsForStyle(styleProps, preloadProps) {
|
|
if (styleProps.crossOrigin == null)
|
|
styleProps.crossOrigin = preloadProps.crossOrigin;
|
|
if (styleProps.referrerPolicy == null)
|
|
styleProps.referrerPolicy = preloadProps.referrerPolicy;
|
|
if (styleProps.title == null) styleProps.title = preloadProps.title;
|
|
}
|
|
|
|
function immediatelyPreloadStyleResource(resource) {
|
|
// This function must be called synchronously after creating a styleResource otherwise it may
|
|
// violate assumptions around the existence of a preload. The reason it is extracted out is we
|
|
// don't always want to preload a style, in particular when we are going to synchronously insert
|
|
// that style. We confirm the style resource has no preload already and then construct it. If
|
|
// we wait and call this later it is possible a preload will already exist for this href
|
|
if (resource.loaded === false && resource.hint === null) {
|
|
var href = resource.href,
|
|
props = resource.props;
|
|
var preloadProps = preloadPropsFromStyleProps(props);
|
|
resource.hint = createPreloadResource(
|
|
getDocumentFromRoot(resource.root),
|
|
href,
|
|
preloadProps
|
|
);
|
|
}
|
|
}
|
|
|
|
function preloadPropsFromStyleProps(props) {
|
|
return {
|
|
rel: "preload",
|
|
as: "style",
|
|
href: props.href,
|
|
crossOrigin: props.crossOrigin,
|
|
integrity: props.integrity,
|
|
media: props.media,
|
|
hrefLang: props.hrefLang,
|
|
referrerPolicy: props.referrerPolicy
|
|
};
|
|
}
|
|
|
|
function createScriptResource(scriptResources, root, src, props) {
|
|
{
|
|
if (scriptResources.has(src)) {
|
|
error(
|
|
"createScriptResource was called when a script Resource matching the same src already exists. This is a bug in React."
|
|
);
|
|
}
|
|
}
|
|
|
|
var limitedEscapedSrc = escapeSelectorAttributeValueInsideDoubleQuotes(src);
|
|
var existingEl = root.querySelector(
|
|
'script[async][src="' + limitedEscapedSrc + '"]'
|
|
);
|
|
var resource = {
|
|
type: "script",
|
|
src: src,
|
|
props: props,
|
|
root: root,
|
|
instance: existingEl || null
|
|
};
|
|
scriptResources.set(src, resource);
|
|
|
|
if (!existingEl) {
|
|
var hint = preloadResources.get(src);
|
|
|
|
if (hint) {
|
|
// If a preload for this style Resource already exists there are certain props we want to adopt
|
|
// on the style Resource, primarily focussed on making sure the style network pathways utilize
|
|
// the preload pathways. For instance if you have diffreent crossOrigin attributes for a preload
|
|
// and a stylesheet the stylesheet will make a new request even if the preload had already loaded
|
|
var preloadProps = hint.props;
|
|
adoptPreloadPropsForScript(props, hint.props);
|
|
|
|
{
|
|
resource._dev_preload_props = preloadProps;
|
|
}
|
|
}
|
|
} else {
|
|
markNodeAsResource(existingEl);
|
|
}
|
|
|
|
return resource;
|
|
}
|
|
|
|
function adoptPreloadPropsForScript(scriptProps, preloadProps) {
|
|
if (scriptProps.crossOrigin == null)
|
|
scriptProps.crossOrigin = preloadProps.crossOrigin;
|
|
if (scriptProps.referrerPolicy == null)
|
|
scriptProps.referrerPolicy = preloadProps.referrerPolicy;
|
|
if (scriptProps.integrity == null)
|
|
scriptProps.referrerPolicy = preloadProps.integrity;
|
|
}
|
|
|
|
function createPreloadResource(ownerDocument, href, props) {
|
|
var limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(href);
|
|
var element = ownerDocument.querySelector(
|
|
'link[rel="preload"][href="' + limitedEscapedHref + '"]'
|
|
);
|
|
|
|
if (!element) {
|
|
element = createResourceInstance("link", props, ownerDocument);
|
|
insertResourceInstanceBefore(ownerDocument, element, null);
|
|
} else {
|
|
markNodeAsResource(element);
|
|
}
|
|
|
|
return {
|
|
type: "preload",
|
|
href: href,
|
|
ownerDocument: ownerDocument,
|
|
props: props,
|
|
instance: element
|
|
};
|
|
}
|
|
|
|
function acquireHeadResource(resource) {
|
|
resource.count++;
|
|
var instance = resource.instance;
|
|
|
|
if (!instance) {
|
|
var props = resource.props,
|
|
root = resource.root,
|
|
type = resource.type;
|
|
|
|
switch (type) {
|
|
case "title": {
|
|
var titles = root.querySelectorAll("title");
|
|
|
|
for (var i = 0; i < titles.length; i++) {
|
|
if (titles[i].textContent === props.children) {
|
|
instance = resource.instance = titles[i];
|
|
markNodeAsResource(instance);
|
|
return instance;
|
|
}
|
|
}
|
|
|
|
instance = resource.instance = createResourceInstance(
|
|
type,
|
|
props,
|
|
root
|
|
);
|
|
var firstTitle = titles[0];
|
|
insertResourceInstanceBefore(
|
|
root,
|
|
instance,
|
|
firstTitle && firstTitle.namespaceURI !== SVG_NAMESPACE
|
|
? firstTitle
|
|
: null
|
|
);
|
|
break;
|
|
}
|
|
|
|
case "meta": {
|
|
var insertBefore = null;
|
|
var metaResource = resource;
|
|
var matcher = metaResource.matcher,
|
|
property = metaResource.property,
|
|
parentResource = metaResource.parentResource;
|
|
|
|
if (parentResource && typeof property === "string") {
|
|
// This resoruce is a structured meta type with a parent.
|
|
// Instead of using the matcher we just traverse forward
|
|
// siblings of the parent instance until we find a match
|
|
// or exhaust.
|
|
var parent = parentResource.instance;
|
|
|
|
if (parent) {
|
|
var node = null;
|
|
var nextNode = (insertBefore = parent.nextSibling);
|
|
|
|
while ((node = nextNode)) {
|
|
nextNode = node.nextSibling;
|
|
|
|
if (node.nodeName === "META") {
|
|
var meta = node;
|
|
var propertyAttr = meta.getAttribute("property");
|
|
|
|
if (typeof propertyAttr !== "string") {
|
|
continue;
|
|
} else if (
|
|
propertyAttr === property &&
|
|
meta.getAttribute("content") === props.content
|
|
) {
|
|
resource.instance = meta;
|
|
markNodeAsResource(meta);
|
|
return meta;
|
|
} else if (property.startsWith(propertyAttr + ":")) {
|
|
// This meta starts a new instance of a parent structure for this meta type
|
|
// We need to halt our search here because even if we find a later match it
|
|
// is for a different parent element
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if ((instance = root.querySelector(matcher))) {
|
|
resource.instance = instance;
|
|
markNodeAsResource(instance);
|
|
return instance;
|
|
}
|
|
|
|
instance = resource.instance = createResourceInstance(
|
|
type,
|
|
props,
|
|
root
|
|
);
|
|
insertResourceInstanceBefore(root, instance, insertBefore);
|
|
break;
|
|
}
|
|
|
|
case "link": {
|
|
var linkProps = props;
|
|
var limitedEscapedRel = escapeSelectorAttributeValueInsideDoubleQuotes(
|
|
linkProps.rel
|
|
);
|
|
var limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(
|
|
linkProps.href
|
|
);
|
|
var selector =
|
|
'link[rel="' +
|
|
limitedEscapedRel +
|
|
'"][href="' +
|
|
limitedEscapedHref +
|
|
'"]';
|
|
|
|
if (typeof linkProps.sizes === "string") {
|
|
var limitedEscapedSizes = escapeSelectorAttributeValueInsideDoubleQuotes(
|
|
linkProps.sizes
|
|
);
|
|
selector += '[sizes="' + limitedEscapedSizes + '"]';
|
|
}
|
|
|
|
if (typeof linkProps.media === "string") {
|
|
var limitedEscapedMedia = escapeSelectorAttributeValueInsideDoubleQuotes(
|
|
linkProps.media
|
|
);
|
|
selector += '[media="' + limitedEscapedMedia + '"]';
|
|
}
|
|
|
|
var existingEl = root.querySelector(selector);
|
|
|
|
if (existingEl) {
|
|
instance = resource.instance = existingEl;
|
|
markNodeAsResource(instance);
|
|
return instance;
|
|
}
|
|
|
|
instance = resource.instance = createResourceInstance(
|
|
type,
|
|
props,
|
|
root
|
|
);
|
|
insertResourceInstanceBefore(root, instance, null);
|
|
return instance;
|
|
}
|
|
|
|
case "base": {
|
|
var baseResource = resource;
|
|
var _matcher2 = baseResource.matcher;
|
|
var base = root.querySelector(_matcher2);
|
|
|
|
if (base) {
|
|
instance = resource.instance = base;
|
|
markNodeAsResource(instance);
|
|
} else {
|
|
instance = resource.instance = createResourceInstance(
|
|
type,
|
|
props,
|
|
root
|
|
);
|
|
insertResourceInstanceBefore(
|
|
root,
|
|
instance,
|
|
root.querySelector("base")
|
|
);
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
default: {
|
|
throw new Error(
|
|
'acquireHeadResource encountered a resource type it did not expect: "' +
|
|
type +
|
|
'". This is a bug in React.'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
function acquireStyleResource(resource) {
|
|
var instance = resource.instance;
|
|
|
|
if (!instance) {
|
|
var props = resource.props,
|
|
root = resource.root,
|
|
precedence = resource.precedence;
|
|
var limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(
|
|
props.href
|
|
);
|
|
var existingEl = root.querySelector(
|
|
'link[rel="stylesheet"][data-precedence][href="' +
|
|
limitedEscapedHref +
|
|
'"]'
|
|
);
|
|
|
|
if (existingEl) {
|
|
instance = resource.instance = existingEl;
|
|
markNodeAsResource(instance);
|
|
resource.preloaded = true;
|
|
var loadingState = existingEl._p;
|
|
|
|
if (loadingState) {
|
|
// if an existingEl is found there should always be a loadingState because if
|
|
// the resource was flushed in the head it should have already been found when
|
|
// the resource was first created. Still defensively we gate this
|
|
switch (loadingState.s) {
|
|
case "l": {
|
|
resource.loaded = true;
|
|
resource.error = false;
|
|
break;
|
|
}
|
|
|
|
case "e": {
|
|
resource.error = true;
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
attachLoadListeners(existingEl, resource);
|
|
}
|
|
}
|
|
} else {
|
|
resource.loaded = true;
|
|
}
|
|
} else {
|
|
instance = resource.instance = createResourceInstance(
|
|
"link",
|
|
resource.props,
|
|
getDocumentFromRoot(root)
|
|
);
|
|
attachLoadListeners(instance, resource);
|
|
insertStyleInstance(instance, precedence, root);
|
|
}
|
|
}
|
|
|
|
resource.count++;
|
|
return instance;
|
|
}
|
|
|
|
function acquireScriptResource(resource) {
|
|
var instance = resource.instance;
|
|
|
|
if (!instance) {
|
|
var props = resource.props,
|
|
root = resource.root;
|
|
var limitedEscapedSrc = escapeSelectorAttributeValueInsideDoubleQuotes(
|
|
props.src
|
|
);
|
|
var existingEl = root.querySelector(
|
|
'script[async][src="' + limitedEscapedSrc + '"]'
|
|
);
|
|
|
|
if (existingEl) {
|
|
instance = resource.instance = existingEl;
|
|
markNodeAsResource(instance);
|
|
} else {
|
|
instance = resource.instance = createResourceInstance(
|
|
"script",
|
|
resource.props,
|
|
getDocumentFromRoot(root)
|
|
);
|
|
insertResourceInstanceBefore(getDocumentFromRoot(root), instance, null);
|
|
}
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
function attachLoadListeners(instance, resource) {
|
|
var listeners = {};
|
|
listeners.load = onResourceLoad.bind(
|
|
null,
|
|
instance,
|
|
resource,
|
|
listeners,
|
|
loadAndErrorEventListenerOptions
|
|
);
|
|
listeners.error = onResourceError.bind(
|
|
null,
|
|
instance,
|
|
resource,
|
|
listeners,
|
|
loadAndErrorEventListenerOptions
|
|
);
|
|
instance.addEventListener(
|
|
"load",
|
|
listeners.load,
|
|
loadAndErrorEventListenerOptions
|
|
);
|
|
instance.addEventListener(
|
|
"error",
|
|
listeners.error,
|
|
loadAndErrorEventListenerOptions
|
|
);
|
|
}
|
|
|
|
var loadAndErrorEventListenerOptions = {
|
|
passive: true
|
|
};
|
|
|
|
function onResourceLoad(instance, resource, listeners, listenerOptions) {
|
|
resource.loaded = true;
|
|
resource.error = false;
|
|
|
|
for (var event in listeners) {
|
|
instance.removeEventListener(event, listeners[event], listenerOptions);
|
|
}
|
|
}
|
|
|
|
function onResourceError(instance, resource, listeners, listenerOptions) {
|
|
resource.loaded = false;
|
|
resource.error = true;
|
|
|
|
for (var event in listeners) {
|
|
instance.removeEventListener(event, listeners[event], listenerOptions);
|
|
}
|
|
}
|
|
|
|
function insertStyleInstance(instance, precedence, root) {
|
|
var nodes = root.querySelectorAll('link[rel="stylesheet"][data-precedence]');
|
|
var last = nodes.length ? nodes[nodes.length - 1] : null;
|
|
var prior = last;
|
|
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
var node = nodes[i];
|
|
var nodePrecedence = node.dataset.precedence;
|
|
|
|
if (nodePrecedence === precedence) {
|
|
prior = node;
|
|
} else if (prior !== last) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (prior) {
|
|
// We get the prior from the document so we know it is in the tree.
|
|
// We also know that links can't be the topmost Node so the parentNode
|
|
// must exist.
|
|
prior.parentNode.insertBefore(instance, prior.nextSibling);
|
|
} else {
|
|
var parent = root.nodeType === DOCUMENT_NODE ? root.head : root;
|
|
|
|
if (parent) {
|
|
parent.insertBefore(instance, parent.firstChild);
|
|
} else {
|
|
throw new Error(
|
|
"While attempting to insert a Resource, React expected the Document to contain" +
|
|
" a head element but it was not found."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertResourceInstanceBefore(ownerDocument, instance, before) {
|
|
{
|
|
if (instance.tagName === "LINK" && instance.rel === "stylesheet") {
|
|
error(
|
|
"insertResourceInstanceBefore was called with a stylesheet. Stylesheets must be" +
|
|
" inserted with insertStyleInstance instead. This is a bug in React."
|
|
);
|
|
}
|
|
}
|
|
|
|
var parent = (before && before.parentNode) || ownerDocument.head;
|
|
|
|
if (parent) {
|
|
parent.insertBefore(instance, before);
|
|
} else {
|
|
throw new Error(
|
|
"While attempting to insert a Resource, React expected the Document to contain" +
|
|
" a head element but it was not found."
|
|
);
|
|
}
|
|
} // When passing user input into querySelector(All) the embedded string must not alter
|
|
// the semantics of the query. This escape function is safe to use when we know the
|
|
// provided value is going to be wrapped in double quotes as part of an attribute selector
|
|
// Do not use it anywhere else
|
|
// we escape double quotes and backslashes
|
|
|
|
var escapeSelectorAttributeValueInsideDoubleQuotesRegex = /[\n\"\\]/g;
|
|
|
|
function escapeSelectorAttributeValueInsideDoubleQuotes(value) {
|
|
return value.replace(
|
|
escapeSelectorAttributeValueInsideDoubleQuotesRegex,
|
|
function(ch) {
|
|
return "\\" + ch.charCodeAt(0).toString(16);
|
|
}
|
|
);
|
|
}
|
|
|
|
var SUPPRESS_HYDRATION_WARNING$1 = "suppressHydrationWarning";
|
|
var SUSPENSE_START_DATA = "$";
|
|
var SUSPENSE_END_DATA = "/$";
|
|
var SUSPENSE_PENDING_START_DATA = "$?";
|
|
var SUSPENSE_FALLBACK_START_DATA = "$!";
|
|
var STYLE$1 = "style";
|
|
var eventsEnabled = null;
|
|
var selectionInformation = null;
|
|
function getRootHostContext(rootContainerInstance) {
|
|
var type;
|
|
var namespace;
|
|
var nodeType = rootContainerInstance.nodeType;
|
|
|
|
switch (nodeType) {
|
|
case DOCUMENT_NODE:
|
|
case DOCUMENT_FRAGMENT_NODE: {
|
|
type = nodeType === DOCUMENT_NODE ? "#document" : "#fragment";
|
|
var root = rootContainerInstance.documentElement;
|
|
namespace = root ? root.namespaceURI : getChildNamespace(null, "");
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
var container =
|
|
nodeType === COMMENT_NODE
|
|
? rootContainerInstance.parentNode
|
|
: rootContainerInstance;
|
|
var ownNamespace = container.namespaceURI || null;
|
|
type = container.tagName;
|
|
namespace = getChildNamespace(ownNamespace, type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
var validatedTag = type.toLowerCase();
|
|
var ancestorInfo = updatedAncestorInfoDev(null, validatedTag);
|
|
return {
|
|
namespace: namespace,
|
|
ancestorInfo: ancestorInfo
|
|
};
|
|
}
|
|
}
|
|
function getChildHostContext(parentHostContext, type) {
|
|
{
|
|
var parentHostContextDev = parentHostContext;
|
|
var namespace = getChildNamespace(parentHostContextDev.namespace, type);
|
|
var ancestorInfo = updatedAncestorInfoDev(
|
|
parentHostContextDev.ancestorInfo,
|
|
type
|
|
);
|
|
return {
|
|
namespace: namespace,
|
|
ancestorInfo: ancestorInfo
|
|
};
|
|
}
|
|
}
|
|
function getPublicInstance(instance) {
|
|
return instance;
|
|
}
|
|
function prepareForCommit(containerInfo) {
|
|
eventsEnabled = isEnabled();
|
|
selectionInformation = getSelectionInformation();
|
|
var activeInstance = null;
|
|
|
|
{
|
|
var focusedElem = selectionInformation.focusedElem;
|
|
|
|
if (focusedElem !== null) {
|
|
activeInstance = getClosestInstanceFromNode(focusedElem);
|
|
}
|
|
}
|
|
|
|
setEnabled(false);
|
|
return activeInstance;
|
|
}
|
|
function beforeActiveInstanceBlur(internalInstanceHandle) {
|
|
{
|
|
setEnabled(true);
|
|
dispatchBeforeDetachedBlur(
|
|
selectionInformation.focusedElem,
|
|
internalInstanceHandle
|
|
);
|
|
setEnabled(false);
|
|
}
|
|
}
|
|
function afterActiveInstanceBlur() {
|
|
{
|
|
setEnabled(true);
|
|
dispatchAfterDetachedBlur(selectionInformation.focusedElem);
|
|
setEnabled(false);
|
|
}
|
|
}
|
|
function resetAfterCommit(containerInfo) {
|
|
restoreSelection(selectionInformation);
|
|
setEnabled(eventsEnabled);
|
|
eventsEnabled = null;
|
|
selectionInformation = null;
|
|
}
|
|
function createInstance(
|
|
type,
|
|
props,
|
|
rootContainerInstance,
|
|
hostContext,
|
|
internalInstanceHandle
|
|
) {
|
|
var parentNamespace;
|
|
|
|
{
|
|
// TODO: take namespace into account when validating.
|
|
var hostContextDev = hostContext;
|
|
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
|
|
|
|
if (
|
|
typeof props.children === "string" ||
|
|
typeof props.children === "number"
|
|
) {
|
|
var string = "" + props.children;
|
|
var ownAncestorInfo = updatedAncestorInfoDev(
|
|
hostContextDev.ancestorInfo,
|
|
type
|
|
);
|
|
validateDOMNesting(null, string, ownAncestorInfo);
|
|
}
|
|
|
|
parentNamespace = hostContextDev.namespace;
|
|
}
|
|
|
|
var domElement = createElement(
|
|
type,
|
|
props,
|
|
rootContainerInstance,
|
|
parentNamespace
|
|
);
|
|
precacheFiberNode(internalInstanceHandle, domElement);
|
|
updateFiberProps(domElement, props);
|
|
return domElement;
|
|
}
|
|
function appendInitialChild(parentInstance, child) {
|
|
parentInstance.appendChild(child);
|
|
}
|
|
function finalizeInitialChildren(domElement, type, props, hostContext) {
|
|
setInitialProperties(domElement, type, props);
|
|
|
|
switch (type) {
|
|
case "button":
|
|
case "input":
|
|
case "select":
|
|
case "textarea":
|
|
return !!props.autoFocus;
|
|
|
|
case "img":
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
function prepareUpdate(domElement, type, oldProps, newProps, hostContext) {
|
|
{
|
|
var hostContextDev = hostContext;
|
|
|
|
if (
|
|
typeof newProps.children !== typeof oldProps.children &&
|
|
(typeof newProps.children === "string" ||
|
|
typeof newProps.children === "number")
|
|
) {
|
|
var string = "" + newProps.children;
|
|
var ownAncestorInfo = updatedAncestorInfoDev(
|
|
hostContextDev.ancestorInfo,
|
|
type
|
|
);
|
|
validateDOMNesting(null, string, ownAncestorInfo);
|
|
}
|
|
}
|
|
|
|
return diffProperties(domElement, type, oldProps, newProps);
|
|
}
|
|
function shouldSetTextContent(type, props) {
|
|
return (
|
|
type === "textarea" ||
|
|
type === "noscript" ||
|
|
typeof props.children === "string" ||
|
|
typeof props.children === "number" ||
|
|
(typeof props.dangerouslySetInnerHTML === "object" &&
|
|
props.dangerouslySetInnerHTML !== null &&
|
|
props.dangerouslySetInnerHTML.__html != null)
|
|
);
|
|
}
|
|
function createTextInstance(
|
|
text,
|
|
rootContainerInstance,
|
|
hostContext,
|
|
internalInstanceHandle
|
|
) {
|
|
{
|
|
var hostContextDev = hostContext;
|
|
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
|
|
}
|
|
|
|
var textNode = createTextNode(text, rootContainerInstance);
|
|
precacheFiberNode(internalInstanceHandle, textNode);
|
|
return textNode;
|
|
}
|
|
function getCurrentEventPriority() {
|
|
var currentEvent = window.event;
|
|
|
|
if (currentEvent === undefined) {
|
|
return DefaultEventPriority;
|
|
}
|
|
|
|
return getEventPriority(currentEvent.type);
|
|
}
|
|
// if a component just imports ReactDOM (e.g. for findDOMNode).
|
|
// Some environments might not have setTimeout or clearTimeout.
|
|
|
|
var scheduleTimeout = typeof setTimeout === "function" ? setTimeout : undefined;
|
|
var cancelTimeout =
|
|
typeof clearTimeout === "function" ? clearTimeout : undefined;
|
|
var noTimeout = -1;
|
|
var localPromise = typeof Promise === "function" ? Promise : undefined;
|
|
function getInstanceFromNode(node) {
|
|
return getClosestInstanceFromNode(node) || null;
|
|
}
|
|
function preparePortalMount(portalInstance) {
|
|
listenToAllSupportedEvents(portalInstance);
|
|
}
|
|
function prepareScopeUpdate(scopeInstance, internalInstanceHandle) {
|
|
{
|
|
precacheFiberNode(internalInstanceHandle, scopeInstance);
|
|
}
|
|
}
|
|
function getInstanceFromScope(scopeInstance) {
|
|
{
|
|
return getFiberFromScopeInstance(scopeInstance);
|
|
}
|
|
} // -------------------
|
|
var scheduleMicrotask =
|
|
typeof queueMicrotask === "function"
|
|
? queueMicrotask
|
|
: typeof localPromise !== "undefined"
|
|
? function(callback) {
|
|
return localPromise
|
|
.resolve(null)
|
|
.then(callback)
|
|
.catch(handleErrorInNextTick);
|
|
}
|
|
: scheduleTimeout; // TODO: Determine the best fallback here.
|
|
|
|
function handleErrorInNextTick(error) {
|
|
setTimeout(function() {
|
|
throw error;
|
|
});
|
|
} // -------------------
|
|
function commitMount(domElement, type, newProps, internalInstanceHandle) {
|
|
// Despite the naming that might imply otherwise, this method only
|
|
// fires if there is an `Update` effect scheduled during mounting.
|
|
// This happens if `finalizeInitialChildren` returns `true` (which it
|
|
// does to implement the `autoFocus` attribute on the client). But
|
|
// there are also other cases when this might happen (such as patching
|
|
// up text content during hydration mismatch). So we'll check this again.
|
|
switch (type) {
|
|
case "button":
|
|
case "input":
|
|
case "select":
|
|
case "textarea":
|
|
if (newProps.autoFocus) {
|
|
domElement.focus();
|
|
}
|
|
|
|
return;
|
|
|
|
case "img": {
|
|
if (newProps.src) {
|
|
domElement.src = newProps.src;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
function commitUpdate(
|
|
domElement,
|
|
updatePayload,
|
|
type,
|
|
oldProps,
|
|
newProps,
|
|
internalInstanceHandle
|
|
) {
|
|
// Apply the diff to the DOM node.
|
|
updateProperties(domElement, updatePayload, type, oldProps, newProps); // Update the props handle so that we know which props are the ones with
|
|
// with current event handlers.
|
|
|
|
updateFiberProps(domElement, newProps);
|
|
}
|
|
function resetTextContent(domElement) {
|
|
setTextContent(domElement, "");
|
|
}
|
|
function commitTextUpdate(textInstance, oldText, newText) {
|
|
textInstance.nodeValue = newText;
|
|
}
|
|
function appendChild(parentInstance, child) {
|
|
parentInstance.appendChild(child);
|
|
}
|
|
function appendChildToContainer(container, child) {
|
|
var parentNode;
|
|
|
|
if (container.nodeType === COMMENT_NODE) {
|
|
parentNode = container.parentNode;
|
|
parentNode.insertBefore(child, container);
|
|
} else {
|
|
parentNode = container;
|
|
parentNode.appendChild(child);
|
|
} // This container might be used for a portal.
|
|
// If something inside a portal is clicked, that click should bubble
|
|
// through the React tree. However, on Mobile Safari the click would
|
|
// never bubble through the *DOM* tree unless an ancestor with onclick
|
|
// event exists. So we wouldn't see it and dispatch it.
|
|
// This is why we ensure that non React root containers have inline onclick
|
|
// defined.
|
|
// https://github.com/facebook/react/issues/11918
|
|
|
|
var reactRootContainer = container._reactRootContainer;
|
|
|
|
if (
|
|
(reactRootContainer === null || reactRootContainer === undefined) &&
|
|
parentNode.onclick === null
|
|
) {
|
|
// TODO: This cast may not be sound for SVG, MathML or custom elements.
|
|
trapClickOnNonInteractiveElement(parentNode);
|
|
}
|
|
}
|
|
function insertBefore(parentInstance, child, beforeChild) {
|
|
parentInstance.insertBefore(child, beforeChild);
|
|
}
|
|
function insertInContainerBefore(container, child, beforeChild) {
|
|
if (container.nodeType === COMMENT_NODE) {
|
|
container.parentNode.insertBefore(child, beforeChild);
|
|
} else {
|
|
container.insertBefore(child, beforeChild);
|
|
}
|
|
}
|
|
|
|
function createEvent(type, bubbles) {
|
|
var event = document.createEvent("Event");
|
|
event.initEvent(type, bubbles, false);
|
|
return event;
|
|
}
|
|
|
|
function dispatchBeforeDetachedBlur(target, internalInstanceHandle) {
|
|
{
|
|
var event = createEvent("beforeblur", true); // Dispatch "beforeblur" directly on the target,
|
|
// so it gets picked up by the event system and
|
|
// can propagate through the React internal tree.
|
|
// $FlowFixMe: internal field
|
|
|
|
event._detachedInterceptFiber = internalInstanceHandle;
|
|
target.dispatchEvent(event);
|
|
}
|
|
}
|
|
|
|
function dispatchAfterDetachedBlur(target) {
|
|
{
|
|
var event = createEvent("afterblur", false); // So we know what was detached, make the relatedTarget the
|
|
// detached target on the "afterblur" event.
|
|
|
|
event.relatedTarget = target; // Dispatch the event on the document.
|
|
|
|
document.dispatchEvent(event);
|
|
}
|
|
}
|
|
|
|
function removeChild(parentInstance, child) {
|
|
parentInstance.removeChild(child);
|
|
}
|
|
function removeChildFromContainer(container, child) {
|
|
if (container.nodeType === COMMENT_NODE) {
|
|
container.parentNode.removeChild(child);
|
|
} else {
|
|
container.removeChild(child);
|
|
}
|
|
}
|
|
function clearSuspenseBoundary(parentInstance, suspenseInstance) {
|
|
var node = suspenseInstance; // Delete all nodes within this suspense boundary.
|
|
// There might be nested nodes so we need to keep track of how
|
|
// deep we are and only break out when we're back on top.
|
|
|
|
var depth = 0;
|
|
|
|
do {
|
|
var nextNode = node.nextSibling;
|
|
parentInstance.removeChild(node);
|
|
|
|
if (nextNode && nextNode.nodeType === COMMENT_NODE) {
|
|
var data = nextNode.data;
|
|
|
|
if (data === SUSPENSE_END_DATA) {
|
|
if (depth === 0) {
|
|
parentInstance.removeChild(nextNode); // Retry if any event replaying was blocked on this.
|
|
|
|
retryIfBlockedOn(suspenseInstance);
|
|
return;
|
|
} else {
|
|
depth--;
|
|
}
|
|
} else if (
|
|
data === SUSPENSE_START_DATA ||
|
|
data === SUSPENSE_PENDING_START_DATA ||
|
|
data === SUSPENSE_FALLBACK_START_DATA
|
|
) {
|
|
depth++;
|
|
}
|
|
} // $FlowFixMe[incompatible-type] we bail out when we get a null
|
|
|
|
node = nextNode;
|
|
} while (node); // TODO: Warn, we didn't find the end comment boundary.
|
|
// Retry if any event replaying was blocked on this.
|
|
|
|
retryIfBlockedOn(suspenseInstance);
|
|
}
|
|
function clearSuspenseBoundaryFromContainer(container, suspenseInstance) {
|
|
if (container.nodeType === COMMENT_NODE) {
|
|
clearSuspenseBoundary(container.parentNode, suspenseInstance);
|
|
} else if (container.nodeType === ELEMENT_NODE) {
|
|
clearSuspenseBoundary(container, suspenseInstance);
|
|
} // Retry if any event replaying was blocked on this.
|
|
|
|
retryIfBlockedOn(container);
|
|
}
|
|
function hideInstance(instance) {
|
|
// TODO: Does this work for all element types? What about MathML? Should we
|
|
// pass host context to this method?
|
|
instance = instance;
|
|
var style = instance.style; // $FlowFixMe[method-unbinding]
|
|
|
|
if (typeof style.setProperty === "function") {
|
|
style.setProperty("display", "none", "important");
|
|
} else {
|
|
style.display = "none";
|
|
}
|
|
}
|
|
function hideTextInstance(textInstance) {
|
|
textInstance.nodeValue = "";
|
|
}
|
|
function unhideInstance(instance, props) {
|
|
instance = instance;
|
|
var styleProp = props[STYLE$1];
|
|
var display =
|
|
styleProp !== undefined &&
|
|
styleProp !== null &&
|
|
styleProp.hasOwnProperty("display")
|
|
? styleProp.display
|
|
: null;
|
|
instance.style.display = dangerousStyleValue("display", display);
|
|
}
|
|
function unhideTextInstance(textInstance, text) {
|
|
textInstance.nodeValue = text;
|
|
}
|
|
function clearContainer(container) {
|
|
{
|
|
var nodeType = container.nodeType;
|
|
|
|
if (nodeType === DOCUMENT_NODE) {
|
|
clearRootResources(container);
|
|
clearContainerSparingly(container);
|
|
} else if (nodeType === ELEMENT_NODE) {
|
|
switch (container.nodeName) {
|
|
case "HEAD": {
|
|
// If we are clearing document.head as a container we are essentially clearing everything
|
|
// that was hoisted to the head and should forget the instances that will no longer be in the DOM
|
|
clearRootResources(container); // fall through to clear child contents
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case "HTML":
|
|
case "BODY":
|
|
clearContainerSparingly(container);
|
|
return;
|
|
|
|
default: {
|
|
container.textContent = "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function clearContainerSparingly(container) {
|
|
var node;
|
|
var nextNode = container.firstChild;
|
|
|
|
if (nextNode && nextNode.nodeType === DOCUMENT_TYPE_NODE) {
|
|
nextNode = nextNode.nextSibling;
|
|
}
|
|
|
|
while (nextNode) {
|
|
node = nextNode;
|
|
nextNode = nextNode.nextSibling;
|
|
|
|
switch (node.nodeName) {
|
|
case "HTML":
|
|
case "HEAD":
|
|
case "BODY": {
|
|
var element = node;
|
|
clearContainerSparingly(element); // If these singleton instances had previously been rendered with React they
|
|
// may still hold on to references to the previous fiber tree. We detatch them
|
|
// prospectively to reset them to a baseline starting state since we cannot create
|
|
// new instances.
|
|
|
|
detachDeletedInstance(element);
|
|
continue;
|
|
}
|
|
|
|
case "STYLE": {
|
|
continue;
|
|
}
|
|
|
|
case "LINK": {
|
|
if (node.rel.toLowerCase() === "stylesheet") {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
container.removeChild(node);
|
|
}
|
|
|
|
return;
|
|
} // Making this so we can eventually move all of the instance caching to the commit phase.
|
|
// inserted without breaking hydration
|
|
|
|
function isHydratable(type, props) {
|
|
{
|
|
if (type === "link") {
|
|
if (props.rel === "stylesheet" && typeof props.precedence !== "string") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} else if (type === "script") {
|
|
var async = props.async,
|
|
onLoad = props.onLoad,
|
|
onError = props.onError;
|
|
return !(async && (onLoad || onError));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
function canHydrateInstance(instance, type, props) {
|
|
if (
|
|
instance.nodeType !== ELEMENT_NODE ||
|
|
type.toLowerCase() !== instance.nodeName.toLowerCase()
|
|
) {
|
|
return null;
|
|
} // This has now been refined to an element node.
|
|
|
|
return instance;
|
|
}
|
|
function canHydrateTextInstance(instance, text) {
|
|
if (text === "" || instance.nodeType !== TEXT_NODE) {
|
|
// Empty strings are not parsed by HTML so there won't be a correct match here.
|
|
return null;
|
|
} // This has now been refined to a text node.
|
|
|
|
return instance;
|
|
}
|
|
function canHydrateSuspenseInstance(instance) {
|
|
if (instance.nodeType !== COMMENT_NODE) {
|
|
// Empty strings are not parsed by HTML so there won't be a correct match here.
|
|
return null;
|
|
} // This has now been refined to a suspense node.
|
|
|
|
return instance;
|
|
}
|
|
function isSuspenseInstancePending(instance) {
|
|
return instance.data === SUSPENSE_PENDING_START_DATA;
|
|
}
|
|
function isSuspenseInstanceFallback(instance) {
|
|
return instance.data === SUSPENSE_FALLBACK_START_DATA;
|
|
}
|
|
function getSuspenseInstanceFallbackErrorDetails(instance) {
|
|
var dataset = instance.nextSibling && instance.nextSibling.dataset;
|
|
var digest, message, stack;
|
|
|
|
if (dataset) {
|
|
digest = dataset.dgst;
|
|
|
|
{
|
|
message = dataset.msg;
|
|
stack = dataset.stck;
|
|
}
|
|
}
|
|
|
|
{
|
|
return {
|
|
message: message,
|
|
digest: digest,
|
|
stack: stack
|
|
};
|
|
}
|
|
}
|
|
function registerSuspenseInstanceRetry(instance, callback) {
|
|
instance._reactRetry = callback;
|
|
}
|
|
|
|
function getNextHydratable(node) {
|
|
// Skip non-hydratable nodes.
|
|
for (; node != null; node = node.nextSibling) {
|
|
var nodeType = node.nodeType;
|
|
|
|
{
|
|
if (nodeType === ELEMENT_NODE) {
|
|
var element = node;
|
|
|
|
switch (element.tagName) {
|
|
// This is subtle. in SVG scope the title tag is case sensitive. we don't want to skip
|
|
// titles in svg but we do want to skip them outside of svg. there is an edge case where
|
|
// you could do `React.createElement('TITLE', ...)` inside an svg scope but the SSR serializer
|
|
// will still emit lowercase. Practically speaking the only time the DOM will have a non-uppercased
|
|
// title tagName is if it is inside an svg.
|
|
// Other Resource types like META, BASE, LINK, and SCRIPT should be treated as resources even inside
|
|
// svg scope because they are invalid otherwise. We still don't need to handle the lowercase variant
|
|
// because if they are present in the DOM already they would have been hoisted outside the SVG scope
|
|
// as Resources. So while it would be correct to skip a <link> inside <svg> and this algorithm won't
|
|
// skip that link because the tagName will not be uppercased it functionally is irrelevant. If one
|
|
// tries to render incompatible types such as a non-resource stylesheet inside an svg the server will
|
|
// emit that invalid html and hydration will fail. In Dev this will present warnings guiding the
|
|
// developer on how to fix.
|
|
case "TITLE":
|
|
case "META":
|
|
case "BASE":
|
|
case "HTML":
|
|
case "HEAD":
|
|
case "BODY": {
|
|
continue;
|
|
}
|
|
|
|
case "LINK": {
|
|
var linkEl = element; // All links that are server rendered are resources except
|
|
// stylesheets that do not have a precedence
|
|
|
|
if (
|
|
linkEl.rel === "stylesheet" &&
|
|
!linkEl.hasAttribute("data-precedence")
|
|
) {
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
case "STYLE": {
|
|
var styleEl = element;
|
|
|
|
if (styleEl.hasAttribute("data-precedence")) {
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case "SCRIPT": {
|
|
var scriptEl = element;
|
|
|
|
if (scriptEl.hasAttribute("async")) {
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
} else if (nodeType === TEXT_NODE) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nodeType === COMMENT_NODE) {
|
|
var nodeData = node.data;
|
|
|
|
if (
|
|
nodeData === SUSPENSE_START_DATA ||
|
|
nodeData === SUSPENSE_FALLBACK_START_DATA ||
|
|
nodeData === SUSPENSE_PENDING_START_DATA
|
|
) {
|
|
break;
|
|
}
|
|
|
|
if (nodeData === SUSPENSE_END_DATA) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
function getNextHydratableSibling(instance) {
|
|
return getNextHydratable(instance.nextSibling);
|
|
}
|
|
function getFirstHydratableChild(parentInstance) {
|
|
return getNextHydratable(parentInstance.firstChild);
|
|
}
|
|
function getFirstHydratableChildWithinContainer(parentContainer) {
|
|
return getNextHydratable(parentContainer.firstChild);
|
|
}
|
|
function getFirstHydratableChildWithinSuspenseInstance(parentInstance) {
|
|
return getNextHydratable(parentInstance.nextSibling);
|
|
}
|
|
function hydrateInstance(
|
|
instance,
|
|
type,
|
|
props,
|
|
hostContext,
|
|
internalInstanceHandle,
|
|
shouldWarnDev
|
|
) {
|
|
precacheFiberNode(internalInstanceHandle, instance); // TODO: Possibly defer this until the commit phase where all the events
|
|
// get attached.
|
|
|
|
updateFiberProps(instance, props);
|
|
var parentNamespace;
|
|
|
|
{
|
|
var hostContextDev = hostContext;
|
|
parentNamespace = hostContextDev.namespace;
|
|
} // TODO: Temporary hack to check if we're in a concurrent root. We can delete
|
|
// when the legacy root API is removed.
|
|
|
|
var isConcurrentMode =
|
|
(internalInstanceHandle.mode & ConcurrentMode) !== NoMode;
|
|
return diffHydratedProperties(
|
|
instance,
|
|
type,
|
|
props,
|
|
parentNamespace,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
);
|
|
}
|
|
function hydrateTextInstance(
|
|
textInstance,
|
|
text,
|
|
internalInstanceHandle,
|
|
shouldWarnDev
|
|
) {
|
|
precacheFiberNode(internalInstanceHandle, textInstance); // TODO: Temporary hack to check if we're in a concurrent root. We can delete
|
|
// when the legacy root API is removed.
|
|
|
|
var isConcurrentMode =
|
|
(internalInstanceHandle.mode & ConcurrentMode) !== NoMode;
|
|
return diffHydratedText(textInstance, text);
|
|
}
|
|
function hydrateSuspenseInstance(suspenseInstance, internalInstanceHandle) {
|
|
precacheFiberNode(internalInstanceHandle, suspenseInstance);
|
|
}
|
|
function getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance) {
|
|
var node = suspenseInstance.nextSibling; // Skip past all nodes within this suspense boundary.
|
|
// There might be nested nodes so we need to keep track of how
|
|
// deep we are and only break out when we're back on top.
|
|
|
|
var depth = 0;
|
|
|
|
while (node) {
|
|
if (node.nodeType === COMMENT_NODE) {
|
|
var data = node.data;
|
|
|
|
if (data === SUSPENSE_END_DATA) {
|
|
if (depth === 0) {
|
|
return getNextHydratableSibling(node);
|
|
} else {
|
|
depth--;
|
|
}
|
|
} else if (
|
|
data === SUSPENSE_START_DATA ||
|
|
data === SUSPENSE_FALLBACK_START_DATA ||
|
|
data === SUSPENSE_PENDING_START_DATA
|
|
) {
|
|
depth++;
|
|
}
|
|
}
|
|
|
|
node = node.nextSibling;
|
|
} // TODO: Warn, we didn't find the end comment boundary.
|
|
|
|
return null;
|
|
} // Returns the SuspenseInstance if this node is a direct child of a
|
|
// SuspenseInstance. I.e. if its previous sibling is a Comment with
|
|
// SUSPENSE_x_START_DATA. Otherwise, null.
|
|
|
|
function getParentSuspenseInstance(targetInstance) {
|
|
var node = targetInstance.previousSibling; // Skip past all nodes within this suspense boundary.
|
|
// There might be nested nodes so we need to keep track of how
|
|
// deep we are and only break out when we're back on top.
|
|
|
|
var depth = 0;
|
|
|
|
while (node) {
|
|
if (node.nodeType === COMMENT_NODE) {
|
|
var data = node.data;
|
|
|
|
if (
|
|
data === SUSPENSE_START_DATA ||
|
|
data === SUSPENSE_FALLBACK_START_DATA ||
|
|
data === SUSPENSE_PENDING_START_DATA
|
|
) {
|
|
if (depth === 0) {
|
|
return node;
|
|
} else {
|
|
depth--;
|
|
}
|
|
} else if (data === SUSPENSE_END_DATA) {
|
|
depth++;
|
|
}
|
|
}
|
|
|
|
node = node.previousSibling;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
function commitHydratedContainer(container) {
|
|
// Retry if any event replaying was blocked on this.
|
|
retryIfBlockedOn(container);
|
|
}
|
|
function commitHydratedSuspenseInstance(suspenseInstance) {
|
|
// Retry if any event replaying was blocked on this.
|
|
retryIfBlockedOn(suspenseInstance);
|
|
} // @TODO remove this function once float lands and hydrated tail nodes
|
|
function didNotMatchHydratedContainerTextInstance(
|
|
parentContainer,
|
|
textInstance,
|
|
text,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
) {
|
|
checkForUnmatchedText(
|
|
textInstance.nodeValue,
|
|
text,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
);
|
|
}
|
|
function didNotMatchHydratedTextInstance(
|
|
parentType,
|
|
parentProps,
|
|
parentInstance,
|
|
textInstance,
|
|
text,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
) {
|
|
if (parentProps[SUPPRESS_HYDRATION_WARNING$1] !== true) {
|
|
checkForUnmatchedText(
|
|
textInstance.nodeValue,
|
|
text,
|
|
isConcurrentMode,
|
|
shouldWarnDev
|
|
);
|
|
}
|
|
}
|
|
function didNotHydrateInstanceWithinContainer(parentContainer, instance) {
|
|
{
|
|
if (instance.nodeType === ELEMENT_NODE) {
|
|
warnForDeletedHydratableElement(parentContainer, instance);
|
|
} else if (instance.nodeType === COMMENT_NODE);
|
|
else {
|
|
warnForDeletedHydratableText(parentContainer, instance);
|
|
}
|
|
}
|
|
}
|
|
function didNotHydrateInstanceWithinSuspenseInstance(parentInstance, instance) {
|
|
{
|
|
// $FlowFixMe: Only Element or Document can be parent nodes.
|
|
var parentNode = parentInstance.parentNode;
|
|
|
|
if (parentNode !== null) {
|
|
if (instance.nodeType === ELEMENT_NODE) {
|
|
warnForDeletedHydratableElement(parentNode, instance);
|
|
} else if (instance.nodeType === COMMENT_NODE);
|
|
else {
|
|
warnForDeletedHydratableText(parentNode, instance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function didNotHydrateInstance(
|
|
parentType,
|
|
parentProps,
|
|
parentInstance,
|
|
instance,
|
|
isConcurrentMode
|
|
) {
|
|
{
|
|
if (
|
|
isConcurrentMode ||
|
|
parentProps[SUPPRESS_HYDRATION_WARNING$1] !== true
|
|
) {
|
|
if (instance.nodeType === ELEMENT_NODE) {
|
|
warnForDeletedHydratableElement(parentInstance, instance);
|
|
} else if (instance.nodeType === COMMENT_NODE);
|
|
else {
|
|
warnForDeletedHydratableText(parentInstance, instance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function didNotFindHydratableInstanceWithinContainer(
|
|
parentContainer,
|
|
type,
|
|
props
|
|
) {
|
|
{
|
|
warnForInsertedHydratedElement(parentContainer, type);
|
|
}
|
|
}
|
|
function didNotFindHydratableTextInstanceWithinContainer(
|
|
parentContainer,
|
|
text
|
|
) {
|
|
{
|
|
warnForInsertedHydratedText(parentContainer, text);
|
|
}
|
|
}
|
|
function didNotFindHydratableInstanceWithinSuspenseInstance(
|
|
parentInstance,
|
|
type,
|
|
props
|
|
) {
|
|
{
|
|
// $FlowFixMe: Only Element or Document can be parent nodes.
|
|
var parentNode = parentInstance.parentNode;
|
|
if (parentNode !== null) warnForInsertedHydratedElement(parentNode, type);
|
|
}
|
|
}
|
|
function didNotFindHydratableTextInstanceWithinSuspenseInstance(
|
|
parentInstance,
|
|
text
|
|
) {
|
|
{
|
|
// $FlowFixMe: Only Element or Document can be parent nodes.
|
|
var parentNode = parentInstance.parentNode;
|
|
if (parentNode !== null) warnForInsertedHydratedText(parentNode, text);
|
|
}
|
|
}
|
|
function didNotFindHydratableInstance(
|
|
parentType,
|
|
parentProps,
|
|
parentInstance,
|
|
type,
|
|
props,
|
|
isConcurrentMode
|
|
) {
|
|
{
|
|
if (
|
|
isConcurrentMode ||
|
|
parentProps[SUPPRESS_HYDRATION_WARNING$1] !== true
|
|
) {
|
|
warnForInsertedHydratedElement(parentInstance, type);
|
|
}
|
|
}
|
|
}
|
|
function didNotFindHydratableTextInstance(
|
|
parentType,
|
|
parentProps,
|
|
parentInstance,
|
|
text,
|
|
isConcurrentMode
|
|
) {
|
|
{
|
|
if (
|
|
isConcurrentMode ||
|
|
parentProps[SUPPRESS_HYDRATION_WARNING$1] !== true
|
|
) {
|
|
warnForInsertedHydratedText(parentInstance, text);
|
|
}
|
|
}
|
|
}
|
|
function errorHydratingContainer(parentContainer) {
|
|
{
|
|
// TODO: This gets logged by onRecoverableError, too, so we should be
|
|
// able to remove it.
|
|
error(
|
|
"An error occurred during hydration. The server HTML was replaced with client content in <%s>.",
|
|
parentContainer.nodeName.toLowerCase()
|
|
);
|
|
}
|
|
} // -------------------
|
|
function findFiberRoot(node) {
|
|
var stack = [node];
|
|
var index = 0;
|
|
|
|
while (index < stack.length) {
|
|
var current = stack[index++];
|
|
|
|
if (isContainerMarkedAsRoot(current)) {
|
|
return getInstanceFromNode$1(current);
|
|
}
|
|
|
|
stack.push.apply(stack, current.children);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
function getBoundingRect(node) {
|
|
var rect = node.getBoundingClientRect();
|
|
return {
|
|
x: rect.left,
|
|
y: rect.top,
|
|
width: rect.width,
|
|
height: rect.height
|
|
};
|
|
}
|
|
function matchAccessibilityRole(node, role) {
|
|
if (hasRole(node, role)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
function getTextContent(fiber) {
|
|
switch (fiber.tag) {
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
var textContent = "";
|
|
var childNodes = fiber.stateNode.childNodes;
|
|
|
|
for (var i = 0; i < childNodes.length; i++) {
|
|
var childNode = childNodes[i];
|
|
|
|
if (childNode.nodeType === Node.TEXT_NODE) {
|
|
textContent += childNode.textContent;
|
|
}
|
|
}
|
|
|
|
return textContent;
|
|
|
|
case HostText:
|
|
return fiber.stateNode.textContent;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
function isHiddenSubtree(fiber) {
|
|
return fiber.tag === HostComponent && fiber.memoizedProps.hidden === true;
|
|
}
|
|
function setFocusIfFocusable(node) {
|
|
// The logic for determining if an element is focusable is kind of complex,
|
|
// and since we want to actually change focus anyway- we can just skip it.
|
|
// Instead we'll just listen for a "focus" event to verify that focus was set.
|
|
//
|
|
// We could compare the node to document.activeElement after focus,
|
|
// but this would not handle the case where application code managed focus to automatically blur.
|
|
var didFocus = false;
|
|
|
|
var handleFocus = function() {
|
|
didFocus = true;
|
|
};
|
|
|
|
var element = node;
|
|
|
|
try {
|
|
element.addEventListener("focus", handleFocus); // $FlowFixMe[method-unbinding]
|
|
|
|
(element.focus || HTMLElement.prototype.focus).call(element);
|
|
} finally {
|
|
element.removeEventListener("focus", handleFocus);
|
|
}
|
|
|
|
return didFocus;
|
|
}
|
|
function setupIntersectionObserver(targets, callback, options) {
|
|
var rectRatioCache = new Map();
|
|
targets.forEach(function(target) {
|
|
rectRatioCache.set(target, {
|
|
rect: getBoundingRect(target),
|
|
ratio: 0
|
|
});
|
|
});
|
|
|
|
var handleIntersection = function(entries) {
|
|
entries.forEach(function(entry) {
|
|
var boundingClientRect = entry.boundingClientRect,
|
|
intersectionRatio = entry.intersectionRatio,
|
|
target = entry.target;
|
|
rectRatioCache.set(target, {
|
|
rect: {
|
|
x: boundingClientRect.left,
|
|
y: boundingClientRect.top,
|
|
width: boundingClientRect.width,
|
|
height: boundingClientRect.height
|
|
},
|
|
ratio: intersectionRatio
|
|
});
|
|
});
|
|
callback(Array.from(rectRatioCache.values()));
|
|
};
|
|
|
|
var observer = new IntersectionObserver(handleIntersection, options);
|
|
targets.forEach(function(target) {
|
|
observer.observe(target);
|
|
});
|
|
return {
|
|
disconnect: function() {
|
|
return observer.disconnect();
|
|
},
|
|
observe: function(target) {
|
|
rectRatioCache.set(target, {
|
|
rect: getBoundingRect(target),
|
|
ratio: 0
|
|
});
|
|
observer.observe(target);
|
|
},
|
|
unobserve: function(target) {
|
|
rectRatioCache.delete(target);
|
|
observer.unobserve(target);
|
|
}
|
|
};
|
|
}
|
|
function isHostResourceType(type, props, hostContext) {
|
|
var outsideHostContainerContext;
|
|
var namespace;
|
|
|
|
{
|
|
var hostContextDev = hostContext; // We can only render resources when we are not within the host container context
|
|
|
|
outsideHostContainerContext = !hostContextDev.ancestorInfo
|
|
.containerTagInScope;
|
|
namespace = hostContextDev.namespace;
|
|
}
|
|
|
|
switch (type) {
|
|
case "base":
|
|
case "meta": {
|
|
return true;
|
|
}
|
|
|
|
case "title": {
|
|
return namespace !== SVG_NAMESPACE;
|
|
}
|
|
|
|
case "link": {
|
|
var onLoad = props.onLoad,
|
|
onError = props.onError;
|
|
|
|
if (onLoad || onError) {
|
|
{
|
|
if (outsideHostContainerContext) {
|
|
error(
|
|
"Cannot render a <link> with onLoad or onError listeners outside the main document." +
|
|
" Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or" +
|
|
" somewhere in the <body>."
|
|
);
|
|
} else if (namespace === SVG_NAMESPACE) {
|
|
error(
|
|
"Cannot render a <link> with onLoad or onError listeners as a descendent of <svg>." +
|
|
" Try removing onLoad={...} and onError={...} or moving it above the <svg> ancestor."
|
|
);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
switch (props.rel) {
|
|
case "stylesheet": {
|
|
var href = props.href,
|
|
precedence = props.precedence,
|
|
disabled = props.disabled;
|
|
|
|
{
|
|
validateLinkPropsForStyleResource(props);
|
|
|
|
if (typeof precedence !== "string") {
|
|
if (outsideHostContainerContext) {
|
|
error(
|
|
'Cannot render a <link rel="stylesheet" /> outside the main document without knowing its precedence.' +
|
|
' Consider adding precedence="default" or moving it into the root <head> tag.'
|
|
);
|
|
} else if (namespace === SVG_NAMESPACE) {
|
|
error(
|
|
'Cannot render a <link rel="stylesheet" /> as a descendent of an <svg> element without knowing its precedence.' +
|
|
' Consider adding precedence="default" or moving it above the <svg> ancestor.'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
typeof href === "string" &&
|
|
typeof precedence === "string" &&
|
|
disabled == null
|
|
);
|
|
}
|
|
|
|
default: {
|
|
var rel = props.rel,
|
|
_href = props.href;
|
|
return typeof _href === "string" && typeof rel === "string";
|
|
}
|
|
}
|
|
}
|
|
|
|
case "script": {
|
|
// We don't validate because it is valid to use async with onLoad/onError unlike combining
|
|
// precedence with these for style resources
|
|
var src = props.src,
|
|
async = props.async,
|
|
_onLoad = props.onLoad,
|
|
_onError = props.onError;
|
|
|
|
{
|
|
if (async !== true) {
|
|
if (outsideHostContainerContext) {
|
|
error(
|
|
"Cannot render a sync or defer <script> outside the main document without knowing its order." +
|
|
' Try adding async="" or moving it into the root <head> tag.'
|
|
);
|
|
} else if (namespace === SVG_NAMESPACE) {
|
|
error(
|
|
"Cannot render a sync or defer <script> as a descendent of an <svg> element." +
|
|
' Try adding async="" or moving it above the ancestor <svg> element.'
|
|
);
|
|
}
|
|
} else if (_onLoad || _onError) {
|
|
if (outsideHostContainerContext) {
|
|
error(
|
|
"Cannot render a <script> with onLoad or onError listeners outside the main document." +
|
|
" Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or" +
|
|
" somewhere in the <body>."
|
|
);
|
|
} else if (namespace === SVG_NAMESPACE) {
|
|
error(
|
|
"Cannot render a <script> with onLoad or onError listeners as a descendent of an <svg> element." +
|
|
" Try removing onLoad={...} and onError={...} or moving it above the ancestor <svg> element."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return async && typeof src === "string" && !_onLoad && !_onError;
|
|
}
|
|
|
|
case "noscript":
|
|
case "template":
|
|
case "style": {
|
|
{
|
|
if (outsideHostContainerContext) {
|
|
error(
|
|
"Cannot render <%s> outside the main document. Try moving it into the root <head> tag.",
|
|
type
|
|
);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
function prepareRendererToRender(rootContainer) {
|
|
{
|
|
prepareToRenderResources(rootContainer);
|
|
}
|
|
}
|
|
function resetRendererAfterRender() {
|
|
{
|
|
cleanupAfterRenderResources();
|
|
}
|
|
}
|
|
function isHostSingletonType(type) {
|
|
return type === "html" || type === "head" || type === "body";
|
|
}
|
|
function resolveSingletonInstance(
|
|
type,
|
|
props,
|
|
rootContainerInstance,
|
|
hostContext,
|
|
validateDOMNestingDev
|
|
) {
|
|
{
|
|
if (validateDOMNestingDev) {
|
|
var hostContextDev = hostContext;
|
|
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
|
|
}
|
|
}
|
|
|
|
var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerInstance);
|
|
|
|
switch (type) {
|
|
case "html": {
|
|
var documentElement = ownerDocument.documentElement;
|
|
|
|
if (!documentElement) {
|
|
throw new Error(
|
|
"React expected an <html> element (document.documentElement) to exist in the Document but one was" +
|
|
" not found. React never removes the documentElement for any Document it renders into so" +
|
|
" the cause is likely in some other script running on this page."
|
|
);
|
|
}
|
|
|
|
return documentElement;
|
|
}
|
|
|
|
case "head": {
|
|
var head = ownerDocument.head;
|
|
|
|
if (!head) {
|
|
throw new Error(
|
|
"React expected a <head> element (document.head) to exist in the Document but one was" +
|
|
" not found. React never removes the head for any Document it renders into so" +
|
|
" the cause is likely in some other script running on this page."
|
|
);
|
|
}
|
|
|
|
return head;
|
|
}
|
|
|
|
case "body": {
|
|
var body = ownerDocument.body;
|
|
|
|
if (!body) {
|
|
throw new Error(
|
|
"React expected a <body> element (document.body) to exist in the Document but one was" +
|
|
" not found. React never removes the body for any Document it renders into so" +
|
|
" the cause is likely in some other script running on this page."
|
|
);
|
|
}
|
|
|
|
return body;
|
|
}
|
|
|
|
default: {
|
|
throw new Error(
|
|
"resolveSingletonInstance was called with an element type that is not supported. This is a bug in React."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
function acquireSingletonInstance(
|
|
type,
|
|
props,
|
|
instance,
|
|
internalInstanceHandle
|
|
) {
|
|
{
|
|
var currentInstanceHandle = getInstanceFromNode$1(instance);
|
|
|
|
if (currentInstanceHandle) {
|
|
var tagName = instance.tagName.toLowerCase();
|
|
|
|
error(
|
|
"You are mounting a new %s component when a previous one has not first unmounted. It is an" +
|
|
" error to render more than one %s component at a time and attributes and children of these" +
|
|
" components will likely fail in unpredictable ways. Please only render a single instance of" +
|
|
" <%s> and if you need to mount a new one, ensure any previous ones have unmounted first.",
|
|
tagName,
|
|
tagName,
|
|
tagName
|
|
);
|
|
}
|
|
|
|
switch (type) {
|
|
case "html":
|
|
case "head":
|
|
case "body": {
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
error(
|
|
"acquireSingletonInstance was called with an element type that is not supported. This is a bug in React."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
var attributes = instance.attributes;
|
|
|
|
while (attributes.length) {
|
|
instance.removeAttributeNode(attributes[0]);
|
|
}
|
|
|
|
setInitialProperties(instance, type, props);
|
|
precacheFiberNode(internalInstanceHandle, instance);
|
|
updateFiberProps(instance, props);
|
|
}
|
|
function releaseSingletonInstance(instance) {
|
|
var attributes = instance.attributes;
|
|
|
|
while (attributes.length) {
|
|
instance.removeAttributeNode(attributes[0]);
|
|
}
|
|
|
|
detachDeletedInstance(instance);
|
|
}
|
|
function clearSingleton(instance) {
|
|
var element = instance;
|
|
var node = element.firstChild;
|
|
|
|
while (node) {
|
|
var nextNode = node.nextSibling;
|
|
var nodeName = node.nodeName;
|
|
|
|
if (
|
|
isMarkedResource(node) ||
|
|
nodeName === "HEAD" ||
|
|
nodeName === "BODY" ||
|
|
nodeName === "STYLE" ||
|
|
(nodeName === "LINK" && node.rel.toLowerCase() === "stylesheet")
|
|
);
|
|
else {
|
|
element.removeChild(node);
|
|
}
|
|
|
|
node = nextNode;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var randomKey = Math.random()
|
|
.toString(36)
|
|
.slice(2);
|
|
var internalInstanceKey = "__reactFiber$" + randomKey;
|
|
var internalPropsKey = "__reactProps$" + randomKey;
|
|
var internalContainerInstanceKey = "__reactContainer$" + randomKey;
|
|
var internalEventHandlersKey = "__reactEvents$" + randomKey;
|
|
var internalEventHandlerListenersKey = "__reactListeners$" + randomKey;
|
|
var internalEventHandlesSetKey = "__reactHandles$" + randomKey;
|
|
var internalRootNodeResourcesKey = "__reactResources$" + randomKey;
|
|
var internalResourceMarker = "__reactMarker$" + randomKey;
|
|
function detachDeletedInstance(node) {
|
|
// TODO: This function is only called on host components. I don't think all of
|
|
// these fields are relevant.
|
|
delete node[internalInstanceKey];
|
|
delete node[internalPropsKey];
|
|
delete node[internalEventHandlersKey];
|
|
delete node[internalEventHandlerListenersKey];
|
|
delete node[internalEventHandlesSetKey];
|
|
}
|
|
function precacheFiberNode(hostInst, node) {
|
|
node[internalInstanceKey] = hostInst;
|
|
}
|
|
function markContainerAsRoot(hostRoot, node) {
|
|
// $FlowFixMe[prop-missing]
|
|
node[internalContainerInstanceKey] = hostRoot;
|
|
}
|
|
function unmarkContainerAsRoot(node) {
|
|
// $FlowFixMe[prop-missing]
|
|
node[internalContainerInstanceKey] = null;
|
|
}
|
|
function isContainerMarkedAsRoot(node) {
|
|
// $FlowFixMe[prop-missing]
|
|
return !!node[internalContainerInstanceKey];
|
|
} // Given a DOM node, return the closest HostComponent or HostText fiber ancestor.
|
|
// If the target node is part of a hydrated or not yet rendered subtree, then
|
|
// this may also return a SuspenseComponent or HostRoot to indicate that.
|
|
// Conceptually the HostRoot fiber is a child of the Container node. So if you
|
|
// pass the Container node as the targetNode, you will not actually get the
|
|
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
|
|
// The same thing applies to Suspense boundaries.
|
|
|
|
function getClosestInstanceFromNode(targetNode) {
|
|
var targetInst = targetNode[internalInstanceKey];
|
|
|
|
if (targetInst) {
|
|
// Don't return HostRoot or SuspenseComponent here.
|
|
return targetInst;
|
|
} // If the direct event target isn't a React owned DOM node, we need to look
|
|
// to see if one of its parents is a React owned DOM node.
|
|
|
|
var parentNode = targetNode.parentNode;
|
|
|
|
while (parentNode) {
|
|
// We'll check if this is a container root that could include
|
|
// React nodes in the future. We need to check this first because
|
|
// if we're a child of a dehydrated container, we need to first
|
|
// find that inner container before moving on to finding the parent
|
|
// instance. Note that we don't check this field on the targetNode
|
|
// itself because the fibers are conceptually between the container
|
|
// node and the first child. It isn't surrounding the container node.
|
|
// If it's not a container, we check if it's an instance.
|
|
targetInst =
|
|
parentNode[internalContainerInstanceKey] ||
|
|
parentNode[internalInstanceKey];
|
|
|
|
if (targetInst) {
|
|
// Since this wasn't the direct target of the event, we might have
|
|
// stepped past dehydrated DOM nodes to get here. However they could
|
|
// also have been non-React nodes. We need to answer which one.
|
|
// If we the instance doesn't have any children, then there can't be
|
|
// a nested suspense boundary within it. So we can use this as a fast
|
|
// bailout. Most of the time, when people add non-React children to
|
|
// the tree, it is using a ref to a child-less DOM node.
|
|
// Normally we'd only need to check one of the fibers because if it
|
|
// has ever gone from having children to deleting them or vice versa
|
|
// it would have deleted the dehydrated boundary nested inside already.
|
|
// However, since the HostRoot starts out with an alternate it might
|
|
// have one on the alternate so we need to check in case this was a
|
|
// root.
|
|
var alternate = targetInst.alternate;
|
|
|
|
if (
|
|
targetInst.child !== null ||
|
|
(alternate !== null && alternate.child !== null)
|
|
) {
|
|
// Next we need to figure out if the node that skipped past is
|
|
// nested within a dehydrated boundary and if so, which one.
|
|
var suspenseInstance = getParentSuspenseInstance(targetNode);
|
|
|
|
while (suspenseInstance !== null) {
|
|
// We found a suspense instance. That means that we haven't
|
|
// hydrated it yet. Even though we leave the comments in the
|
|
// DOM after hydrating, and there are boundaries in the DOM
|
|
// that could already be hydrated, we wouldn't have found them
|
|
// through this pass since if the target is hydrated it would
|
|
// have had an internalInstanceKey on it.
|
|
// Let's get the fiber associated with the SuspenseComponent
|
|
// as the deepest instance.
|
|
// $FlowFixMe[prop-missing]
|
|
var targetSuspenseInst = suspenseInstance[internalInstanceKey];
|
|
|
|
if (targetSuspenseInst) {
|
|
return targetSuspenseInst;
|
|
} // If we don't find a Fiber on the comment, it might be because
|
|
// we haven't gotten to hydrate it yet. There might still be a
|
|
// parent boundary that hasn't above this one so we need to find
|
|
// the outer most that is known.
|
|
|
|
suspenseInstance = getParentSuspenseInstance(suspenseInstance); // If we don't find one, then that should mean that the parent
|
|
// host component also hasn't hydrated yet. We can return it
|
|
// below since it will bail out on the isMounted check later.
|
|
}
|
|
}
|
|
|
|
return targetInst;
|
|
}
|
|
|
|
targetNode = parentNode;
|
|
parentNode = targetNode.parentNode;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
/**
|
|
* Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
|
|
* instance, or null if the node was not rendered by this React.
|
|
*/
|
|
|
|
function getInstanceFromNode$1(node) {
|
|
var inst = node[internalInstanceKey] || node[internalContainerInstanceKey];
|
|
|
|
if (inst) {
|
|
var tag = inst.tag;
|
|
|
|
if (
|
|
tag === HostComponent ||
|
|
tag === HostText ||
|
|
tag === SuspenseComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton ||
|
|
tag === HostRoot
|
|
) {
|
|
return inst;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
/**
|
|
* Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
|
|
* DOM node.
|
|
*/
|
|
|
|
function getNodeFromInstance(inst) {
|
|
var tag = inst.tag;
|
|
|
|
if (
|
|
tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton ||
|
|
tag === HostText
|
|
) {
|
|
// In Fiber this, is just the state node right now. We assume it will be
|
|
// a host component or host text.
|
|
return inst.stateNode;
|
|
} // Without this first invariant, passing a non-DOM-component triggers the next
|
|
// invariant for a missing parent, which is super confusing.
|
|
|
|
throw new Error("getNodeFromInstance: Invalid argument.");
|
|
}
|
|
function getFiberCurrentPropsFromNode(node) {
|
|
return node[internalPropsKey] || null;
|
|
}
|
|
function updateFiberProps(node, props) {
|
|
node[internalPropsKey] = props;
|
|
}
|
|
function getEventListenerSet(node) {
|
|
var elementListenerSet = node[internalEventHandlersKey];
|
|
|
|
if (elementListenerSet === undefined) {
|
|
elementListenerSet = node[internalEventHandlersKey] = new Set();
|
|
}
|
|
|
|
return elementListenerSet;
|
|
}
|
|
function getFiberFromScopeInstance(scope) {
|
|
{
|
|
return scope[internalInstanceKey] || null;
|
|
}
|
|
}
|
|
function setEventHandlerListeners(scope, listeners) {
|
|
scope[internalEventHandlerListenersKey] = listeners;
|
|
}
|
|
function getEventHandlerListeners(scope) {
|
|
return scope[internalEventHandlerListenersKey] || null;
|
|
}
|
|
function addEventHandleToTarget(target, eventHandle) {
|
|
var eventHandles = target[internalEventHandlesSetKey];
|
|
|
|
if (eventHandles === undefined) {
|
|
eventHandles = target[internalEventHandlesSetKey] = new Set();
|
|
}
|
|
|
|
eventHandles.add(eventHandle);
|
|
}
|
|
function doesTargetHaveEventHandle(target, eventHandle) {
|
|
var eventHandles = target[internalEventHandlesSetKey];
|
|
|
|
if (eventHandles === undefined) {
|
|
return false;
|
|
}
|
|
|
|
return eventHandles.has(eventHandle);
|
|
}
|
|
function getResourcesFromRoot(root) {
|
|
var resources = root[internalRootNodeResourcesKey];
|
|
|
|
if (!resources) {
|
|
resources = root[internalRootNodeResourcesKey] = {
|
|
styles: new Map(),
|
|
scripts: new Map(),
|
|
head: new Map(),
|
|
lastStructuredMeta: new Map()
|
|
};
|
|
}
|
|
|
|
return resources;
|
|
}
|
|
function isMarkedResource(node) {
|
|
return !!node[internalResourceMarker];
|
|
}
|
|
function markNodeAsResource(node) {
|
|
node[internalResourceMarker] = true;
|
|
}
|
|
|
|
var loggedTypeFailures = {};
|
|
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
|
|
|
|
function setCurrentlyValidatingElement(element) {
|
|
{
|
|
if (element) {
|
|
var owner = element._owner;
|
|
var stack = describeUnknownElementTypeFrameInDEV(
|
|
element.type,
|
|
element._source,
|
|
owner ? owner.type : null
|
|
);
|
|
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
|
|
} else {
|
|
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkPropTypes(typeSpecs, values, location, componentName, element) {
|
|
{
|
|
// $FlowFixMe This is okay but Flow doesn't know it.
|
|
var has = Function.call.bind(hasOwnProperty);
|
|
|
|
for (var typeSpecName in typeSpecs) {
|
|
if (has(typeSpecs, typeSpecName)) {
|
|
var error$1 = void 0; // Prop type validation may throw. In case they do, we don't want to
|
|
// fail the render phase where it didn't fail before. So we log it.
|
|
// After these have been cleaned up, we'll let them throw.
|
|
|
|
try {
|
|
// This is intentionally an invariant that gets caught. It's the same
|
|
// behavior as without this statement except with a better message.
|
|
if (typeof typeSpecs[typeSpecName] !== "function") {
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
var err = Error(
|
|
(componentName || "React class") +
|
|
": " +
|
|
location +
|
|
" type `" +
|
|
typeSpecName +
|
|
"` is invalid; " +
|
|
"it must be a function, usually from the `prop-types` package, but received `" +
|
|
typeof typeSpecs[typeSpecName] +
|
|
"`." +
|
|
"This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`."
|
|
);
|
|
err.name = "Invariant Violation";
|
|
throw err;
|
|
}
|
|
|
|
error$1 = typeSpecs[typeSpecName](
|
|
values,
|
|
typeSpecName,
|
|
componentName,
|
|
location,
|
|
null,
|
|
"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"
|
|
);
|
|
} catch (ex) {
|
|
error$1 = ex;
|
|
}
|
|
|
|
if (error$1 && !(error$1 instanceof Error)) {
|
|
setCurrentlyValidatingElement(element);
|
|
|
|
error(
|
|
"%s: type specification of %s" +
|
|
" `%s` is invalid; the type checker " +
|
|
"function must return `null` or an `Error` but returned a %s. " +
|
|
"You may have forgotten to pass an argument to the type checker " +
|
|
"creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and " +
|
|
"shape all require an argument).",
|
|
componentName || "React class",
|
|
location,
|
|
typeSpecName,
|
|
typeof error$1
|
|
);
|
|
|
|
setCurrentlyValidatingElement(null);
|
|
}
|
|
|
|
if (
|
|
error$1 instanceof Error &&
|
|
!(error$1.message in loggedTypeFailures)
|
|
) {
|
|
// Only monitor this failure once because there tends to be a lot of the
|
|
// same error.
|
|
loggedTypeFailures[error$1.message] = true;
|
|
setCurrentlyValidatingElement(element);
|
|
|
|
error("Failed %s type: %s", location, error$1.message);
|
|
|
|
setCurrentlyValidatingElement(null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var emptyContextObject = {};
|
|
|
|
{
|
|
Object.freeze(emptyContextObject);
|
|
} // A cursor to the current merged context object on the stack.
|
|
|
|
function hasContextChanged() {
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isContextProvider(type) {
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function pushTopLevelContextObject(fiber, context, didChange) {
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
function processChildContext(fiber, type, parentContext) {
|
|
{
|
|
return parentContext;
|
|
}
|
|
}
|
|
|
|
function findCurrentUnmaskedContext(fiber) {
|
|
{
|
|
return emptyContextObject;
|
|
}
|
|
}
|
|
|
|
var LegacyRoot = 0;
|
|
var ConcurrentRoot = 1;
|
|
|
|
// We use the existence of the state object as an indicator that the component
|
|
// is hidden.
|
|
var OffscreenVisible =
|
|
/* */
|
|
1;
|
|
var OffscreenDetached =
|
|
/* */
|
|
2;
|
|
var OffscreenPassiveEffectsConnected =
|
|
/* */
|
|
4;
|
|
function isOffscreenManual(offscreenFiber) {
|
|
return (
|
|
offscreenFiber.memoizedProps !== null &&
|
|
offscreenFiber.memoizedProps.mode === "manual"
|
|
);
|
|
}
|
|
|
|
var syncQueue = null;
|
|
var includesLegacySyncCallbacks = false;
|
|
var isFlushingSyncQueue = false;
|
|
function scheduleSyncCallback(callback) {
|
|
// Push this callback into an internal queue. We'll flush these either in
|
|
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
|
|
if (syncQueue === null) {
|
|
syncQueue = [callback];
|
|
} else {
|
|
// Push onto existing queue. Don't need to schedule a callback because
|
|
// we already scheduled one when we created the queue.
|
|
syncQueue.push(callback);
|
|
}
|
|
}
|
|
function scheduleLegacySyncCallback(callback) {
|
|
includesLegacySyncCallbacks = true;
|
|
scheduleSyncCallback(callback);
|
|
}
|
|
function flushSyncCallbacksOnlyInLegacyMode() {
|
|
// Only flushes the queue if there's a legacy sync callback scheduled.
|
|
// TODO: There's only a single type of callback: performSyncOnWorkOnRoot. So
|
|
// it might make more sense for the queue to be a list of roots instead of a
|
|
// list of generic callbacks. Then we can have two: one for legacy roots, one
|
|
// for concurrent roots. And this method would only flush the legacy ones.
|
|
if (includesLegacySyncCallbacks) {
|
|
flushSyncCallbacks();
|
|
}
|
|
}
|
|
function flushSyncCallbacks() {
|
|
if (!isFlushingSyncQueue && syncQueue !== null) {
|
|
// Prevent re-entrance.
|
|
isFlushingSyncQueue = true;
|
|
var i = 0;
|
|
var previousUpdatePriority = getCurrentUpdatePriority();
|
|
|
|
try {
|
|
var isSync = true;
|
|
var queue = syncQueue; // TODO: Is this necessary anymore? The only user code that runs in this
|
|
// queue is in the render or commit phases.
|
|
|
|
setCurrentUpdatePriority(DiscreteEventPriority); // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
for (; i < queue.length; i++) {
|
|
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
var callback = queue[i];
|
|
|
|
do {
|
|
// $FlowFixMe[incompatible-type] we bail out when we get a null
|
|
callback = callback(isSync);
|
|
} while (callback !== null);
|
|
}
|
|
|
|
syncQueue = null;
|
|
includesLegacySyncCallbacks = false;
|
|
} catch (error) {
|
|
// If something throws, leave the remaining callbacks on the queue.
|
|
if (syncQueue !== null) {
|
|
syncQueue = syncQueue.slice(i + 1);
|
|
} // Resume flushing in the next tick
|
|
|
|
scheduleCallback(ImmediatePriority, flushSyncCallbacks);
|
|
throw error;
|
|
} finally {
|
|
setCurrentUpdatePriority(previousUpdatePriority);
|
|
isFlushingSyncQueue = false;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Intentionally not using it yet to derisk the initial implementation, because
|
|
// the way we push/pop these values is a bit unusual. If there's a mistake, I'd
|
|
// rather the ids be wrong than crash the whole reconciler.
|
|
|
|
var forkStack = [];
|
|
var forkStackIndex = 0;
|
|
var treeForkProvider = null;
|
|
var treeForkCount = 0;
|
|
var idStack = [];
|
|
var idStackIndex = 0;
|
|
var treeContextProvider = null;
|
|
var treeContextId = 1;
|
|
var treeContextOverflow = "";
|
|
function isForkedChild(workInProgress) {
|
|
warnIfNotHydrating();
|
|
return (workInProgress.flags & Forked) !== NoFlags;
|
|
}
|
|
function getForksAtLevel(workInProgress) {
|
|
warnIfNotHydrating();
|
|
return treeForkCount;
|
|
}
|
|
function getTreeId() {
|
|
var overflow = treeContextOverflow;
|
|
var idWithLeadingBit = treeContextId;
|
|
var id = idWithLeadingBit & ~getLeadingBit(idWithLeadingBit);
|
|
return id.toString(32) + overflow;
|
|
}
|
|
function pushTreeFork(workInProgress, totalChildren) {
|
|
// This is called right after we reconcile an array (or iterator) of child
|
|
// fibers, because that's the only place where we know how many children in
|
|
// the whole set without doing extra work later, or storing addtional
|
|
// information on the fiber.
|
|
//
|
|
// That's why this function is separate from pushTreeId — it's called during
|
|
// the render phase of the fork parent, not the child, which is where we push
|
|
// the other context values.
|
|
//
|
|
// In the Fizz implementation this is much simpler because the child is
|
|
// rendered in the same callstack as the parent.
|
|
//
|
|
// It might be better to just add a `forks` field to the Fiber type. It would
|
|
// make this module simpler.
|
|
warnIfNotHydrating();
|
|
forkStack[forkStackIndex++] = treeForkCount;
|
|
forkStack[forkStackIndex++] = treeForkProvider;
|
|
treeForkProvider = workInProgress;
|
|
treeForkCount = totalChildren;
|
|
}
|
|
function pushTreeId(workInProgress, totalChildren, index) {
|
|
warnIfNotHydrating();
|
|
idStack[idStackIndex++] = treeContextId;
|
|
idStack[idStackIndex++] = treeContextOverflow;
|
|
idStack[idStackIndex++] = treeContextProvider;
|
|
treeContextProvider = workInProgress;
|
|
var baseIdWithLeadingBit = treeContextId;
|
|
var baseOverflow = treeContextOverflow; // The leftmost 1 marks the end of the sequence, non-inclusive. It's not part
|
|
// of the id; we use it to account for leading 0s.
|
|
|
|
var baseLength = getBitLength(baseIdWithLeadingBit) - 1;
|
|
var baseId = baseIdWithLeadingBit & ~(1 << baseLength);
|
|
var slot = index + 1;
|
|
var length = getBitLength(totalChildren) + baseLength; // 30 is the max length we can store without overflowing, taking into
|
|
// consideration the leading 1 we use to mark the end of the sequence.
|
|
|
|
if (length > 30) {
|
|
// We overflowed the bitwise-safe range. Fall back to slower algorithm.
|
|
// This branch assumes the length of the base id is greater than 5; it won't
|
|
// work for smaller ids, because you need 5 bits per character.
|
|
//
|
|
// We encode the id in multiple steps: first the base id, then the
|
|
// remaining digits.
|
|
//
|
|
// Each 5 bit sequence corresponds to a single base 32 character. So for
|
|
// example, if the current id is 23 bits long, we can convert 20 of those
|
|
// bits into a string of 4 characters, with 3 bits left over.
|
|
//
|
|
// First calculate how many bits in the base id represent a complete
|
|
// sequence of characters.
|
|
var numberOfOverflowBits = baseLength - (baseLength % 5); // Then create a bitmask that selects only those bits.
|
|
|
|
var newOverflowBits = (1 << numberOfOverflowBits) - 1; // Select the bits, and convert them to a base 32 string.
|
|
|
|
var newOverflow = (baseId & newOverflowBits).toString(32); // Now we can remove those bits from the base id.
|
|
|
|
var restOfBaseId = baseId >> numberOfOverflowBits;
|
|
var restOfBaseLength = baseLength - numberOfOverflowBits; // Finally, encode the rest of the bits using the normal algorithm. Because
|
|
// we made more room, this time it won't overflow.
|
|
|
|
var restOfLength = getBitLength(totalChildren) + restOfBaseLength;
|
|
var restOfNewBits = slot << restOfBaseLength;
|
|
var id = restOfNewBits | restOfBaseId;
|
|
var overflow = newOverflow + baseOverflow;
|
|
treeContextId = (1 << restOfLength) | id;
|
|
treeContextOverflow = overflow;
|
|
} else {
|
|
// Normal path
|
|
var newBits = slot << baseLength;
|
|
|
|
var _id = newBits | baseId;
|
|
|
|
var _overflow = baseOverflow;
|
|
treeContextId = (1 << length) | _id;
|
|
treeContextOverflow = _overflow;
|
|
}
|
|
}
|
|
function pushMaterializedTreeId(workInProgress) {
|
|
warnIfNotHydrating(); // This component materialized an id. This will affect any ids that appear
|
|
// in its children.
|
|
|
|
var returnFiber = workInProgress.return;
|
|
|
|
if (returnFiber !== null) {
|
|
var numberOfForks = 1;
|
|
var slotIndex = 0;
|
|
pushTreeFork(workInProgress, numberOfForks);
|
|
pushTreeId(workInProgress, numberOfForks, slotIndex);
|
|
}
|
|
}
|
|
|
|
function getBitLength(number) {
|
|
return 32 - clz32(number);
|
|
}
|
|
|
|
function getLeadingBit(id) {
|
|
return 1 << (getBitLength(id) - 1);
|
|
}
|
|
|
|
function popTreeContext(workInProgress) {
|
|
// Restore the previous values.
|
|
// This is a bit more complicated than other context-like modules in Fiber
|
|
// because the same Fiber may appear on the stack multiple times and for
|
|
// different reasons. We have to keep popping until the work-in-progress is
|
|
// no longer at the top of the stack.
|
|
while (workInProgress === treeForkProvider) {
|
|
treeForkProvider = forkStack[--forkStackIndex];
|
|
forkStack[forkStackIndex] = null;
|
|
treeForkCount = forkStack[--forkStackIndex];
|
|
forkStack[forkStackIndex] = null;
|
|
}
|
|
|
|
while (workInProgress === treeContextProvider) {
|
|
treeContextProvider = idStack[--idStackIndex];
|
|
idStack[idStackIndex] = null;
|
|
treeContextOverflow = idStack[--idStackIndex];
|
|
idStack[idStackIndex] = null;
|
|
treeContextId = idStack[--idStackIndex];
|
|
idStack[idStackIndex] = null;
|
|
}
|
|
}
|
|
function getSuspendedTreeContext() {
|
|
warnIfNotHydrating();
|
|
|
|
if (treeContextProvider !== null) {
|
|
return {
|
|
id: treeContextId,
|
|
overflow: treeContextOverflow
|
|
};
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
function restoreSuspendedTreeContext(workInProgress, suspendedContext) {
|
|
warnIfNotHydrating();
|
|
idStack[idStackIndex++] = treeContextId;
|
|
idStack[idStackIndex++] = treeContextOverflow;
|
|
idStack[idStackIndex++] = treeContextProvider;
|
|
treeContextId = suspendedContext.id;
|
|
treeContextOverflow = suspendedContext.overflow;
|
|
treeContextProvider = workInProgress;
|
|
}
|
|
|
|
function warnIfNotHydrating() {
|
|
{
|
|
if (!getIsHydrating()) {
|
|
error(
|
|
"Expected to be hydrating. This is a bug in React. Please file " +
|
|
"an issue."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This may have been an insertion or a hydration.
|
|
|
|
var hydrationParentFiber = null;
|
|
var nextHydratableInstance = null;
|
|
var isHydrating = false; // This flag allows for warning supression when we expect there to be mismatches
|
|
// due to earlier mismatches or a suspended fiber.
|
|
|
|
var didSuspendOrErrorDEV = false; // Hydration errors that were thrown inside this boundary
|
|
|
|
var hydrationErrors = null;
|
|
|
|
function warnIfHydrating() {
|
|
{
|
|
if (isHydrating) {
|
|
error(
|
|
"We should not be hydrating here. This is a bug in React. Please file a bug."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function markDidThrowWhileHydratingDEV() {
|
|
{
|
|
didSuspendOrErrorDEV = true;
|
|
}
|
|
}
|
|
|
|
function enterHydrationState(fiber) {
|
|
var parentInstance = fiber.stateNode.containerInfo;
|
|
nextHydratableInstance = getFirstHydratableChildWithinContainer(
|
|
parentInstance
|
|
);
|
|
hydrationParentFiber = fiber;
|
|
isHydrating = true;
|
|
hydrationErrors = null;
|
|
didSuspendOrErrorDEV = false;
|
|
return true;
|
|
}
|
|
|
|
function reenterHydrationStateFromDehydratedSuspenseInstance(
|
|
fiber,
|
|
suspenseInstance,
|
|
treeContext
|
|
) {
|
|
nextHydratableInstance = getFirstHydratableChildWithinSuspenseInstance(
|
|
suspenseInstance
|
|
);
|
|
hydrationParentFiber = fiber;
|
|
isHydrating = true;
|
|
hydrationErrors = null;
|
|
didSuspendOrErrorDEV = false;
|
|
|
|
if (treeContext !== null) {
|
|
restoreSuspendedTreeContext(fiber, treeContext);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function warnUnhydratedInstance(returnFiber, instance) {
|
|
{
|
|
switch (returnFiber.tag) {
|
|
case HostRoot: {
|
|
didNotHydrateInstanceWithinContainer(
|
|
returnFiber.stateNode.containerInfo,
|
|
instance
|
|
);
|
|
break;
|
|
}
|
|
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
var isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
|
|
didNotHydrateInstance(
|
|
returnFiber.type,
|
|
returnFiber.memoizedProps,
|
|
returnFiber.stateNode,
|
|
instance, // TODO: Delete this argument when we remove the legacy root API.
|
|
isConcurrentMode
|
|
);
|
|
break;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
var suspenseState = returnFiber.memoizedState;
|
|
if (suspenseState.dehydrated !== null)
|
|
didNotHydrateInstanceWithinSuspenseInstance(
|
|
suspenseState.dehydrated,
|
|
instance
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function deleteHydratableInstance(returnFiber, instance) {
|
|
warnUnhydratedInstance(returnFiber, instance);
|
|
var childToDelete = createFiberFromHostInstanceForDeletion();
|
|
childToDelete.stateNode = instance;
|
|
childToDelete.return = returnFiber;
|
|
var deletions = returnFiber.deletions;
|
|
|
|
if (deletions === null) {
|
|
returnFiber.deletions = [childToDelete];
|
|
returnFiber.flags |= ChildDeletion;
|
|
} else {
|
|
deletions.push(childToDelete);
|
|
}
|
|
}
|
|
|
|
function warnNonhydratedInstance(returnFiber, fiber) {
|
|
{
|
|
if (didSuspendOrErrorDEV) {
|
|
// Inside a boundary that already suspended. We're currently rendering the
|
|
// siblings of a suspended node. The mismatch may be due to the missing
|
|
// data, so it's probably a false positive.
|
|
return;
|
|
}
|
|
|
|
switch (returnFiber.tag) {
|
|
case HostRoot: {
|
|
var parentContainer = returnFiber.stateNode.containerInfo;
|
|
|
|
switch (fiber.tag) {
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
var type = fiber.type;
|
|
var props = fiber.pendingProps;
|
|
didNotFindHydratableInstanceWithinContainer(parentContainer, type);
|
|
break;
|
|
|
|
case HostText:
|
|
var text = fiber.pendingProps;
|
|
didNotFindHydratableTextInstanceWithinContainer(
|
|
parentContainer,
|
|
text
|
|
);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
var parentType = returnFiber.type;
|
|
var parentProps = returnFiber.memoizedProps;
|
|
var parentInstance = returnFiber.stateNode;
|
|
|
|
switch (fiber.tag) {
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
var _type = fiber.type;
|
|
var _props = fiber.pendingProps;
|
|
var isConcurrentMode =
|
|
(returnFiber.mode & ConcurrentMode) !== NoMode;
|
|
didNotFindHydratableInstance(
|
|
parentType,
|
|
parentProps,
|
|
parentInstance,
|
|
_type,
|
|
_props, // TODO: Delete this argument when we remove the legacy root API.
|
|
isConcurrentMode
|
|
);
|
|
break;
|
|
}
|
|
|
|
case HostText: {
|
|
var _text = fiber.pendingProps;
|
|
|
|
var _isConcurrentMode =
|
|
(returnFiber.mode & ConcurrentMode) !== NoMode;
|
|
|
|
didNotFindHydratableTextInstance(
|
|
parentType,
|
|
parentProps,
|
|
parentInstance,
|
|
_text, // TODO: Delete this argument when we remove the legacy root API.
|
|
_isConcurrentMode
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
var suspenseState = returnFiber.memoizedState;
|
|
var _parentInstance = suspenseState.dehydrated;
|
|
if (_parentInstance !== null)
|
|
switch (fiber.tag) {
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
var _type2 = fiber.type;
|
|
var _props2 = fiber.pendingProps;
|
|
didNotFindHydratableInstanceWithinSuspenseInstance(
|
|
_parentInstance,
|
|
_type2
|
|
);
|
|
break;
|
|
|
|
case HostText:
|
|
var _text2 = fiber.pendingProps;
|
|
didNotFindHydratableTextInstanceWithinSuspenseInstance(
|
|
_parentInstance,
|
|
_text2
|
|
);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertNonHydratedInstance(returnFiber, fiber) {
|
|
fiber.flags = (fiber.flags & ~Hydrating) | Placement;
|
|
warnNonhydratedInstance(returnFiber, fiber);
|
|
}
|
|
|
|
function tryHydrate(fiber, nextInstance) {
|
|
switch (fiber.tag) {
|
|
// HostSingleton is intentionally omitted. the hydration pathway for singletons is non-fallible
|
|
// you can find it inlined in claimHydratableSingleton
|
|
case HostComponent: {
|
|
var type = fiber.type;
|
|
var props = fiber.pendingProps;
|
|
var instance = canHydrateInstance(nextInstance, type);
|
|
|
|
if (instance !== null) {
|
|
fiber.stateNode = instance;
|
|
hydrationParentFiber = fiber;
|
|
nextHydratableInstance = getFirstHydratableChild(instance);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
case HostText: {
|
|
var text = fiber.pendingProps;
|
|
var textInstance = canHydrateTextInstance(nextInstance, text);
|
|
|
|
if (textInstance !== null) {
|
|
fiber.stateNode = textInstance;
|
|
hydrationParentFiber = fiber; // Text Instances don't have children so there's nothing to hydrate.
|
|
|
|
nextHydratableInstance = null;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
var suspenseInstance = canHydrateSuspenseInstance(nextInstance);
|
|
|
|
if (suspenseInstance !== null) {
|
|
var suspenseState = {
|
|
dehydrated: suspenseInstance,
|
|
treeContext: getSuspendedTreeContext(),
|
|
retryLane: OffscreenLane
|
|
};
|
|
fiber.memoizedState = suspenseState; // Store the dehydrated fragment as a child fiber.
|
|
// This simplifies the code for getHostSibling and deleting nodes,
|
|
// since it doesn't have to consider all Suspense boundaries and
|
|
// check if they're dehydrated ones or not.
|
|
|
|
var dehydratedFragment = createFiberFromDehydratedFragment(
|
|
suspenseInstance
|
|
);
|
|
dehydratedFragment.return = fiber;
|
|
fiber.child = dehydratedFragment;
|
|
hydrationParentFiber = fiber; // While a Suspense Instance does have children, we won't step into
|
|
// it during the first pass. Instead, we'll reenter it later.
|
|
|
|
nextHydratableInstance = null;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function shouldClientRenderOnMismatch(fiber) {
|
|
return (
|
|
(fiber.mode & ConcurrentMode) !== NoMode &&
|
|
(fiber.flags & DidCapture) === NoFlags
|
|
);
|
|
}
|
|
|
|
function throwOnHydrationMismatch(fiber) {
|
|
throw new Error(
|
|
"Hydration failed because the initial UI does not match what was " +
|
|
"rendered on the server."
|
|
);
|
|
}
|
|
|
|
function claimHydratableSingleton(fiber) {
|
|
{
|
|
if (!isHydrating) {
|
|
return;
|
|
}
|
|
|
|
var currentRootContainer = getRootHostContainer();
|
|
var currentHostContext = getHostContext();
|
|
var instance = (fiber.stateNode = resolveSingletonInstance(
|
|
fiber.type,
|
|
fiber.pendingProps,
|
|
currentRootContainer,
|
|
currentHostContext,
|
|
false
|
|
));
|
|
hydrationParentFiber = fiber;
|
|
nextHydratableInstance = getFirstHydratableChild(instance);
|
|
}
|
|
}
|
|
|
|
function tryToClaimNextHydratableInstance(fiber) {
|
|
if (!isHydrating) {
|
|
return;
|
|
}
|
|
|
|
if (!isHydratable(fiber.type, fiber.pendingProps)) {
|
|
// This fiber never hydrates from the DOM and always does an insert
|
|
fiber.flags = (fiber.flags & ~Hydrating) | Placement;
|
|
isHydrating = false;
|
|
hydrationParentFiber = fiber;
|
|
return;
|
|
}
|
|
|
|
var nextInstance = nextHydratableInstance;
|
|
|
|
if (!nextInstance) {
|
|
if (shouldClientRenderOnMismatch(fiber)) {
|
|
warnNonhydratedInstance(hydrationParentFiber, fiber);
|
|
throwOnHydrationMismatch();
|
|
} // Nothing to hydrate. Make it an insertion.
|
|
|
|
insertNonHydratedInstance(hydrationParentFiber, fiber);
|
|
isHydrating = false;
|
|
hydrationParentFiber = fiber;
|
|
return;
|
|
}
|
|
|
|
var firstAttemptedInstance = nextInstance;
|
|
|
|
if (!tryHydrate(fiber, nextInstance)) {
|
|
if (shouldClientRenderOnMismatch(fiber)) {
|
|
warnNonhydratedInstance(hydrationParentFiber, fiber);
|
|
throwOnHydrationMismatch();
|
|
} // If we can't hydrate this instance let's try the next one.
|
|
// We use this as a heuristic. It's based on intuition and not data so it
|
|
// might be flawed or unnecessary.
|
|
|
|
nextInstance = getNextHydratableSibling(firstAttemptedInstance);
|
|
var prevHydrationParentFiber = hydrationParentFiber;
|
|
|
|
if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
|
|
// Nothing to hydrate. Make it an insertion.
|
|
insertNonHydratedInstance(hydrationParentFiber, fiber);
|
|
isHydrating = false;
|
|
hydrationParentFiber = fiber;
|
|
return;
|
|
} // We matched the next one, we'll now assume that the first one was
|
|
// superfluous and we'll delete it. Since we can't eagerly delete it
|
|
// we'll have to schedule a deletion. To do that, this node needs a dummy
|
|
// fiber associated with it.
|
|
|
|
deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance);
|
|
}
|
|
}
|
|
|
|
function prepareToHydrateHostInstance(fiber, hostContext) {
|
|
var instance = fiber.stateNode;
|
|
var shouldWarnIfMismatchDev = !didSuspendOrErrorDEV;
|
|
var updatePayload = hydrateInstance(
|
|
instance,
|
|
fiber.type,
|
|
fiber.memoizedProps,
|
|
hostContext,
|
|
fiber,
|
|
shouldWarnIfMismatchDev
|
|
); // TODO: Type this specific to this type of component.
|
|
|
|
fiber.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there
|
|
// is a new ref we mark this as an update.
|
|
|
|
if (updatePayload !== null) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function prepareToHydrateHostTextInstance(fiber) {
|
|
var textInstance = fiber.stateNode;
|
|
var textContent = fiber.memoizedProps;
|
|
var shouldWarnIfMismatchDev = !didSuspendOrErrorDEV;
|
|
var shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber);
|
|
|
|
if (shouldUpdate) {
|
|
// We assume that prepareToHydrateHostTextInstance is called in a context where the
|
|
// hydration parent is the parent host component of this host text.
|
|
var returnFiber = hydrationParentFiber;
|
|
|
|
if (returnFiber !== null) {
|
|
switch (returnFiber.tag) {
|
|
case HostRoot: {
|
|
var parentContainer = returnFiber.stateNode.containerInfo;
|
|
var isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
|
|
didNotMatchHydratedContainerTextInstance(
|
|
parentContainer,
|
|
textInstance,
|
|
textContent, // TODO: Delete this argument when we remove the legacy root API.
|
|
isConcurrentMode,
|
|
shouldWarnIfMismatchDev
|
|
);
|
|
break;
|
|
}
|
|
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
var parentType = returnFiber.type;
|
|
var parentProps = returnFiber.memoizedProps;
|
|
var parentInstance = returnFiber.stateNode;
|
|
|
|
var _isConcurrentMode2 =
|
|
(returnFiber.mode & ConcurrentMode) !== NoMode;
|
|
|
|
didNotMatchHydratedTextInstance(
|
|
parentType,
|
|
parentProps,
|
|
parentInstance,
|
|
textInstance,
|
|
textContent, // TODO: Delete this argument when we remove the legacy root API.
|
|
_isConcurrentMode2,
|
|
shouldWarnIfMismatchDev
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return shouldUpdate;
|
|
}
|
|
|
|
function prepareToHydrateHostSuspenseInstance(fiber) {
|
|
var suspenseState = fiber.memoizedState;
|
|
var suspenseInstance =
|
|
suspenseState !== null ? suspenseState.dehydrated : null;
|
|
|
|
if (!suspenseInstance) {
|
|
throw new Error(
|
|
"Expected to have a hydrated suspense instance. " +
|
|
"This error is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
hydrateSuspenseInstance(suspenseInstance, fiber);
|
|
}
|
|
|
|
function skipPastDehydratedSuspenseInstance(fiber) {
|
|
var suspenseState = fiber.memoizedState;
|
|
var suspenseInstance =
|
|
suspenseState !== null ? suspenseState.dehydrated : null;
|
|
|
|
if (!suspenseInstance) {
|
|
throw new Error(
|
|
"Expected to have a hydrated suspense instance. " +
|
|
"This error is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
return getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance);
|
|
}
|
|
|
|
function popToNextHostParent(fiber) {
|
|
var parent = fiber.return;
|
|
|
|
while (
|
|
parent !== null &&
|
|
parent.tag !== HostComponent &&
|
|
parent.tag !== HostRoot &&
|
|
parent.tag !== SuspenseComponent &&
|
|
parent.tag !== HostSingleton
|
|
) {
|
|
parent = parent.return;
|
|
}
|
|
|
|
hydrationParentFiber = parent;
|
|
}
|
|
|
|
function popHydrationState(fiber) {
|
|
if (fiber !== hydrationParentFiber) {
|
|
// We're deeper than the current hydration context, inside an inserted
|
|
// tree.
|
|
return false;
|
|
}
|
|
|
|
if (!isHydrating) {
|
|
// If we're not currently hydrating but we're in a hydration context, then
|
|
// we were an insertion and now need to pop up reenter hydration of our
|
|
// siblings.
|
|
popToNextHostParent(fiber);
|
|
isHydrating = true;
|
|
return false;
|
|
}
|
|
|
|
var shouldClear = false;
|
|
|
|
{
|
|
// With float we never clear the Root, or Singleton instances. We also do not clear Instances
|
|
// that have singleton text content
|
|
if (
|
|
fiber.tag !== HostRoot &&
|
|
fiber.tag !== HostSingleton &&
|
|
!(
|
|
fiber.tag === HostComponent &&
|
|
shouldSetTextContent(fiber.type, fiber.memoizedProps)
|
|
)
|
|
) {
|
|
shouldClear = true;
|
|
}
|
|
}
|
|
|
|
if (shouldClear) {
|
|
var nextInstance = nextHydratableInstance;
|
|
|
|
if (nextInstance) {
|
|
if (shouldClientRenderOnMismatch(fiber)) {
|
|
warnIfUnhydratedTailNodes(fiber);
|
|
throwOnHydrationMismatch();
|
|
} else {
|
|
while (nextInstance) {
|
|
deleteHydratableInstance(fiber, nextInstance);
|
|
nextInstance = getNextHydratableSibling(nextInstance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
popToNextHostParent(fiber);
|
|
|
|
if (fiber.tag === SuspenseComponent) {
|
|
nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber);
|
|
} else {
|
|
nextHydratableInstance = hydrationParentFiber
|
|
? getNextHydratableSibling(fiber.stateNode)
|
|
: null;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function hasUnhydratedTailNodes() {
|
|
return isHydrating && nextHydratableInstance !== null;
|
|
}
|
|
|
|
function warnIfUnhydratedTailNodes(fiber) {
|
|
var nextInstance = nextHydratableInstance;
|
|
|
|
while (nextInstance) {
|
|
warnUnhydratedInstance(fiber, nextInstance);
|
|
nextInstance = getNextHydratableSibling(nextInstance);
|
|
}
|
|
}
|
|
|
|
function resetHydrationState() {
|
|
hydrationParentFiber = null;
|
|
nextHydratableInstance = null;
|
|
isHydrating = false;
|
|
didSuspendOrErrorDEV = false;
|
|
}
|
|
|
|
function upgradeHydrationErrorsToRecoverable() {
|
|
if (hydrationErrors !== null) {
|
|
// Successfully completed a forced client render. The errors that occurred
|
|
// during the hydration attempt are now recovered. We will log them in
|
|
// commit phase, once the entire tree has finished.
|
|
queueRecoverableErrors(hydrationErrors);
|
|
hydrationErrors = null;
|
|
}
|
|
}
|
|
|
|
function getIsHydrating() {
|
|
return isHydrating;
|
|
}
|
|
|
|
function queueHydrationError(error) {
|
|
if (hydrationErrors === null) {
|
|
hydrationErrors = [error];
|
|
} else {
|
|
hydrationErrors.push(error);
|
|
}
|
|
}
|
|
|
|
// we wait until the current render is over (either finished or interrupted)
|
|
// before adding it to the fiber/hook queue. Push to this array so we can
|
|
// access the queue, fiber, update, et al later.
|
|
|
|
var concurrentQueues = [];
|
|
var concurrentQueuesIndex = 0;
|
|
var concurrentlyUpdatedLanes = NoLanes;
|
|
function finishQueueingConcurrentUpdates() {
|
|
var endIndex = concurrentQueuesIndex;
|
|
concurrentQueuesIndex = 0;
|
|
concurrentlyUpdatedLanes = NoLanes;
|
|
var i = 0;
|
|
|
|
while (i < endIndex) {
|
|
var fiber = concurrentQueues[i];
|
|
concurrentQueues[i++] = null;
|
|
var queue = concurrentQueues[i];
|
|
concurrentQueues[i++] = null;
|
|
var update = concurrentQueues[i];
|
|
concurrentQueues[i++] = null;
|
|
var lane = concurrentQueues[i];
|
|
concurrentQueues[i++] = null;
|
|
|
|
if (queue !== null && update !== null) {
|
|
var pending = queue.pending;
|
|
|
|
if (pending === null) {
|
|
// This is the first update. Create a circular list.
|
|
update.next = update;
|
|
} else {
|
|
update.next = pending.next;
|
|
pending.next = update;
|
|
}
|
|
|
|
queue.pending = update;
|
|
}
|
|
|
|
if (lane !== NoLane) {
|
|
markUpdateLaneFromFiberToRoot(fiber, update, lane);
|
|
}
|
|
}
|
|
}
|
|
function getConcurrentlyUpdatedLanes() {
|
|
return concurrentlyUpdatedLanes;
|
|
}
|
|
|
|
function enqueueUpdate(fiber, queue, update, lane) {
|
|
// Don't update the `childLanes` on the return path yet. If we already in
|
|
// the middle of rendering, wait until after it has completed.
|
|
concurrentQueues[concurrentQueuesIndex++] = fiber;
|
|
concurrentQueues[concurrentQueuesIndex++] = queue;
|
|
concurrentQueues[concurrentQueuesIndex++] = update;
|
|
concurrentQueues[concurrentQueuesIndex++] = lane;
|
|
concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane); // The fiber's `lane` field is used in some places to check if any work is
|
|
// scheduled, to perform an eager bailout, so we need to update it immediately.
|
|
// TODO: We should probably move this to the "shared" queue instead.
|
|
|
|
fiber.lanes = mergeLanes(fiber.lanes, lane);
|
|
var alternate = fiber.alternate;
|
|
|
|
if (alternate !== null) {
|
|
alternate.lanes = mergeLanes(alternate.lanes, lane);
|
|
}
|
|
}
|
|
|
|
function enqueueConcurrentHookUpdate(fiber, queue, update, lane) {
|
|
var concurrentQueue = queue;
|
|
var concurrentUpdate = update;
|
|
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
|
|
return getRootForUpdatedFiber(fiber);
|
|
}
|
|
function enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update) {
|
|
// This function is used to queue an update that doesn't need a rerender. The
|
|
// only reason we queue it is in case there's a subsequent higher priority
|
|
// update that causes it to be rebased.
|
|
var lane = NoLane;
|
|
var concurrentQueue = queue;
|
|
var concurrentUpdate = update;
|
|
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); // Usually we can rely on the upcoming render phase to process the concurrent
|
|
// queue. However, since this is a bail out, we're not scheduling any work
|
|
// here. So the update we just queued will leak until something else happens
|
|
// to schedule work (if ever).
|
|
//
|
|
// Check if we're currently in the middle of rendering a tree, and if not,
|
|
// process the queue immediately to prevent a leak.
|
|
|
|
var isConcurrentlyRendering = getWorkInProgressRoot() !== null;
|
|
|
|
if (!isConcurrentlyRendering) {
|
|
finishQueueingConcurrentUpdates();
|
|
}
|
|
}
|
|
function enqueueConcurrentClassUpdate(fiber, queue, update, lane) {
|
|
var concurrentQueue = queue;
|
|
var concurrentUpdate = update;
|
|
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
|
|
return getRootForUpdatedFiber(fiber);
|
|
}
|
|
function enqueueConcurrentRenderForLane(fiber, lane) {
|
|
enqueueUpdate(fiber, null, null, lane);
|
|
return getRootForUpdatedFiber(fiber);
|
|
} // Calling this function outside this module should only be done for backwards
|
|
// compatibility and should always be accompanied by a warning.
|
|
|
|
function unsafe_markUpdateLaneFromFiberToRoot(sourceFiber, lane) {
|
|
// NOTE: For Hyrum's Law reasons, if an infinite update loop is detected, it
|
|
// should throw before `markUpdateLaneFromFiberToRoot` is called. But this is
|
|
// undefined behavior and we can change it if we need to; it just so happens
|
|
// that, at the time of this writing, there's an internal product test that
|
|
// happens to rely on this.
|
|
var root = getRootForUpdatedFiber(sourceFiber);
|
|
markUpdateLaneFromFiberToRoot(sourceFiber, null, lane);
|
|
return root;
|
|
}
|
|
|
|
function markUpdateLaneFromFiberToRoot(sourceFiber, update, lane) {
|
|
// Update the source fiber's lanes
|
|
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
|
|
var alternate = sourceFiber.alternate;
|
|
|
|
if (alternate !== null) {
|
|
alternate.lanes = mergeLanes(alternate.lanes, lane);
|
|
} // Walk the parent path to the root and update the child lanes.
|
|
|
|
var isHidden = false;
|
|
var parent = sourceFiber.return;
|
|
var node = sourceFiber;
|
|
|
|
while (parent !== null) {
|
|
parent.childLanes = mergeLanes(parent.childLanes, lane);
|
|
alternate = parent.alternate;
|
|
|
|
if (alternate !== null) {
|
|
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
|
|
}
|
|
|
|
if (parent.tag === OffscreenComponent) {
|
|
// Check if this offscreen boundary is currently hidden.
|
|
//
|
|
// The instance may be null if the Offscreen parent was unmounted. Usually
|
|
// the parent wouldn't be reachable in that case because we disconnect
|
|
// fibers from the tree when they are deleted. However, there's a weird
|
|
// edge case where setState is called on a fiber that was interrupted
|
|
// before it ever mounted. Because it never mounts, it also never gets
|
|
// deleted. Because it never gets deleted, its return pointer never gets
|
|
// disconnected. Which means it may be attached to a deleted Offscreen
|
|
// parent node. (This discovery suggests it may be better for memory usage
|
|
// if we don't attach the `return` pointer until the commit phase, though
|
|
// in order to do that we'd need some other way to track the return
|
|
// pointer during the initial render, like on the stack.)
|
|
//
|
|
// This case is always accompanied by a warning, but we still need to
|
|
// account for it. (There may be other cases that we haven't discovered,
|
|
// too.)
|
|
var offscreenInstance = parent.stateNode;
|
|
|
|
if (
|
|
offscreenInstance !== null &&
|
|
!(offscreenInstance._visibility & OffscreenVisible)
|
|
) {
|
|
isHidden = true;
|
|
}
|
|
}
|
|
|
|
node = parent;
|
|
parent = parent.return;
|
|
}
|
|
|
|
if (isHidden && update !== null && node.tag === HostRoot) {
|
|
var root = node.stateNode;
|
|
markHiddenUpdate(root, update, lane);
|
|
}
|
|
}
|
|
|
|
function getRootForUpdatedFiber(sourceFiber) {
|
|
// TODO: We will detect and infinite update loop and throw even if this fiber
|
|
// has already unmounted. This isn't really necessary but it happens to be the
|
|
// current behavior we've used for several release cycles. Consider not
|
|
// performing this check if the updated fiber already unmounted, since it's
|
|
// not possible for that to cause an infinite update loop.
|
|
throwIfInfiniteUpdateLoopDetected(); // When a setState happens, we must ensure the root is scheduled. Because
|
|
// update queues do not have a backpointer to the root, the only way to do
|
|
// this currently is to walk up the return path. This used to not be a big
|
|
// deal because we would have to walk up the return path to set
|
|
// the `childLanes`, anyway, but now those two traversals happen at
|
|
// different times.
|
|
// TODO: Consider adding a `root` backpointer on the update queue.
|
|
|
|
detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
|
|
var node = sourceFiber;
|
|
var parent = node.return;
|
|
|
|
while (parent !== null) {
|
|
detectUpdateOnUnmountedFiber(sourceFiber, node);
|
|
node = parent;
|
|
parent = node.return;
|
|
}
|
|
|
|
return node.tag === HostRoot ? node.stateNode : null;
|
|
}
|
|
|
|
function detectUpdateOnUnmountedFiber(sourceFiber, parent) {
|
|
{
|
|
var alternate = parent.alternate;
|
|
|
|
if (
|
|
alternate === null &&
|
|
(parent.flags & (Placement | Hydrating)) !== NoFlags
|
|
) {
|
|
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
|
|
}
|
|
}
|
|
}
|
|
|
|
var UpdateState = 0;
|
|
var ReplaceState = 1;
|
|
var ForceUpdate = 2;
|
|
var CaptureUpdate = 3; // Global state that is reset at the beginning of calling `processUpdateQueue`.
|
|
// It should only be read right after calling `processUpdateQueue`, via
|
|
// `checkHasForceUpdateAfterProcessing`.
|
|
|
|
var hasForceUpdate = false;
|
|
var didWarnUpdateInsideUpdate;
|
|
var currentlyProcessingQueue;
|
|
|
|
{
|
|
didWarnUpdateInsideUpdate = false;
|
|
currentlyProcessingQueue = null;
|
|
}
|
|
|
|
function initializeUpdateQueue(fiber) {
|
|
var queue = {
|
|
baseState: fiber.memoizedState,
|
|
firstBaseUpdate: null,
|
|
lastBaseUpdate: null,
|
|
shared: {
|
|
pending: null,
|
|
lanes: NoLanes,
|
|
hiddenCallbacks: null
|
|
},
|
|
callbacks: null
|
|
};
|
|
fiber.updateQueue = queue;
|
|
}
|
|
function cloneUpdateQueue(current, workInProgress) {
|
|
// Clone the update queue from current. Unless it's already a clone.
|
|
var queue = workInProgress.updateQueue;
|
|
var currentQueue = current.updateQueue;
|
|
|
|
if (queue === currentQueue) {
|
|
var clone = {
|
|
baseState: currentQueue.baseState,
|
|
firstBaseUpdate: currentQueue.firstBaseUpdate,
|
|
lastBaseUpdate: currentQueue.lastBaseUpdate,
|
|
shared: currentQueue.shared,
|
|
callbacks: null
|
|
};
|
|
workInProgress.updateQueue = clone;
|
|
}
|
|
}
|
|
function createUpdate(eventTime, lane) {
|
|
var update = {
|
|
eventTime: eventTime,
|
|
lane: lane,
|
|
tag: UpdateState,
|
|
payload: null,
|
|
callback: null,
|
|
next: null
|
|
};
|
|
return update;
|
|
}
|
|
function enqueueUpdate$1(fiber, update, lane) {
|
|
var updateQueue = fiber.updateQueue;
|
|
|
|
if (updateQueue === null) {
|
|
// Only occurs if the fiber has been unmounted.
|
|
return null;
|
|
}
|
|
|
|
var sharedQueue = updateQueue.shared;
|
|
|
|
{
|
|
if (
|
|
currentlyProcessingQueue === sharedQueue &&
|
|
!didWarnUpdateInsideUpdate
|
|
) {
|
|
var componentName = getComponentNameFromFiber(fiber);
|
|
|
|
error(
|
|
"An update (setState, replaceState, or forceUpdate) was scheduled " +
|
|
"from inside an update function. Update functions should be pure, " +
|
|
"with zero side-effects. Consider using componentDidUpdate or a " +
|
|
"callback.\n\nPlease update the following component: %s",
|
|
componentName
|
|
);
|
|
|
|
didWarnUpdateInsideUpdate = true;
|
|
}
|
|
}
|
|
|
|
if (isUnsafeClassRenderPhaseUpdate()) {
|
|
// This is an unsafe render phase update. Add directly to the update
|
|
// queue so we can process it immediately during the current render.
|
|
var pending = sharedQueue.pending;
|
|
|
|
if (pending === null) {
|
|
// This is the first update. Create a circular list.
|
|
update.next = update;
|
|
} else {
|
|
update.next = pending.next;
|
|
pending.next = update;
|
|
}
|
|
|
|
sharedQueue.pending = update; // Update the childLanes even though we're most likely already rendering
|
|
// this fiber. This is for backwards compatibility in the case where you
|
|
// update a different component during render phase than the one that is
|
|
// currently renderings (a pattern that is accompanied by a warning).
|
|
|
|
return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
|
|
} else {
|
|
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
|
|
}
|
|
}
|
|
function entangleTransitions(root, fiber, lane) {
|
|
var updateQueue = fiber.updateQueue;
|
|
|
|
if (updateQueue === null) {
|
|
// Only occurs if the fiber has been unmounted.
|
|
return;
|
|
}
|
|
|
|
var sharedQueue = updateQueue.shared;
|
|
|
|
if (isTransitionLane(lane)) {
|
|
var queueLanes = sharedQueue.lanes; // If any entangled lanes are no longer pending on the root, then they must
|
|
// have finished. We can remove them from the shared queue, which represents
|
|
// a superset of the actually pending lanes. In some cases we may entangle
|
|
// more than we need to, but that's OK. In fact it's worse if we *don't*
|
|
// entangle when we should.
|
|
|
|
queueLanes = intersectLanes(queueLanes, root.pendingLanes); // Entangle the new transition lane with the other transition lanes.
|
|
|
|
var newQueueLanes = mergeLanes(queueLanes, lane);
|
|
sharedQueue.lanes = newQueueLanes; // Even if queue.lanes already include lane, we don't know for certain if
|
|
// the lane finished since the last time we entangled it. So we need to
|
|
// entangle it again, just to be sure.
|
|
|
|
markRootEntangled(root, newQueueLanes);
|
|
}
|
|
}
|
|
function enqueueCapturedUpdate(workInProgress, capturedUpdate) {
|
|
// Captured updates are updates that are thrown by a child during the render
|
|
// phase. They should be discarded if the render is aborted. Therefore,
|
|
// we should only put them on the work-in-progress queue, not the current one.
|
|
var queue = workInProgress.updateQueue; // Check if the work-in-progress queue is a clone.
|
|
|
|
var current = workInProgress.alternate;
|
|
|
|
if (current !== null) {
|
|
var currentQueue = current.updateQueue;
|
|
|
|
if (queue === currentQueue) {
|
|
// The work-in-progress queue is the same as current. This happens when
|
|
// we bail out on a parent fiber that then captures an error thrown by
|
|
// a child. Since we want to append the update only to the work-in
|
|
// -progress queue, we need to clone the updates. We usually clone during
|
|
// processUpdateQueue, but that didn't happen in this case because we
|
|
// skipped over the parent when we bailed out.
|
|
var newFirst = null;
|
|
var newLast = null;
|
|
var firstBaseUpdate = queue.firstBaseUpdate;
|
|
|
|
if (firstBaseUpdate !== null) {
|
|
// Loop through the updates and clone them.
|
|
var update = firstBaseUpdate;
|
|
|
|
do {
|
|
var clone = {
|
|
eventTime: update.eventTime,
|
|
lane: update.lane,
|
|
tag: update.tag,
|
|
payload: update.payload,
|
|
// When this update is rebased, we should not fire its
|
|
// callback again.
|
|
callback: null,
|
|
next: null
|
|
};
|
|
|
|
if (newLast === null) {
|
|
newFirst = newLast = clone;
|
|
} else {
|
|
newLast.next = clone;
|
|
newLast = clone;
|
|
} // $FlowFixMe[incompatible-type] we bail out when we get a null
|
|
|
|
update = update.next;
|
|
} while (update !== null); // Append the captured update the end of the cloned list.
|
|
|
|
if (newLast === null) {
|
|
newFirst = newLast = capturedUpdate;
|
|
} else {
|
|
newLast.next = capturedUpdate;
|
|
newLast = capturedUpdate;
|
|
}
|
|
} else {
|
|
// There are no base updates.
|
|
newFirst = newLast = capturedUpdate;
|
|
}
|
|
|
|
queue = {
|
|
baseState: currentQueue.baseState,
|
|
firstBaseUpdate: newFirst,
|
|
lastBaseUpdate: newLast,
|
|
shared: currentQueue.shared,
|
|
callbacks: currentQueue.callbacks
|
|
};
|
|
workInProgress.updateQueue = queue;
|
|
return;
|
|
}
|
|
} // Append the update to the end of the list.
|
|
|
|
var lastBaseUpdate = queue.lastBaseUpdate;
|
|
|
|
if (lastBaseUpdate === null) {
|
|
queue.firstBaseUpdate = capturedUpdate;
|
|
} else {
|
|
lastBaseUpdate.next = capturedUpdate;
|
|
}
|
|
|
|
queue.lastBaseUpdate = capturedUpdate;
|
|
}
|
|
|
|
function getStateFromUpdate(
|
|
workInProgress,
|
|
queue,
|
|
update,
|
|
prevState,
|
|
nextProps,
|
|
instance
|
|
) {
|
|
switch (update.tag) {
|
|
case ReplaceState: {
|
|
var payload = update.payload;
|
|
|
|
if (typeof payload === "function") {
|
|
// Updater function
|
|
{
|
|
enterDisallowedContextReadInDEV();
|
|
}
|
|
|
|
var nextState = payload.call(instance, prevState, nextProps);
|
|
|
|
{
|
|
exitDisallowedContextReadInDEV();
|
|
}
|
|
|
|
return nextState;
|
|
} // State object
|
|
|
|
return payload;
|
|
}
|
|
|
|
case CaptureUpdate: {
|
|
workInProgress.flags =
|
|
(workInProgress.flags & ~ShouldCapture) | DidCapture;
|
|
}
|
|
// Intentional fallthrough
|
|
|
|
case UpdateState: {
|
|
var _payload = update.payload;
|
|
var partialState;
|
|
|
|
if (typeof _payload === "function") {
|
|
// Updater function
|
|
{
|
|
enterDisallowedContextReadInDEV();
|
|
}
|
|
|
|
partialState = _payload.call(instance, prevState, nextProps);
|
|
|
|
{
|
|
exitDisallowedContextReadInDEV();
|
|
}
|
|
} else {
|
|
// Partial state object
|
|
partialState = _payload;
|
|
}
|
|
|
|
if (partialState === null || partialState === undefined) {
|
|
// Null and undefined are treated as no-ops.
|
|
return prevState;
|
|
} // Merge the partial state and the previous state.
|
|
|
|
return assign({}, prevState, partialState);
|
|
}
|
|
|
|
case ForceUpdate: {
|
|
hasForceUpdate = true;
|
|
return prevState;
|
|
}
|
|
}
|
|
|
|
return prevState;
|
|
}
|
|
|
|
function processUpdateQueue(workInProgress, props, instance, renderLanes) {
|
|
// This is always non-null on a ClassComponent or HostRoot
|
|
var queue = workInProgress.updateQueue;
|
|
hasForceUpdate = false;
|
|
|
|
{
|
|
// $FlowFixMe[escaped-generic] discovered when updating Flow
|
|
currentlyProcessingQueue = queue.shared;
|
|
}
|
|
|
|
var firstBaseUpdate = queue.firstBaseUpdate;
|
|
var lastBaseUpdate = queue.lastBaseUpdate; // Check if there are pending updates. If so, transfer them to the base queue.
|
|
|
|
var pendingQueue = queue.shared.pending;
|
|
|
|
if (pendingQueue !== null) {
|
|
queue.shared.pending = null; // The pending queue is circular. Disconnect the pointer between first
|
|
// and last so that it's non-circular.
|
|
|
|
var lastPendingUpdate = pendingQueue;
|
|
var firstPendingUpdate = lastPendingUpdate.next;
|
|
lastPendingUpdate.next = null; // Append pending updates to base queue
|
|
|
|
if (lastBaseUpdate === null) {
|
|
firstBaseUpdate = firstPendingUpdate;
|
|
} else {
|
|
lastBaseUpdate.next = firstPendingUpdate;
|
|
}
|
|
|
|
lastBaseUpdate = lastPendingUpdate; // If there's a current queue, and it's different from the base queue, then
|
|
// we need to transfer the updates to that queue, too. Because the base
|
|
// queue is a singly-linked list with no cycles, we can append to both
|
|
// lists and take advantage of structural sharing.
|
|
// TODO: Pass `current` as argument
|
|
|
|
var current = workInProgress.alternate;
|
|
|
|
if (current !== null) {
|
|
// This is always non-null on a ClassComponent or HostRoot
|
|
var currentQueue = current.updateQueue;
|
|
var currentLastBaseUpdate = currentQueue.lastBaseUpdate;
|
|
|
|
if (currentLastBaseUpdate !== lastBaseUpdate) {
|
|
if (currentLastBaseUpdate === null) {
|
|
currentQueue.firstBaseUpdate = firstPendingUpdate;
|
|
} else {
|
|
currentLastBaseUpdate.next = firstPendingUpdate;
|
|
}
|
|
|
|
currentQueue.lastBaseUpdate = lastPendingUpdate;
|
|
}
|
|
}
|
|
} // These values may change as we process the queue.
|
|
|
|
if (firstBaseUpdate !== null) {
|
|
// Iterate through the list of updates to compute the result.
|
|
var newState = queue.baseState; // TODO: Don't need to accumulate this. Instead, we can remove renderLanes
|
|
// from the original lanes.
|
|
|
|
var newLanes = NoLanes;
|
|
var newBaseState = null;
|
|
var newFirstBaseUpdate = null;
|
|
var newLastBaseUpdate = null;
|
|
var update = firstBaseUpdate;
|
|
|
|
do {
|
|
// TODO: Don't need this field anymore
|
|
var updateEventTime = update.eventTime; // An extra OffscreenLane bit is added to updates that were made to
|
|
// a hidden tree, so that we can distinguish them from updates that were
|
|
// already there when the tree was hidden.
|
|
|
|
var updateLane = removeLanes(update.lane, OffscreenLane);
|
|
var isHiddenUpdate = updateLane !== update.lane; // Check if this update was made while the tree was hidden. If so, then
|
|
// it's not a "base" update and we should disregard the extra base lanes
|
|
// that were added to renderLanes when we entered the Offscreen tree.
|
|
|
|
var shouldSkipUpdate = isHiddenUpdate
|
|
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
|
|
: !isSubsetOfLanes(renderLanes, updateLane);
|
|
|
|
if (shouldSkipUpdate) {
|
|
// Priority is insufficient. Skip this update. If this is the first
|
|
// skipped update, the previous update/state is the new base
|
|
// update/state.
|
|
var clone = {
|
|
eventTime: updateEventTime,
|
|
lane: updateLane,
|
|
tag: update.tag,
|
|
payload: update.payload,
|
|
callback: update.callback,
|
|
next: null
|
|
};
|
|
|
|
if (newLastBaseUpdate === null) {
|
|
newFirstBaseUpdate = newLastBaseUpdate = clone;
|
|
newBaseState = newState;
|
|
} else {
|
|
newLastBaseUpdate = newLastBaseUpdate.next = clone;
|
|
} // Update the remaining priority in the queue.
|
|
|
|
newLanes = mergeLanes(newLanes, updateLane);
|
|
} else {
|
|
// This update does have sufficient priority.
|
|
if (newLastBaseUpdate !== null) {
|
|
var _clone = {
|
|
eventTime: updateEventTime,
|
|
// This update is going to be committed so we never want uncommit
|
|
// it. Using NoLane works because 0 is a subset of all bitmasks, so
|
|
// this will never be skipped by the check above.
|
|
lane: NoLane,
|
|
tag: update.tag,
|
|
payload: update.payload,
|
|
// When this update is rebased, we should not fire its
|
|
// callback again.
|
|
callback: null,
|
|
next: null
|
|
};
|
|
newLastBaseUpdate = newLastBaseUpdate.next = _clone;
|
|
} // Process this update.
|
|
|
|
newState = getStateFromUpdate(
|
|
workInProgress,
|
|
queue,
|
|
update,
|
|
newState,
|
|
props,
|
|
instance
|
|
);
|
|
var callback = update.callback;
|
|
|
|
if (callback !== null) {
|
|
workInProgress.flags |= Callback;
|
|
|
|
if (isHiddenUpdate) {
|
|
workInProgress.flags |= Visibility;
|
|
}
|
|
|
|
var callbacks = queue.callbacks;
|
|
|
|
if (callbacks === null) {
|
|
queue.callbacks = [callback];
|
|
} else {
|
|
callbacks.push(callback);
|
|
}
|
|
}
|
|
} // $FlowFixMe[incompatible-type] we bail out when we get a null
|
|
|
|
update = update.next;
|
|
|
|
if (update === null) {
|
|
pendingQueue = queue.shared.pending;
|
|
|
|
if (pendingQueue === null) {
|
|
break;
|
|
} else {
|
|
// An update was scheduled from inside a reducer. Add the new
|
|
// pending updates to the end of the list and keep processing.
|
|
var _lastPendingUpdate = pendingQueue; // Intentionally unsound. Pending updates form a circular list, but we
|
|
// unravel them when transferring them to the base queue.
|
|
|
|
var _firstPendingUpdate = _lastPendingUpdate.next;
|
|
_lastPendingUpdate.next = null;
|
|
update = _firstPendingUpdate;
|
|
queue.lastBaseUpdate = _lastPendingUpdate;
|
|
queue.shared.pending = null;
|
|
}
|
|
}
|
|
} while (true);
|
|
|
|
if (newLastBaseUpdate === null) {
|
|
newBaseState = newState;
|
|
}
|
|
|
|
queue.baseState = newBaseState;
|
|
queue.firstBaseUpdate = newFirstBaseUpdate;
|
|
queue.lastBaseUpdate = newLastBaseUpdate;
|
|
|
|
if (firstBaseUpdate === null) {
|
|
// `queue.lanes` is used for entangling transitions. We can set it back to
|
|
// zero once the queue is empty.
|
|
queue.shared.lanes = NoLanes;
|
|
} // Set the remaining expiration time to be whatever is remaining in the queue.
|
|
// This should be fine because the only two other things that contribute to
|
|
// expiration time are props and context. We're already in the middle of the
|
|
// begin phase by the time we start processing the queue, so we've already
|
|
// dealt with the props. Context in components that specify
|
|
// shouldComponentUpdate is tricky; but we'll have to account for
|
|
// that regardless.
|
|
|
|
markSkippedUpdateLanes(newLanes);
|
|
workInProgress.lanes = newLanes;
|
|
workInProgress.memoizedState = newState;
|
|
}
|
|
|
|
{
|
|
currentlyProcessingQueue = null;
|
|
}
|
|
}
|
|
|
|
function callCallback(callback, context) {
|
|
if (typeof callback !== "function") {
|
|
throw new Error(
|
|
"Invalid argument passed as callback. Expected a function. Instead " +
|
|
("received: " + callback)
|
|
);
|
|
}
|
|
|
|
callback.call(context);
|
|
}
|
|
|
|
function resetHasForceUpdateBeforeProcessing() {
|
|
hasForceUpdate = false;
|
|
}
|
|
function checkHasForceUpdateAfterProcessing() {
|
|
return hasForceUpdate;
|
|
}
|
|
function deferHiddenCallbacks(updateQueue) {
|
|
// When an update finishes on a hidden component, its callback should not
|
|
// be fired until/unless the component is made visible again. Stash the
|
|
// callback on the shared queue object so it can be fired later.
|
|
var newHiddenCallbacks = updateQueue.callbacks;
|
|
|
|
if (newHiddenCallbacks !== null) {
|
|
var existingHiddenCallbacks = updateQueue.shared.hiddenCallbacks;
|
|
|
|
if (existingHiddenCallbacks === null) {
|
|
updateQueue.shared.hiddenCallbacks = newHiddenCallbacks;
|
|
} else {
|
|
updateQueue.shared.hiddenCallbacks = existingHiddenCallbacks.concat(
|
|
newHiddenCallbacks
|
|
);
|
|
}
|
|
}
|
|
}
|
|
function commitHiddenCallbacks(updateQueue, context) {
|
|
// This component is switching from hidden -> visible. Commit any callbacks
|
|
// that were previously deferred.
|
|
var hiddenCallbacks = updateQueue.shared.hiddenCallbacks;
|
|
|
|
if (hiddenCallbacks !== null) {
|
|
updateQueue.shared.hiddenCallbacks = null;
|
|
|
|
for (var i = 0; i < hiddenCallbacks.length; i++) {
|
|
var callback = hiddenCallbacks[i];
|
|
callCallback(callback, context);
|
|
}
|
|
}
|
|
}
|
|
function commitCallbacks(updateQueue, context) {
|
|
var callbacks = updateQueue.callbacks;
|
|
|
|
if (callbacks !== null) {
|
|
updateQueue.callbacks = null;
|
|
|
|
for (var i = 0; i < callbacks.length; i++) {
|
|
var callback = callbacks[i];
|
|
callCallback(callback, context);
|
|
}
|
|
}
|
|
}
|
|
|
|
var ReactStrictModeWarnings = {
|
|
recordUnsafeLifecycleWarnings: function(fiber, instance) {},
|
|
flushPendingUnsafeLifecycleWarnings: function() {},
|
|
recordLegacyContextWarning: function(fiber, instance) {},
|
|
flushLegacyContextWarning: function() {},
|
|
discardPendingWarnings: function() {}
|
|
};
|
|
|
|
{
|
|
var findStrictRoot = function(fiber) {
|
|
var maybeStrictRoot = null;
|
|
var node = fiber;
|
|
|
|
while (node !== null) {
|
|
if (node.mode & StrictLegacyMode) {
|
|
maybeStrictRoot = node;
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
|
|
return maybeStrictRoot;
|
|
};
|
|
|
|
var setToSortedString = function(set) {
|
|
var array = [];
|
|
set.forEach(function(value) {
|
|
array.push(value);
|
|
});
|
|
return array.sort().join(", ");
|
|
};
|
|
|
|
var pendingComponentWillMountWarnings = [];
|
|
var pendingUNSAFE_ComponentWillMountWarnings = [];
|
|
var pendingComponentWillReceivePropsWarnings = [];
|
|
var pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
|
|
var pendingComponentWillUpdateWarnings = [];
|
|
var pendingUNSAFE_ComponentWillUpdateWarnings = []; // Tracks components we have already warned about.
|
|
|
|
var didWarnAboutUnsafeLifecycles = new Set();
|
|
|
|
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = function(
|
|
fiber,
|
|
instance
|
|
) {
|
|
// Dedupe strategy: Warn once per component.
|
|
if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
typeof instance.componentWillMount === "function" && // Don't warn about react-lifecycles-compat polyfilled components.
|
|
instance.componentWillMount.__suppressDeprecationWarning !== true
|
|
) {
|
|
pendingComponentWillMountWarnings.push(fiber);
|
|
}
|
|
|
|
if (
|
|
fiber.mode & StrictLegacyMode &&
|
|
typeof instance.UNSAFE_componentWillMount === "function"
|
|
) {
|
|
pendingUNSAFE_ComponentWillMountWarnings.push(fiber);
|
|
}
|
|
|
|
if (
|
|
typeof instance.componentWillReceiveProps === "function" &&
|
|
instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
|
|
) {
|
|
pendingComponentWillReceivePropsWarnings.push(fiber);
|
|
}
|
|
|
|
if (
|
|
fiber.mode & StrictLegacyMode &&
|
|
typeof instance.UNSAFE_componentWillReceiveProps === "function"
|
|
) {
|
|
pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber);
|
|
}
|
|
|
|
if (
|
|
typeof instance.componentWillUpdate === "function" &&
|
|
instance.componentWillUpdate.__suppressDeprecationWarning !== true
|
|
) {
|
|
pendingComponentWillUpdateWarnings.push(fiber);
|
|
}
|
|
|
|
if (
|
|
fiber.mode & StrictLegacyMode &&
|
|
typeof instance.UNSAFE_componentWillUpdate === "function"
|
|
) {
|
|
pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber);
|
|
}
|
|
};
|
|
|
|
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = function() {
|
|
// We do an initial pass to gather component names
|
|
var componentWillMountUniqueNames = new Set();
|
|
|
|
if (pendingComponentWillMountWarnings.length > 0) {
|
|
pendingComponentWillMountWarnings.forEach(function(fiber) {
|
|
componentWillMountUniqueNames.add(
|
|
getComponentNameFromFiber(fiber) || "Component"
|
|
);
|
|
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
|
});
|
|
pendingComponentWillMountWarnings = [];
|
|
}
|
|
|
|
var UNSAFE_componentWillMountUniqueNames = new Set();
|
|
|
|
if (pendingUNSAFE_ComponentWillMountWarnings.length > 0) {
|
|
pendingUNSAFE_ComponentWillMountWarnings.forEach(function(fiber) {
|
|
UNSAFE_componentWillMountUniqueNames.add(
|
|
getComponentNameFromFiber(fiber) || "Component"
|
|
);
|
|
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
|
});
|
|
pendingUNSAFE_ComponentWillMountWarnings = [];
|
|
}
|
|
|
|
var componentWillReceivePropsUniqueNames = new Set();
|
|
|
|
if (pendingComponentWillReceivePropsWarnings.length > 0) {
|
|
pendingComponentWillReceivePropsWarnings.forEach(function(fiber) {
|
|
componentWillReceivePropsUniqueNames.add(
|
|
getComponentNameFromFiber(fiber) || "Component"
|
|
);
|
|
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
|
});
|
|
pendingComponentWillReceivePropsWarnings = [];
|
|
}
|
|
|
|
var UNSAFE_componentWillReceivePropsUniqueNames = new Set();
|
|
|
|
if (pendingUNSAFE_ComponentWillReceivePropsWarnings.length > 0) {
|
|
pendingUNSAFE_ComponentWillReceivePropsWarnings.forEach(function(fiber) {
|
|
UNSAFE_componentWillReceivePropsUniqueNames.add(
|
|
getComponentNameFromFiber(fiber) || "Component"
|
|
);
|
|
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
|
});
|
|
pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
|
|
}
|
|
|
|
var componentWillUpdateUniqueNames = new Set();
|
|
|
|
if (pendingComponentWillUpdateWarnings.length > 0) {
|
|
pendingComponentWillUpdateWarnings.forEach(function(fiber) {
|
|
componentWillUpdateUniqueNames.add(
|
|
getComponentNameFromFiber(fiber) || "Component"
|
|
);
|
|
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
|
});
|
|
pendingComponentWillUpdateWarnings = [];
|
|
}
|
|
|
|
var UNSAFE_componentWillUpdateUniqueNames = new Set();
|
|
|
|
if (pendingUNSAFE_ComponentWillUpdateWarnings.length > 0) {
|
|
pendingUNSAFE_ComponentWillUpdateWarnings.forEach(function(fiber) {
|
|
UNSAFE_componentWillUpdateUniqueNames.add(
|
|
getComponentNameFromFiber(fiber) || "Component"
|
|
);
|
|
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
|
});
|
|
pendingUNSAFE_ComponentWillUpdateWarnings = [];
|
|
} // Finally, we flush all the warnings
|
|
// UNSAFE_ ones before the deprecated ones, since they'll be 'louder'
|
|
|
|
if (UNSAFE_componentWillMountUniqueNames.size > 0) {
|
|
var sortedNames = setToSortedString(UNSAFE_componentWillMountUniqueNames);
|
|
|
|
error(
|
|
"Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. " +
|
|
"See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n" +
|
|
"* Move code with side effects to componentDidMount, and set initial state in the constructor.\n" +
|
|
"\nPlease update the following components: %s",
|
|
sortedNames
|
|
);
|
|
}
|
|
|
|
if (UNSAFE_componentWillReceivePropsUniqueNames.size > 0) {
|
|
var _sortedNames = setToSortedString(
|
|
UNSAFE_componentWillReceivePropsUniqueNames
|
|
);
|
|
|
|
error(
|
|
"Using UNSAFE_componentWillReceiveProps in strict mode is not recommended " +
|
|
"and may indicate bugs in your code. " +
|
|
"See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n" +
|
|
"* Move data fetching code or side effects to componentDidUpdate.\n" +
|
|
"* If you're updating state whenever props change, " +
|
|
"refactor your code to use memoization techniques or move it to " +
|
|
"static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state\n" +
|
|
"\nPlease update the following components: %s",
|
|
_sortedNames
|
|
);
|
|
}
|
|
|
|
if (UNSAFE_componentWillUpdateUniqueNames.size > 0) {
|
|
var _sortedNames2 = setToSortedString(
|
|
UNSAFE_componentWillUpdateUniqueNames
|
|
);
|
|
|
|
error(
|
|
"Using UNSAFE_componentWillUpdate in strict mode is not recommended " +
|
|
"and may indicate bugs in your code. " +
|
|
"See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n" +
|
|
"* Move data fetching code or side effects to componentDidUpdate.\n" +
|
|
"\nPlease update the following components: %s",
|
|
_sortedNames2
|
|
);
|
|
}
|
|
|
|
if (componentWillMountUniqueNames.size > 0) {
|
|
var _sortedNames3 = setToSortedString(componentWillMountUniqueNames);
|
|
|
|
warn(
|
|
"componentWillMount has been renamed, and is not recommended for use. " +
|
|
"See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n" +
|
|
"* Move code with side effects to componentDidMount, and set initial state in the constructor.\n" +
|
|
"* Rename componentWillMount to UNSAFE_componentWillMount to suppress " +
|
|
"this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. " +
|
|
"To rename all deprecated lifecycles to their new names, you can run " +
|
|
"`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n" +
|
|
"\nPlease update the following components: %s",
|
|
_sortedNames3
|
|
);
|
|
}
|
|
|
|
if (componentWillReceivePropsUniqueNames.size > 0) {
|
|
var _sortedNames4 = setToSortedString(
|
|
componentWillReceivePropsUniqueNames
|
|
);
|
|
|
|
warn(
|
|
"componentWillReceiveProps has been renamed, and is not recommended for use. " +
|
|
"See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n" +
|
|
"* Move data fetching code or side effects to componentDidUpdate.\n" +
|
|
"* If you're updating state whenever props change, refactor your " +
|
|
"code to use memoization techniques or move it to " +
|
|
"static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state\n" +
|
|
"* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress " +
|
|
"this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. " +
|
|
"To rename all deprecated lifecycles to their new names, you can run " +
|
|
"`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n" +
|
|
"\nPlease update the following components: %s",
|
|
_sortedNames4
|
|
);
|
|
}
|
|
|
|
if (componentWillUpdateUniqueNames.size > 0) {
|
|
var _sortedNames5 = setToSortedString(componentWillUpdateUniqueNames);
|
|
|
|
warn(
|
|
"componentWillUpdate has been renamed, and is not recommended for use. " +
|
|
"See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n" +
|
|
"* Move data fetching code or side effects to componentDidUpdate.\n" +
|
|
"* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress " +
|
|
"this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. " +
|
|
"To rename all deprecated lifecycles to their new names, you can run " +
|
|
"`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n" +
|
|
"\nPlease update the following components: %s",
|
|
_sortedNames5
|
|
);
|
|
}
|
|
};
|
|
|
|
var pendingLegacyContextWarning = new Map(); // Tracks components we have already warned about.
|
|
|
|
var didWarnAboutLegacyContext = new Set();
|
|
|
|
ReactStrictModeWarnings.recordLegacyContextWarning = function(
|
|
fiber,
|
|
instance
|
|
) {
|
|
var strictRoot = findStrictRoot(fiber);
|
|
|
|
if (strictRoot === null) {
|
|
error(
|
|
"Expected to find a StrictMode component in a strict mode tree. " +
|
|
"This error is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
|
|
return;
|
|
} // Dedup strategy: Warn once per component.
|
|
|
|
if (didWarnAboutLegacyContext.has(fiber.type)) {
|
|
return;
|
|
}
|
|
|
|
var warningsForRoot = pendingLegacyContextWarning.get(strictRoot);
|
|
|
|
if (
|
|
fiber.type.contextTypes != null ||
|
|
fiber.type.childContextTypes != null ||
|
|
(instance !== null && typeof instance.getChildContext === "function")
|
|
) {
|
|
if (warningsForRoot === undefined) {
|
|
warningsForRoot = [];
|
|
pendingLegacyContextWarning.set(strictRoot, warningsForRoot);
|
|
}
|
|
|
|
warningsForRoot.push(fiber);
|
|
}
|
|
};
|
|
|
|
ReactStrictModeWarnings.flushLegacyContextWarning = function() {
|
|
pendingLegacyContextWarning.forEach(function(fiberArray, strictRoot) {
|
|
if (fiberArray.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var firstFiber = fiberArray[0];
|
|
var uniqueNames = new Set();
|
|
fiberArray.forEach(function(fiber) {
|
|
uniqueNames.add(getComponentNameFromFiber(fiber) || "Component");
|
|
didWarnAboutLegacyContext.add(fiber.type);
|
|
});
|
|
var sortedNames = setToSortedString(uniqueNames);
|
|
|
|
try {
|
|
setCurrentFiber(firstFiber);
|
|
|
|
error(
|
|
"Legacy context API has been detected within a strict-mode tree." +
|
|
"\n\nThe old API will be supported in all 16.x releases, but applications " +
|
|
"using it should migrate to the new version." +
|
|
"\n\nPlease update the following components: %s" +
|
|
"\n\nLearn more about this warning here: https://reactjs.org/link/legacy-context",
|
|
sortedNames
|
|
);
|
|
} finally {
|
|
resetCurrentFiber();
|
|
}
|
|
});
|
|
};
|
|
|
|
ReactStrictModeWarnings.discardPendingWarnings = function() {
|
|
pendingComponentWillMountWarnings = [];
|
|
pendingUNSAFE_ComponentWillMountWarnings = [];
|
|
pendingComponentWillReceivePropsWarnings = [];
|
|
pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
|
|
pendingComponentWillUpdateWarnings = [];
|
|
pendingUNSAFE_ComponentWillUpdateWarnings = [];
|
|
pendingLegacyContextWarning = new Map();
|
|
};
|
|
}
|
|
|
|
var didWarnAboutMaps;
|
|
var didWarnAboutGenerators;
|
|
var didWarnAboutStringRefs;
|
|
var ownerHasKeyUseWarning;
|
|
var ownerHasFunctionTypeWarning;
|
|
|
|
var warnForMissingKey = function(child, returnFiber) {};
|
|
|
|
{
|
|
didWarnAboutMaps = false;
|
|
didWarnAboutGenerators = false;
|
|
didWarnAboutStringRefs = {};
|
|
/**
|
|
* Warn if there's no key explicitly set on dynamic arrays of children or
|
|
* object keys are not valid. This allows us to keep track of children between
|
|
* updates.
|
|
*/
|
|
|
|
ownerHasKeyUseWarning = {};
|
|
ownerHasFunctionTypeWarning = {};
|
|
|
|
warnForMissingKey = function(child, returnFiber) {
|
|
if (child === null || typeof child !== "object") {
|
|
return;
|
|
}
|
|
|
|
if (!child._store || child._store.validated || child.key != null) {
|
|
return;
|
|
}
|
|
|
|
if (typeof child._store !== "object") {
|
|
throw new Error(
|
|
"React Component in warnForMissingKey should have a _store. " +
|
|
"This error is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
} // $FlowFixMe unable to narrow type from mixed to writable object
|
|
|
|
child._store.validated = true;
|
|
var componentName = getComponentNameFromFiber(returnFiber) || "Component";
|
|
|
|
if (ownerHasKeyUseWarning[componentName]) {
|
|
return;
|
|
}
|
|
|
|
ownerHasKeyUseWarning[componentName] = true;
|
|
|
|
error(
|
|
"Each child in a list should have a unique " +
|
|
'"key" prop. See https://reactjs.org/link/warning-keys for ' +
|
|
"more information."
|
|
);
|
|
};
|
|
}
|
|
|
|
function isReactClass(type) {
|
|
return type.prototype && type.prototype.isReactComponent;
|
|
}
|
|
|
|
function coerceRef(returnFiber, current, element) {
|
|
var mixedRef = element.ref;
|
|
|
|
if (
|
|
mixedRef !== null &&
|
|
typeof mixedRef !== "function" &&
|
|
typeof mixedRef !== "object"
|
|
) {
|
|
{
|
|
if (
|
|
// We warn in ReactElement.js if owner and self are equal for string refs
|
|
// because these cannot be automatically converted to an arrow function
|
|
// using a codemod. Therefore, we don't have to warn about string refs again.
|
|
!(
|
|
element._owner &&
|
|
element._self &&
|
|
element._owner.stateNode !== element._self
|
|
) && // Will already throw with "Function components cannot have string refs"
|
|
!(element._owner && element._owner.tag !== ClassComponent) && // Will already warn with "Function components cannot be given refs"
|
|
!(typeof element.type === "function" && !isReactClass(element.type)) && // Will already throw with "Element ref was specified as a string (someStringRef) but no owner was set"
|
|
element._owner
|
|
) {
|
|
var componentName =
|
|
getComponentNameFromFiber(returnFiber) || "Component";
|
|
|
|
if (!didWarnAboutStringRefs[componentName]) {
|
|
error(
|
|
'Component "%s" contains the string ref "%s". Support for string refs ' +
|
|
"will be removed in a future major release. We recommend using " +
|
|
"useRef() or createRef() instead. " +
|
|
"Learn more about using refs safely here: " +
|
|
"https://reactjs.org/link/strict-mode-string-ref",
|
|
componentName,
|
|
mixedRef
|
|
);
|
|
|
|
didWarnAboutStringRefs[componentName] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (element._owner) {
|
|
var owner = element._owner;
|
|
var inst;
|
|
|
|
if (owner) {
|
|
var ownerFiber = owner;
|
|
|
|
if (ownerFiber.tag !== ClassComponent) {
|
|
throw new Error(
|
|
"Function components cannot have string refs. " +
|
|
"We recommend using useRef() instead. " +
|
|
"Learn more about using refs safely here: " +
|
|
"https://reactjs.org/link/strict-mode-string-ref"
|
|
);
|
|
}
|
|
|
|
inst = ownerFiber.stateNode;
|
|
}
|
|
|
|
if (!inst) {
|
|
throw new Error(
|
|
"Missing owner for string ref " +
|
|
mixedRef +
|
|
". This error is likely caused by a " +
|
|
"bug in React. Please file an issue."
|
|
);
|
|
} // Assigning this to a const so Flow knows it won't change in the closure
|
|
|
|
var resolvedInst = inst;
|
|
|
|
{
|
|
checkPropStringCoercion(mixedRef, "ref");
|
|
}
|
|
|
|
var stringRef = "" + mixedRef; // Check if previous string ref matches new string ref
|
|
|
|
if (
|
|
current !== null &&
|
|
current.ref !== null &&
|
|
typeof current.ref === "function" &&
|
|
current.ref._stringRef === stringRef
|
|
) {
|
|
return current.ref;
|
|
}
|
|
|
|
var ref = function(value) {
|
|
var refs = resolvedInst.refs;
|
|
|
|
if (value === null) {
|
|
delete refs[stringRef];
|
|
} else {
|
|
refs[stringRef] = value;
|
|
}
|
|
};
|
|
|
|
ref._stringRef = stringRef;
|
|
return ref;
|
|
} else {
|
|
if (typeof mixedRef !== "string") {
|
|
throw new Error(
|
|
"Expected ref to be a function, a string, an object returned by React.createRef(), or null."
|
|
);
|
|
}
|
|
|
|
if (!element._owner) {
|
|
throw new Error(
|
|
"Element ref was specified as a string (" +
|
|
mixedRef +
|
|
") but no owner was set. This could happen for one of" +
|
|
" the following reasons:\n" +
|
|
"1. You may be adding a ref to a function component\n" +
|
|
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
|
|
"3. You have multiple copies of React loaded\n" +
|
|
"See https://reactjs.org/link/refs-must-have-owner for more information."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return mixedRef;
|
|
}
|
|
|
|
function throwOnInvalidObjectType(returnFiber, newChild) {
|
|
// $FlowFixMe[method-unbinding]
|
|
var childString = Object.prototype.toString.call(newChild);
|
|
throw new Error(
|
|
"Objects are not valid as a React child (found: " +
|
|
(childString === "[object Object]"
|
|
? "object with keys {" + Object.keys(newChild).join(", ") + "}"
|
|
: childString) +
|
|
"). " +
|
|
"If you meant to render a collection of children, use an array " +
|
|
"instead."
|
|
);
|
|
}
|
|
|
|
function warnOnFunctionType(returnFiber) {
|
|
{
|
|
var componentName = getComponentNameFromFiber(returnFiber) || "Component";
|
|
|
|
if (ownerHasFunctionTypeWarning[componentName]) {
|
|
return;
|
|
}
|
|
|
|
ownerHasFunctionTypeWarning[componentName] = true;
|
|
|
|
error(
|
|
"Functions are not valid as a React child. This may happen if " +
|
|
"you return a Component instead of <Component /> from render. " +
|
|
"Or maybe you meant to call this function rather than return it."
|
|
);
|
|
}
|
|
}
|
|
|
|
function resolveLazy(lazyType) {
|
|
var payload = lazyType._payload;
|
|
var init = lazyType._init;
|
|
return init(payload);
|
|
} // This wrapper function exists because I expect to clone the code in each path
|
|
// to be able to optimize each path individually by branching early. This needs
|
|
// a compiler or we can do it manually. Helpers that don't need this branching
|
|
// live outside of this function.
|
|
|
|
function createChildReconciler(shouldTrackSideEffects) {
|
|
function deleteChild(returnFiber, childToDelete) {
|
|
if (!shouldTrackSideEffects) {
|
|
// Noop.
|
|
return;
|
|
}
|
|
|
|
var deletions = returnFiber.deletions;
|
|
|
|
if (deletions === null) {
|
|
returnFiber.deletions = [childToDelete];
|
|
returnFiber.flags |= ChildDeletion;
|
|
} else {
|
|
deletions.push(childToDelete);
|
|
}
|
|
}
|
|
|
|
function deleteRemainingChildren(returnFiber, currentFirstChild) {
|
|
if (!shouldTrackSideEffects) {
|
|
// Noop.
|
|
return null;
|
|
} // TODO: For the shouldClone case, this could be micro-optimized a bit by
|
|
// assuming that after the first child we've already added everything.
|
|
|
|
var childToDelete = currentFirstChild;
|
|
|
|
while (childToDelete !== null) {
|
|
deleteChild(returnFiber, childToDelete);
|
|
childToDelete = childToDelete.sibling;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function mapRemainingChildren(returnFiber, currentFirstChild) {
|
|
// Add the remaining children to a temporary map so that we can find them by
|
|
// keys quickly. Implicit (null) keys get added to this set with their index
|
|
// instead.
|
|
var existingChildren = new Map();
|
|
var existingChild = currentFirstChild;
|
|
|
|
while (existingChild !== null) {
|
|
if (existingChild.key !== null) {
|
|
existingChildren.set(existingChild.key, existingChild);
|
|
} else {
|
|
existingChildren.set(existingChild.index, existingChild);
|
|
}
|
|
|
|
existingChild = existingChild.sibling;
|
|
}
|
|
|
|
return existingChildren;
|
|
}
|
|
|
|
function useFiber(fiber, pendingProps) {
|
|
// We currently set sibling to null and index to 0 here because it is easy
|
|
// to forget to do before returning it. E.g. for the single child case.
|
|
var clone = createWorkInProgress(fiber, pendingProps);
|
|
clone.index = 0;
|
|
clone.sibling = null;
|
|
return clone;
|
|
}
|
|
|
|
function placeChild(newFiber, lastPlacedIndex, newIndex) {
|
|
newFiber.index = newIndex;
|
|
|
|
if (!shouldTrackSideEffects) {
|
|
// During hydration, the useId algorithm needs to know which fibers are
|
|
// part of a list of children (arrays, iterators).
|
|
newFiber.flags |= Forked;
|
|
return lastPlacedIndex;
|
|
}
|
|
|
|
var current = newFiber.alternate;
|
|
|
|
if (current !== null) {
|
|
var oldIndex = current.index;
|
|
|
|
if (oldIndex < lastPlacedIndex) {
|
|
// This is a move.
|
|
newFiber.flags |= Placement | PlacementDEV;
|
|
return lastPlacedIndex;
|
|
} else {
|
|
// This item can stay in place.
|
|
return oldIndex;
|
|
}
|
|
} else {
|
|
// This is an insertion.
|
|
newFiber.flags |= Placement | PlacementDEV;
|
|
return lastPlacedIndex;
|
|
}
|
|
}
|
|
|
|
function placeSingleChild(newFiber) {
|
|
// This is simpler for the single child case. We only need to do a
|
|
// placement for inserting new children.
|
|
if (shouldTrackSideEffects && newFiber.alternate === null) {
|
|
newFiber.flags |= Placement | PlacementDEV;
|
|
}
|
|
|
|
return newFiber;
|
|
}
|
|
|
|
function updateTextNode(returnFiber, current, textContent, lanes) {
|
|
if (current === null || current.tag !== HostText) {
|
|
// Insert
|
|
var created = createFiberFromText(textContent, returnFiber.mode, lanes);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} else {
|
|
// Update
|
|
var existing = useFiber(current, textContent);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
}
|
|
}
|
|
|
|
function updateElement(returnFiber, current, element, lanes) {
|
|
var elementType = element.type;
|
|
|
|
if (elementType === REACT_FRAGMENT_TYPE) {
|
|
return updateFragment(
|
|
returnFiber,
|
|
current,
|
|
element.props.children,
|
|
lanes,
|
|
element.key
|
|
);
|
|
}
|
|
|
|
if (current !== null) {
|
|
if (
|
|
current.elementType === elementType || // Keep this check inline so it only runs on the false path:
|
|
isCompatibleFamilyForHotReloading(current, element) || // Lazy types should reconcile their resolved type.
|
|
// We need to do this after the Hot Reloading check above,
|
|
// because hot reloading has different semantics than prod because
|
|
// it doesn't resuspend. So we can't let the call below suspend.
|
|
(typeof elementType === "object" &&
|
|
elementType !== null &&
|
|
elementType.$$typeof === REACT_LAZY_TYPE &&
|
|
resolveLazy(elementType) === current.type)
|
|
) {
|
|
// Move based on index
|
|
var existing = useFiber(current, element.props);
|
|
existing.ref = coerceRef(returnFiber, current, element);
|
|
existing.return = returnFiber;
|
|
|
|
{
|
|
existing._debugSource = element._source;
|
|
existing._debugOwner = element._owner;
|
|
}
|
|
|
|
return existing;
|
|
}
|
|
} // Insert
|
|
|
|
var created = createFiberFromElement(element, returnFiber.mode, lanes);
|
|
created.ref = coerceRef(returnFiber, current, element);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
|
|
function updatePortal(returnFiber, current, portal, lanes) {
|
|
if (
|
|
current === null ||
|
|
current.tag !== HostPortal ||
|
|
current.stateNode.containerInfo !== portal.containerInfo ||
|
|
current.stateNode.implementation !== portal.implementation
|
|
) {
|
|
// Insert
|
|
var created = createFiberFromPortal(portal, returnFiber.mode, lanes);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} else {
|
|
// Update
|
|
var existing = useFiber(current, portal.children || []);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
}
|
|
}
|
|
|
|
function updateFragment(returnFiber, current, fragment, lanes, key) {
|
|
if (current === null || current.tag !== Fragment) {
|
|
// Insert
|
|
var created = createFiberFromFragment(
|
|
fragment,
|
|
returnFiber.mode,
|
|
lanes,
|
|
key
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} else {
|
|
// Update
|
|
var existing = useFiber(current, fragment);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
}
|
|
}
|
|
|
|
function createChild(returnFiber, newChild, lanes) {
|
|
if (
|
|
(typeof newChild === "string" && newChild !== "") ||
|
|
typeof newChild === "number"
|
|
) {
|
|
// Text nodes don't have keys. If the previous node is implicitly keyed
|
|
// we can continue to replace it without aborting even if it is not a text
|
|
// node.
|
|
var created = createFiberFromText("" + newChild, returnFiber.mode, lanes);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
|
|
if (typeof newChild === "object" && newChild !== null) {
|
|
switch (newChild.$$typeof) {
|
|
case REACT_ELEMENT_TYPE: {
|
|
var _created = createFiberFromElement(
|
|
newChild,
|
|
returnFiber.mode,
|
|
lanes
|
|
);
|
|
|
|
_created.ref = coerceRef(returnFiber, null, newChild);
|
|
_created.return = returnFiber;
|
|
return _created;
|
|
}
|
|
|
|
case REACT_PORTAL_TYPE: {
|
|
var _created2 = createFiberFromPortal(
|
|
newChild,
|
|
returnFiber.mode,
|
|
lanes
|
|
);
|
|
|
|
_created2.return = returnFiber;
|
|
return _created2;
|
|
}
|
|
|
|
case REACT_LAZY_TYPE: {
|
|
var payload = newChild._payload;
|
|
var init = newChild._init;
|
|
return createChild(returnFiber, init(payload), lanes);
|
|
}
|
|
}
|
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) {
|
|
var _created3 = createFiberFromFragment(
|
|
newChild,
|
|
returnFiber.mode,
|
|
lanes,
|
|
null
|
|
);
|
|
|
|
_created3.return = returnFiber;
|
|
return _created3;
|
|
}
|
|
|
|
throwOnInvalidObjectType(returnFiber, newChild);
|
|
}
|
|
|
|
{
|
|
if (typeof newChild === "function") {
|
|
warnOnFunctionType(returnFiber);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function updateSlot(returnFiber, oldFiber, newChild, lanes) {
|
|
// Update the fiber if the keys match, otherwise return null.
|
|
var key = oldFiber !== null ? oldFiber.key : null;
|
|
|
|
if (
|
|
(typeof newChild === "string" && newChild !== "") ||
|
|
typeof newChild === "number"
|
|
) {
|
|
// Text nodes don't have keys. If the previous node is implicitly keyed
|
|
// we can continue to replace it without aborting even if it is not a text
|
|
// node.
|
|
if (key !== null) {
|
|
return null;
|
|
}
|
|
|
|
return updateTextNode(returnFiber, oldFiber, "" + newChild, lanes);
|
|
}
|
|
|
|
if (typeof newChild === "object" && newChild !== null) {
|
|
switch (newChild.$$typeof) {
|
|
case REACT_ELEMENT_TYPE: {
|
|
if (newChild.key === key) {
|
|
return updateElement(returnFiber, oldFiber, newChild, lanes);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
case REACT_PORTAL_TYPE: {
|
|
if (newChild.key === key) {
|
|
return updatePortal(returnFiber, oldFiber, newChild, lanes);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
case REACT_LAZY_TYPE: {
|
|
var payload = newChild._payload;
|
|
var init = newChild._init;
|
|
return updateSlot(returnFiber, oldFiber, init(payload), lanes);
|
|
}
|
|
}
|
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) {
|
|
if (key !== null) {
|
|
return null;
|
|
}
|
|
|
|
return updateFragment(returnFiber, oldFiber, newChild, lanes, null);
|
|
}
|
|
|
|
throwOnInvalidObjectType(returnFiber, newChild);
|
|
}
|
|
|
|
{
|
|
if (typeof newChild === "function") {
|
|
warnOnFunctionType(returnFiber);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function updateFromMap(
|
|
existingChildren,
|
|
returnFiber,
|
|
newIdx,
|
|
newChild,
|
|
lanes
|
|
) {
|
|
if (
|
|
(typeof newChild === "string" && newChild !== "") ||
|
|
typeof newChild === "number"
|
|
) {
|
|
// Text nodes don't have keys, so we neither have to check the old nor
|
|
// new node for the key. If both are text nodes, they match.
|
|
var matchedFiber = existingChildren.get(newIdx) || null;
|
|
return updateTextNode(returnFiber, matchedFiber, "" + newChild, lanes);
|
|
}
|
|
|
|
if (typeof newChild === "object" && newChild !== null) {
|
|
switch (newChild.$$typeof) {
|
|
case REACT_ELEMENT_TYPE: {
|
|
var _matchedFiber =
|
|
existingChildren.get(
|
|
newChild.key === null ? newIdx : newChild.key
|
|
) || null;
|
|
|
|
return updateElement(returnFiber, _matchedFiber, newChild, lanes);
|
|
}
|
|
|
|
case REACT_PORTAL_TYPE: {
|
|
var _matchedFiber2 =
|
|
existingChildren.get(
|
|
newChild.key === null ? newIdx : newChild.key
|
|
) || null;
|
|
|
|
return updatePortal(returnFiber, _matchedFiber2, newChild, lanes);
|
|
}
|
|
|
|
case REACT_LAZY_TYPE:
|
|
var payload = newChild._payload;
|
|
var init = newChild._init;
|
|
return updateFromMap(
|
|
existingChildren,
|
|
returnFiber,
|
|
newIdx,
|
|
init(payload),
|
|
lanes
|
|
);
|
|
}
|
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) {
|
|
var _matchedFiber3 = existingChildren.get(newIdx) || null;
|
|
|
|
return updateFragment(
|
|
returnFiber,
|
|
_matchedFiber3,
|
|
newChild,
|
|
lanes,
|
|
null
|
|
);
|
|
}
|
|
|
|
throwOnInvalidObjectType(returnFiber, newChild);
|
|
}
|
|
|
|
{
|
|
if (typeof newChild === "function") {
|
|
warnOnFunctionType(returnFiber);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
/**
|
|
* Warns if there is a duplicate or missing key
|
|
*/
|
|
|
|
function warnOnInvalidKey(child, knownKeys, returnFiber) {
|
|
{
|
|
if (typeof child !== "object" || child === null) {
|
|
return knownKeys;
|
|
}
|
|
|
|
switch (child.$$typeof) {
|
|
case REACT_ELEMENT_TYPE:
|
|
case REACT_PORTAL_TYPE:
|
|
warnForMissingKey(child, returnFiber);
|
|
var key = child.key;
|
|
|
|
if (typeof key !== "string") {
|
|
break;
|
|
}
|
|
|
|
if (knownKeys === null) {
|
|
knownKeys = new Set();
|
|
knownKeys.add(key);
|
|
break;
|
|
}
|
|
|
|
if (!knownKeys.has(key)) {
|
|
knownKeys.add(key);
|
|
break;
|
|
}
|
|
|
|
error(
|
|
"Encountered two children with the same key, `%s`. " +
|
|
"Keys should be unique so that components maintain their identity " +
|
|
"across updates. Non-unique keys may cause children to be " +
|
|
"duplicated and/or omitted — the behavior is unsupported and " +
|
|
"could change in a future version.",
|
|
key
|
|
);
|
|
|
|
break;
|
|
|
|
case REACT_LAZY_TYPE:
|
|
var payload = child._payload;
|
|
var init = child._init;
|
|
warnOnInvalidKey(init(payload), knownKeys, returnFiber);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return knownKeys;
|
|
}
|
|
|
|
function reconcileChildrenArray(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChildren,
|
|
lanes
|
|
) {
|
|
// This algorithm can't optimize by searching from both ends since we
|
|
// don't have backpointers on fibers. I'm trying to see how far we can get
|
|
// with that model. If it ends up not being worth the tradeoffs, we can
|
|
// add it later.
|
|
// Even with a two ended optimization, we'd want to optimize for the case
|
|
// where there are few changes and brute force the comparison instead of
|
|
// going for the Map. It'd like to explore hitting that path first in
|
|
// forward-only mode and only go for the Map once we notice that we need
|
|
// lots of look ahead. This doesn't handle reversal as well as two ended
|
|
// search but that's unusual. Besides, for the two ended optimization to
|
|
// work on Iterables, we'd need to copy the whole set.
|
|
// In this first iteration, we'll just live with hitting the bad case
|
|
// (adding everything to a Map) in for every insert/move.
|
|
// If you change this code, also update reconcileChildrenIterator() which
|
|
// uses the same algorithm.
|
|
{
|
|
// First, validate keys.
|
|
var knownKeys = null;
|
|
|
|
for (var i = 0; i < newChildren.length; i++) {
|
|
var child = newChildren[i];
|
|
knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber);
|
|
}
|
|
}
|
|
|
|
var resultingFirstChild = null;
|
|
var previousNewFiber = null;
|
|
var oldFiber = currentFirstChild;
|
|
var lastPlacedIndex = 0;
|
|
var newIdx = 0;
|
|
var nextOldFiber = null;
|
|
|
|
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
|
|
if (oldFiber.index > newIdx) {
|
|
nextOldFiber = oldFiber;
|
|
oldFiber = null;
|
|
} else {
|
|
nextOldFiber = oldFiber.sibling;
|
|
}
|
|
|
|
var newFiber = updateSlot(
|
|
returnFiber,
|
|
oldFiber,
|
|
newChildren[newIdx],
|
|
lanes
|
|
);
|
|
|
|
if (newFiber === null) {
|
|
// TODO: This breaks on empty slots like null children. That's
|
|
// unfortunate because it triggers the slow path all the time. We need
|
|
// a better way to communicate whether this was a miss or null,
|
|
// boolean, undefined, etc.
|
|
if (oldFiber === null) {
|
|
oldFiber = nextOldFiber;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (shouldTrackSideEffects) {
|
|
if (oldFiber && newFiber.alternate === null) {
|
|
// We matched the slot, but we didn't reuse the existing fiber, so we
|
|
// need to delete the existing child.
|
|
deleteChild(returnFiber, oldFiber);
|
|
}
|
|
}
|
|
|
|
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
|
|
|
|
if (previousNewFiber === null) {
|
|
// TODO: Move out of the loop. This only happens for the first run.
|
|
resultingFirstChild = newFiber;
|
|
} else {
|
|
// TODO: Defer siblings if we're not at the right index for this slot.
|
|
// I.e. if we had null values before, then we want to defer this
|
|
// for each null value. However, we also don't want to call updateSlot
|
|
// with the previous one.
|
|
previousNewFiber.sibling = newFiber;
|
|
}
|
|
|
|
previousNewFiber = newFiber;
|
|
oldFiber = nextOldFiber;
|
|
}
|
|
|
|
if (newIdx === newChildren.length) {
|
|
// We've reached the end of the new children. We can delete the rest.
|
|
deleteRemainingChildren(returnFiber, oldFiber);
|
|
|
|
if (getIsHydrating()) {
|
|
var numberOfForks = newIdx;
|
|
pushTreeFork(returnFiber, numberOfForks);
|
|
}
|
|
|
|
return resultingFirstChild;
|
|
}
|
|
|
|
if (oldFiber === null) {
|
|
// If we don't have any more existing children we can choose a fast path
|
|
// since the rest will all be insertions.
|
|
for (; newIdx < newChildren.length; newIdx++) {
|
|
var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
|
|
|
|
if (_newFiber === null) {
|
|
continue;
|
|
}
|
|
|
|
lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx);
|
|
|
|
if (previousNewFiber === null) {
|
|
// TODO: Move out of the loop. This only happens for the first run.
|
|
resultingFirstChild = _newFiber;
|
|
} else {
|
|
previousNewFiber.sibling = _newFiber;
|
|
}
|
|
|
|
previousNewFiber = _newFiber;
|
|
}
|
|
|
|
if (getIsHydrating()) {
|
|
var _numberOfForks = newIdx;
|
|
pushTreeFork(returnFiber, _numberOfForks);
|
|
}
|
|
|
|
return resultingFirstChild;
|
|
} // Add all children to a key map for quick lookups.
|
|
|
|
var existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves.
|
|
|
|
for (; newIdx < newChildren.length; newIdx++) {
|
|
var _newFiber2 = updateFromMap(
|
|
existingChildren,
|
|
returnFiber,
|
|
newIdx,
|
|
newChildren[newIdx],
|
|
lanes
|
|
);
|
|
|
|
if (_newFiber2 !== null) {
|
|
if (shouldTrackSideEffects) {
|
|
if (_newFiber2.alternate !== null) {
|
|
// The new fiber is a work in progress, but if there exists a
|
|
// current, that means that we reused the fiber. We need to delete
|
|
// it from the child list so that we don't add it to the deletion
|
|
// list.
|
|
existingChildren.delete(
|
|
_newFiber2.key === null ? newIdx : _newFiber2.key
|
|
);
|
|
}
|
|
}
|
|
|
|
lastPlacedIndex = placeChild(_newFiber2, lastPlacedIndex, newIdx);
|
|
|
|
if (previousNewFiber === null) {
|
|
resultingFirstChild = _newFiber2;
|
|
} else {
|
|
previousNewFiber.sibling = _newFiber2;
|
|
}
|
|
|
|
previousNewFiber = _newFiber2;
|
|
}
|
|
}
|
|
|
|
if (shouldTrackSideEffects) {
|
|
// Any existing children that weren't consumed above were deleted. We need
|
|
// to add them to the deletion list.
|
|
existingChildren.forEach(function(child) {
|
|
return deleteChild(returnFiber, child);
|
|
});
|
|
}
|
|
|
|
if (getIsHydrating()) {
|
|
var _numberOfForks2 = newIdx;
|
|
pushTreeFork(returnFiber, _numberOfForks2);
|
|
}
|
|
|
|
return resultingFirstChild;
|
|
}
|
|
|
|
function reconcileChildrenIterator(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChildrenIterable,
|
|
lanes
|
|
) {
|
|
// This is the same implementation as reconcileChildrenArray(),
|
|
// but using the iterator instead.
|
|
var iteratorFn = getIteratorFn(newChildrenIterable);
|
|
|
|
if (typeof iteratorFn !== "function") {
|
|
throw new Error(
|
|
"An object is not an iterable. This error is likely caused by a bug in " +
|
|
"React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
{
|
|
// We don't support rendering Generators because it's a mutation.
|
|
// See https://github.com/facebook/react/issues/12995
|
|
if (
|
|
typeof Symbol === "function" && // $FlowFixMe Flow doesn't know about toStringTag
|
|
newChildrenIterable[Symbol.toStringTag] === "Generator"
|
|
) {
|
|
if (!didWarnAboutGenerators) {
|
|
error(
|
|
"Using Generators as children is unsupported and will likely yield " +
|
|
"unexpected results because enumerating a generator mutates it. " +
|
|
"You may convert it to an array with `Array.from()` or the " +
|
|
"`[...spread]` operator before rendering. Keep in mind " +
|
|
"you might need to polyfill these features for older browsers."
|
|
);
|
|
}
|
|
|
|
didWarnAboutGenerators = true;
|
|
} // Warn about using Maps as children
|
|
|
|
if (newChildrenIterable.entries === iteratorFn) {
|
|
if (!didWarnAboutMaps) {
|
|
error(
|
|
"Using Maps as children is not supported. " +
|
|
"Use an array of keyed ReactElements instead."
|
|
);
|
|
}
|
|
|
|
didWarnAboutMaps = true;
|
|
} // First, validate keys.
|
|
// We'll get a different iterator later for the main pass.
|
|
|
|
var _newChildren = iteratorFn.call(newChildrenIterable);
|
|
|
|
if (_newChildren) {
|
|
var knownKeys = null;
|
|
|
|
var _step = _newChildren.next();
|
|
|
|
for (; !_step.done; _step = _newChildren.next()) {
|
|
var child = _step.value;
|
|
knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber);
|
|
}
|
|
}
|
|
}
|
|
|
|
var newChildren = iteratorFn.call(newChildrenIterable);
|
|
|
|
if (newChildren == null) {
|
|
throw new Error("An iterable object provided no iterator.");
|
|
}
|
|
|
|
var resultingFirstChild = null;
|
|
var previousNewFiber = null;
|
|
var oldFiber = currentFirstChild;
|
|
var lastPlacedIndex = 0;
|
|
var newIdx = 0;
|
|
var nextOldFiber = null;
|
|
var step = newChildren.next();
|
|
|
|
for (
|
|
;
|
|
oldFiber !== null && !step.done;
|
|
newIdx++, step = newChildren.next()
|
|
) {
|
|
if (oldFiber.index > newIdx) {
|
|
nextOldFiber = oldFiber;
|
|
oldFiber = null;
|
|
} else {
|
|
nextOldFiber = oldFiber.sibling;
|
|
}
|
|
|
|
var newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);
|
|
|
|
if (newFiber === null) {
|
|
// TODO: This breaks on empty slots like null children. That's
|
|
// unfortunate because it triggers the slow path all the time. We need
|
|
// a better way to communicate whether this was a miss or null,
|
|
// boolean, undefined, etc.
|
|
if (oldFiber === null) {
|
|
oldFiber = nextOldFiber;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (shouldTrackSideEffects) {
|
|
if (oldFiber && newFiber.alternate === null) {
|
|
// We matched the slot, but we didn't reuse the existing fiber, so we
|
|
// need to delete the existing child.
|
|
deleteChild(returnFiber, oldFiber);
|
|
}
|
|
}
|
|
|
|
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
|
|
|
|
if (previousNewFiber === null) {
|
|
// TODO: Move out of the loop. This only happens for the first run.
|
|
resultingFirstChild = newFiber;
|
|
} else {
|
|
// TODO: Defer siblings if we're not at the right index for this slot.
|
|
// I.e. if we had null values before, then we want to defer this
|
|
// for each null value. However, we also don't want to call updateSlot
|
|
// with the previous one.
|
|
previousNewFiber.sibling = newFiber;
|
|
}
|
|
|
|
previousNewFiber = newFiber;
|
|
oldFiber = nextOldFiber;
|
|
}
|
|
|
|
if (step.done) {
|
|
// We've reached the end of the new children. We can delete the rest.
|
|
deleteRemainingChildren(returnFiber, oldFiber);
|
|
|
|
if (getIsHydrating()) {
|
|
var numberOfForks = newIdx;
|
|
pushTreeFork(returnFiber, numberOfForks);
|
|
}
|
|
|
|
return resultingFirstChild;
|
|
}
|
|
|
|
if (oldFiber === null) {
|
|
// If we don't have any more existing children we can choose a fast path
|
|
// since the rest will all be insertions.
|
|
for (; !step.done; newIdx++, step = newChildren.next()) {
|
|
var _newFiber3 = createChild(returnFiber, step.value, lanes);
|
|
|
|
if (_newFiber3 === null) {
|
|
continue;
|
|
}
|
|
|
|
lastPlacedIndex = placeChild(_newFiber3, lastPlacedIndex, newIdx);
|
|
|
|
if (previousNewFiber === null) {
|
|
// TODO: Move out of the loop. This only happens for the first run.
|
|
resultingFirstChild = _newFiber3;
|
|
} else {
|
|
previousNewFiber.sibling = _newFiber3;
|
|
}
|
|
|
|
previousNewFiber = _newFiber3;
|
|
}
|
|
|
|
if (getIsHydrating()) {
|
|
var _numberOfForks3 = newIdx;
|
|
pushTreeFork(returnFiber, _numberOfForks3);
|
|
}
|
|
|
|
return resultingFirstChild;
|
|
} // Add all children to a key map for quick lookups.
|
|
|
|
var existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves.
|
|
|
|
for (; !step.done; newIdx++, step = newChildren.next()) {
|
|
var _newFiber4 = updateFromMap(
|
|
existingChildren,
|
|
returnFiber,
|
|
newIdx,
|
|
step.value,
|
|
lanes
|
|
);
|
|
|
|
if (_newFiber4 !== null) {
|
|
if (shouldTrackSideEffects) {
|
|
if (_newFiber4.alternate !== null) {
|
|
// The new fiber is a work in progress, but if there exists a
|
|
// current, that means that we reused the fiber. We need to delete
|
|
// it from the child list so that we don't add it to the deletion
|
|
// list.
|
|
existingChildren.delete(
|
|
_newFiber4.key === null ? newIdx : _newFiber4.key
|
|
);
|
|
}
|
|
}
|
|
|
|
lastPlacedIndex = placeChild(_newFiber4, lastPlacedIndex, newIdx);
|
|
|
|
if (previousNewFiber === null) {
|
|
resultingFirstChild = _newFiber4;
|
|
} else {
|
|
previousNewFiber.sibling = _newFiber4;
|
|
}
|
|
|
|
previousNewFiber = _newFiber4;
|
|
}
|
|
}
|
|
|
|
if (shouldTrackSideEffects) {
|
|
// Any existing children that weren't consumed above were deleted. We need
|
|
// to add them to the deletion list.
|
|
existingChildren.forEach(function(child) {
|
|
return deleteChild(returnFiber, child);
|
|
});
|
|
}
|
|
|
|
if (getIsHydrating()) {
|
|
var _numberOfForks4 = newIdx;
|
|
pushTreeFork(returnFiber, _numberOfForks4);
|
|
}
|
|
|
|
return resultingFirstChild;
|
|
}
|
|
|
|
function reconcileSingleTextNode(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
textContent,
|
|
lanes
|
|
) {
|
|
// There's no need to check for keys on text nodes since we don't have a
|
|
// way to define them.
|
|
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
|
|
// We already have an existing node so let's just update it and delete
|
|
// the rest.
|
|
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
|
|
var existing = useFiber(currentFirstChild, textContent);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
} // The existing first child is not a text node so we need to create one
|
|
// and delete the existing ones.
|
|
|
|
deleteRemainingChildren(returnFiber, currentFirstChild);
|
|
var created = createFiberFromText(textContent, returnFiber.mode, lanes);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
|
|
function reconcileSingleElement(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
element,
|
|
lanes
|
|
) {
|
|
var key = element.key;
|
|
var child = currentFirstChild;
|
|
|
|
while (child !== null) {
|
|
// TODO: If key === null and child.key === null, then this only applies to
|
|
// the first item in the list.
|
|
if (child.key === key) {
|
|
var elementType = element.type;
|
|
|
|
if (elementType === REACT_FRAGMENT_TYPE) {
|
|
if (child.tag === Fragment) {
|
|
deleteRemainingChildren(returnFiber, child.sibling);
|
|
var existing = useFiber(child, element.props.children);
|
|
existing.return = returnFiber;
|
|
|
|
{
|
|
existing._debugSource = element._source;
|
|
existing._debugOwner = element._owner;
|
|
}
|
|
|
|
return existing;
|
|
}
|
|
} else {
|
|
if (
|
|
child.elementType === elementType || // Keep this check inline so it only runs on the false path:
|
|
isCompatibleFamilyForHotReloading(child, element) || // Lazy types should reconcile their resolved type.
|
|
// We need to do this after the Hot Reloading check above,
|
|
// because hot reloading has different semantics than prod because
|
|
// it doesn't resuspend. So we can't let the call below suspend.
|
|
(typeof elementType === "object" &&
|
|
elementType !== null &&
|
|
elementType.$$typeof === REACT_LAZY_TYPE &&
|
|
resolveLazy(elementType) === child.type)
|
|
) {
|
|
deleteRemainingChildren(returnFiber, child.sibling);
|
|
|
|
var _existing = useFiber(child, element.props);
|
|
|
|
_existing.ref = coerceRef(returnFiber, child, element);
|
|
_existing.return = returnFiber;
|
|
|
|
{
|
|
_existing._debugSource = element._source;
|
|
_existing._debugOwner = element._owner;
|
|
}
|
|
|
|
return _existing;
|
|
}
|
|
} // Didn't match.
|
|
|
|
deleteRemainingChildren(returnFiber, child);
|
|
break;
|
|
} else {
|
|
deleteChild(returnFiber, child);
|
|
}
|
|
|
|
child = child.sibling;
|
|
}
|
|
|
|
if (element.type === REACT_FRAGMENT_TYPE) {
|
|
var created = createFiberFromFragment(
|
|
element.props.children,
|
|
returnFiber.mode,
|
|
lanes,
|
|
element.key
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} else {
|
|
var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
|
|
|
|
_created4.ref = coerceRef(returnFiber, currentFirstChild, element);
|
|
_created4.return = returnFiber;
|
|
return _created4;
|
|
}
|
|
}
|
|
|
|
function reconcileSinglePortal(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
portal,
|
|
lanes
|
|
) {
|
|
var key = portal.key;
|
|
var child = currentFirstChild;
|
|
|
|
while (child !== null) {
|
|
// TODO: If key === null and child.key === null, then this only applies to
|
|
// the first item in the list.
|
|
if (child.key === key) {
|
|
if (
|
|
child.tag === HostPortal &&
|
|
child.stateNode.containerInfo === portal.containerInfo &&
|
|
child.stateNode.implementation === portal.implementation
|
|
) {
|
|
deleteRemainingChildren(returnFiber, child.sibling);
|
|
var existing = useFiber(child, portal.children || []);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
} else {
|
|
deleteRemainingChildren(returnFiber, child);
|
|
break;
|
|
}
|
|
} else {
|
|
deleteChild(returnFiber, child);
|
|
}
|
|
|
|
child = child.sibling;
|
|
}
|
|
|
|
var created = createFiberFromPortal(portal, returnFiber.mode, lanes);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} // This API will tag the children with the side-effect of the reconciliation
|
|
// itself. They will be added to the side-effect list as we pass through the
|
|
// children and the parent.
|
|
|
|
function reconcileChildFibers(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
lanes
|
|
) {
|
|
// This function is not recursive.
|
|
// If the top level item is an array, we treat it as a set of children,
|
|
// not as a fragment. Nested arrays on the other hand will be treated as
|
|
// fragment nodes. Recursion happens at the normal flow.
|
|
// Handle top level unkeyed fragments as if they were arrays.
|
|
// This leads to an ambiguity between <>{[...]}</> and <>...</>.
|
|
// We treat the ambiguous cases above the same.
|
|
var isUnkeyedTopLevelFragment =
|
|
typeof newChild === "object" &&
|
|
newChild !== null &&
|
|
newChild.type === REACT_FRAGMENT_TYPE &&
|
|
newChild.key === null;
|
|
|
|
if (isUnkeyedTopLevelFragment) {
|
|
newChild = newChild.props.children;
|
|
} // Handle object types
|
|
|
|
if (typeof newChild === "object" && newChild !== null) {
|
|
switch (newChild.$$typeof) {
|
|
case REACT_ELEMENT_TYPE:
|
|
return placeSingleChild(
|
|
reconcileSingleElement(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
lanes
|
|
)
|
|
);
|
|
|
|
case REACT_PORTAL_TYPE:
|
|
return placeSingleChild(
|
|
reconcileSinglePortal(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
lanes
|
|
)
|
|
);
|
|
|
|
case REACT_LAZY_TYPE:
|
|
var payload = newChild._payload;
|
|
var init = newChild._init; // TODO: This function is supposed to be non-recursive.
|
|
|
|
return reconcileChildFibers(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
init(payload),
|
|
lanes
|
|
);
|
|
}
|
|
|
|
if (isArray(newChild)) {
|
|
return reconcileChildrenArray(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
lanes
|
|
);
|
|
}
|
|
|
|
if (getIteratorFn(newChild)) {
|
|
return reconcileChildrenIterator(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
lanes
|
|
);
|
|
}
|
|
|
|
throwOnInvalidObjectType(returnFiber, newChild);
|
|
}
|
|
|
|
if (
|
|
(typeof newChild === "string" && newChild !== "") ||
|
|
typeof newChild === "number"
|
|
) {
|
|
return placeSingleChild(
|
|
reconcileSingleTextNode(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
"" + newChild,
|
|
lanes
|
|
)
|
|
);
|
|
}
|
|
|
|
{
|
|
if (typeof newChild === "function") {
|
|
warnOnFunctionType(returnFiber);
|
|
}
|
|
} // Remaining cases are all treated as empty.
|
|
|
|
return deleteRemainingChildren(returnFiber, currentFirstChild);
|
|
}
|
|
|
|
return reconcileChildFibers;
|
|
}
|
|
|
|
var reconcileChildFibers = createChildReconciler(true);
|
|
var mountChildFibers = createChildReconciler(false);
|
|
function cloneChildFibers(current, workInProgress) {
|
|
if (current !== null && workInProgress.child !== current.child) {
|
|
throw new Error("Resuming work not yet implemented.");
|
|
}
|
|
|
|
if (workInProgress.child === null) {
|
|
return;
|
|
}
|
|
|
|
var currentChild = workInProgress.child;
|
|
var newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
|
|
workInProgress.child = newChild;
|
|
newChild.return = workInProgress;
|
|
|
|
while (currentChild.sibling !== null) {
|
|
currentChild = currentChild.sibling;
|
|
newChild = newChild.sibling = createWorkInProgress(
|
|
currentChild,
|
|
currentChild.pendingProps
|
|
);
|
|
newChild.return = workInProgress;
|
|
}
|
|
|
|
newChild.sibling = null;
|
|
} // Reset a workInProgress child set to prepare it for a second pass.
|
|
|
|
function resetChildFibers(workInProgress, lanes) {
|
|
var child = workInProgress.child;
|
|
|
|
while (child !== null) {
|
|
resetWorkInProgress(child, lanes);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
// TODO: This isn't being used yet, but it's intended to replace the
|
|
// InvisibleParentContext that is currently managed by SuspenseContext.
|
|
|
|
var currentTreeHiddenStackCursor = createCursor(null);
|
|
var prevRenderLanesStackCursor = createCursor(NoLanes);
|
|
function pushHiddenContext(fiber, context) {
|
|
var prevRenderLanes = getRenderLanes();
|
|
push(prevRenderLanesStackCursor, prevRenderLanes, fiber);
|
|
push(currentTreeHiddenStackCursor, context, fiber); // When rendering a subtree that's currently hidden, we must include all
|
|
// lanes that would have rendered if the hidden subtree hadn't been deferred.
|
|
// That is, in order to reveal content from hidden -> visible, we must commit
|
|
// all the updates that we skipped when we originally hid the tree.
|
|
|
|
setRenderLanes(mergeLanes(prevRenderLanes, context.baseLanes));
|
|
}
|
|
function reuseHiddenContextOnStack(fiber) {
|
|
// This subtree is not currently hidden, so we don't need to add any lanes
|
|
// to the render lanes. But we still need to push something to avoid a
|
|
// context mismatch. Reuse the existing context on the stack.
|
|
push(prevRenderLanesStackCursor, getRenderLanes(), fiber);
|
|
push(
|
|
currentTreeHiddenStackCursor,
|
|
currentTreeHiddenStackCursor.current,
|
|
fiber
|
|
);
|
|
}
|
|
function popHiddenContext(fiber) {
|
|
// Restore the previous render lanes from the stack
|
|
setRenderLanes(prevRenderLanesStackCursor.current);
|
|
pop(currentTreeHiddenStackCursor, fiber);
|
|
pop(prevRenderLanesStackCursor, fiber);
|
|
}
|
|
function isCurrentTreeHidden() {
|
|
return currentTreeHiddenStackCursor.current !== null;
|
|
}
|
|
|
|
// suspends, i.e. it's the nearest `catch` block on the stack.
|
|
|
|
var suspenseHandlerStackCursor = createCursor(null); // Represents the outermost boundary that is not visible in the current tree.
|
|
// Everything above this is the "shell". When this is null, it means we're
|
|
// rendering in the shell of the app. If it's non-null, it means we're rendering
|
|
// deeper than the shell, inside a new tree that wasn't already visible.
|
|
//
|
|
// The main way we use this concept is to determine whether showing a fallback
|
|
// would result in a desirable or undesirable loading state. Activing a fallback
|
|
// in the shell is considered an undersirable loading state, because it would
|
|
// mean hiding visible (albeit stale) content in the current tree — we prefer to
|
|
// show the stale content, rather than switch to a fallback. But showing a
|
|
// fallback in a new tree is fine, because there's no stale content to
|
|
// prefer instead.
|
|
|
|
var shellBoundary = null;
|
|
function getShellBoundary() {
|
|
return shellBoundary;
|
|
}
|
|
function pushPrimaryTreeSuspenseHandler(handler) {
|
|
// TODO: Pass as argument
|
|
var current = handler.alternate;
|
|
var props = handler.pendingProps; // Experimental feature: Some Suspense boundaries are marked as having an
|
|
// undesirable fallback state. These have special behavior where we only
|
|
// activate the fallback if there's no other boundary on the stack that we can
|
|
// use instead.
|
|
|
|
if (
|
|
props.unstable_avoidThisFallback === true && // If an avoided boundary is already visible, it behaves identically to
|
|
// a regular Suspense boundary.
|
|
(current === null || isCurrentTreeHidden())
|
|
) {
|
|
if (shellBoundary === null) {
|
|
// We're rendering in the shell. There's no parent Suspense boundary that
|
|
// can provide a desirable fallback state. We'll use this boundary.
|
|
push(suspenseHandlerStackCursor, handler, handler); // However, because this is not a desirable fallback, the children are
|
|
// still considered part of the shell. So we intentionally don't assign
|
|
// to `shellBoundary`.
|
|
} else {
|
|
// There's already a parent Suspense boundary that can provide a desirable
|
|
// fallback state. Prefer that one.
|
|
var handlerOnStack = suspenseHandlerStackCursor.current;
|
|
push(suspenseHandlerStackCursor, handlerOnStack, handler);
|
|
}
|
|
|
|
return;
|
|
} // TODO: If the parent Suspense handler already suspended, there's no reason
|
|
// to push a nested Suspense handler, because it will get replaced by the
|
|
// outer fallback, anyway. Consider this as a future optimization.
|
|
|
|
push(suspenseHandlerStackCursor, handler, handler);
|
|
|
|
if (shellBoundary === null) {
|
|
if (current === null || isCurrentTreeHidden()) {
|
|
// This boundary is not visible in the current UI.
|
|
shellBoundary = handler;
|
|
} else {
|
|
var prevState = current.memoizedState;
|
|
|
|
if (prevState !== null) {
|
|
// This boundary is showing a fallback in the current UI.
|
|
shellBoundary = handler;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function pushFallbackTreeSuspenseHandler(fiber) {
|
|
// We're about to render the fallback. If something in the fallback suspends,
|
|
// it's akin to throwing inside of a `catch` block. This boundary should not
|
|
// capture. Reuse the existing handler on the stack.
|
|
reuseSuspenseHandlerOnStack(fiber);
|
|
}
|
|
function pushOffscreenSuspenseHandler(fiber) {
|
|
if (fiber.tag === OffscreenComponent) {
|
|
push(suspenseHandlerStackCursor, fiber, fiber);
|
|
|
|
if (shellBoundary !== null);
|
|
else {
|
|
var current = fiber.alternate;
|
|
|
|
if (current !== null) {
|
|
var prevState = current.memoizedState;
|
|
|
|
if (prevState !== null) {
|
|
// This is the first boundary in the stack that's already showing
|
|
// a fallback. So everything outside is considered the shell.
|
|
shellBoundary = fiber;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// This is a LegacyHidden component.
|
|
reuseSuspenseHandlerOnStack(fiber);
|
|
}
|
|
}
|
|
function reuseSuspenseHandlerOnStack(fiber) {
|
|
push(suspenseHandlerStackCursor, getSuspenseHandler(), fiber);
|
|
}
|
|
function getSuspenseHandler() {
|
|
return suspenseHandlerStackCursor.current;
|
|
}
|
|
function popSuspenseHandler(fiber) {
|
|
pop(suspenseHandlerStackCursor, fiber);
|
|
|
|
if (shellBoundary === fiber) {
|
|
// Popping back into the shell.
|
|
shellBoundary = null;
|
|
}
|
|
} // SuspenseList context
|
|
// TODO: Move to a separate module? We may change the SuspenseList
|
|
// implementation to hide/show in the commit phase, anyway.
|
|
|
|
var DefaultSuspenseContext = 0;
|
|
var SubtreeSuspenseContextMask = 1; // ForceSuspenseFallback can be used by SuspenseList to force newly added
|
|
// items into their fallback state during one of the render passes.
|
|
|
|
var ForceSuspenseFallback = 2;
|
|
var suspenseStackCursor = createCursor(DefaultSuspenseContext);
|
|
function hasSuspenseListContext(parentContext, flag) {
|
|
return (parentContext & flag) !== 0;
|
|
}
|
|
function setDefaultShallowSuspenseListContext(parentContext) {
|
|
return parentContext & SubtreeSuspenseContextMask;
|
|
}
|
|
function setShallowSuspenseListContext(parentContext, shallowContext) {
|
|
return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
|
|
}
|
|
function pushSuspenseListContext(fiber, newContext) {
|
|
push(suspenseStackCursor, newContext, fiber);
|
|
}
|
|
function popSuspenseListContext(fiber) {
|
|
pop(suspenseStackCursor, fiber);
|
|
}
|
|
|
|
// A non-null SuspenseState means that it is blocked for one reason or another.
|
|
// - A non-null dehydrated field means it's blocked pending hydration.
|
|
// - A non-null dehydrated field can use isSuspenseInstancePending or
|
|
// isSuspenseInstanceFallback to query the reason for being dehydrated.
|
|
// - A null dehydrated field means it's blocked by something suspending and
|
|
// we're currently showing a fallback instead.
|
|
|
|
function findFirstSuspended(row) {
|
|
var node = row;
|
|
|
|
while (node !== null) {
|
|
if (node.tag === SuspenseComponent) {
|
|
var state = node.memoizedState;
|
|
|
|
if (state !== null) {
|
|
var dehydrated = state.dehydrated;
|
|
|
|
if (
|
|
dehydrated === null ||
|
|
isSuspenseInstancePending(dehydrated) ||
|
|
isSuspenseInstanceFallback(dehydrated)
|
|
) {
|
|
return node;
|
|
}
|
|
}
|
|
} else if (
|
|
node.tag === SuspenseListComponent && // revealOrder undefined can't be trusted because it don't
|
|
// keep track of whether it suspended or not.
|
|
node.memoizedProps.revealOrder !== undefined
|
|
) {
|
|
var didSuspend = (node.flags & DidCapture) !== NoFlags;
|
|
|
|
if (didSuspend) {
|
|
return node;
|
|
}
|
|
} else if (node.child !== null) {
|
|
node.child.return = node;
|
|
node = node.child;
|
|
continue;
|
|
}
|
|
|
|
if (node === row) {
|
|
return null;
|
|
}
|
|
|
|
while (node.sibling === null) {
|
|
if (node.return === null || node.return === row) {
|
|
return null;
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
|
|
node.sibling.return = node.return;
|
|
node = node.sibling;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
var NoFlags$1 =
|
|
/* */
|
|
0; // Represents whether effect should fire.
|
|
|
|
var HasEffect =
|
|
/* */
|
|
1; // Represents the phase in which the effect (not the clean-up) fires.
|
|
|
|
var Insertion =
|
|
/* */
|
|
2;
|
|
var Layout =
|
|
/* */
|
|
4;
|
|
var Passive$1 =
|
|
/* */
|
|
8;
|
|
|
|
// and should be reset before starting a new render.
|
|
// This tracks which mutable sources need to be reset after a render.
|
|
|
|
var workInProgressSources = [];
|
|
var rendererSigil;
|
|
|
|
{
|
|
// Used to detect multiple renderers using the same mutable source.
|
|
rendererSigil = {};
|
|
}
|
|
|
|
function markSourceAsDirty(mutableSource) {
|
|
workInProgressSources.push(mutableSource);
|
|
}
|
|
function resetWorkInProgressVersions() {
|
|
for (var i = 0; i < workInProgressSources.length; i++) {
|
|
var mutableSource = workInProgressSources[i];
|
|
|
|
{
|
|
mutableSource._workInProgressVersionPrimary = null;
|
|
}
|
|
}
|
|
|
|
workInProgressSources.length = 0;
|
|
}
|
|
function getWorkInProgressVersion(mutableSource) {
|
|
{
|
|
return mutableSource._workInProgressVersionPrimary;
|
|
}
|
|
}
|
|
function setWorkInProgressVersion(mutableSource, version) {
|
|
{
|
|
mutableSource._workInProgressVersionPrimary = version;
|
|
}
|
|
|
|
workInProgressSources.push(mutableSource);
|
|
}
|
|
function warnAboutMultipleRenderersDEV(mutableSource) {
|
|
{
|
|
{
|
|
if (mutableSource._currentPrimaryRenderer == null) {
|
|
mutableSource._currentPrimaryRenderer = rendererSigil;
|
|
} else if (mutableSource._currentPrimaryRenderer !== rendererSigil) {
|
|
error(
|
|
"Detected multiple renderers concurrently rendering the " +
|
|
"same mutable source. This is currently unsupported."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} // Eager reads the version of a mutable source and stores it on the root.
|
|
// This ensures that the version used for server rendering matches the one
|
|
// that is eventually read during hydration.
|
|
// If they don't match there's a potential tear and a full deopt render is required.
|
|
|
|
function registerMutableSourceForHydration(root, mutableSource) {
|
|
var getVersion = mutableSource._getVersion;
|
|
var version = getVersion(mutableSource._source); // TODO Clear this data once all pending hydration work is finished.
|
|
// Retaining it forever may interfere with GC.
|
|
|
|
if (root.mutableSourceEagerHydrationData == null) {
|
|
root.mutableSourceEagerHydrationData = [mutableSource, version];
|
|
} else {
|
|
root.mutableSourceEagerHydrationData.push(mutableSource, version);
|
|
}
|
|
}
|
|
|
|
var ReactCurrentActQueue = ReactSharedInternals.ReactCurrentActQueue; // An error that is thrown (e.g. by `use`) to trigger Suspense. If we
|
|
// detect this is caught by userspace, we'll log a warning in development.
|
|
|
|
var SuspenseException = new Error(
|
|
"Suspense Exception: This is not a real error! It's an implementation " +
|
|
"detail of `use` to interrupt the current render. You must either " +
|
|
"rethrow it immediately, or move the `use` call outside of the " +
|
|
"`try/catch` block. Capturing without rethrowing will lead to " +
|
|
"unexpected behavior.\n\n" +
|
|
"To handle async errors, wrap your component in an error boundary, or " +
|
|
"call the promise's `.catch` method and pass the result to `use`"
|
|
);
|
|
function createThenableState() {
|
|
// The ThenableState is created the first time a component suspends. If it
|
|
// suspends again, we'll reuse the same state.
|
|
return [];
|
|
}
|
|
function isThenableResolved(thenable) {
|
|
var status = thenable.status;
|
|
return status === "fulfilled" || status === "rejected";
|
|
}
|
|
|
|
function noop$1() {}
|
|
|
|
function trackUsedThenable(thenableState, thenable, index) {
|
|
if (ReactCurrentActQueue.current !== null) {
|
|
ReactCurrentActQueue.didUsePromise = true;
|
|
}
|
|
|
|
var previous = thenableState[index];
|
|
|
|
if (previous === undefined) {
|
|
thenableState.push(thenable);
|
|
} else {
|
|
if (previous !== thenable) {
|
|
// Reuse the previous thenable, and drop the new one. We can assume
|
|
// they represent the same value, because components are idempotent.
|
|
// Avoid an unhandled rejection errors for the Promises that we'll
|
|
// intentionally ignore.
|
|
thenable.then(noop$1, noop$1);
|
|
thenable = previous;
|
|
}
|
|
} // We use an expando to track the status and result of a thenable so that we
|
|
// can synchronously unwrap the value. Think of this as an extension of the
|
|
// Promise API, or a custom interface that is a superset of Thenable.
|
|
//
|
|
// If the thenable doesn't have a status, set it to "pending" and attach
|
|
// a listener that will update its status and result when it resolves.
|
|
|
|
switch (thenable.status) {
|
|
case "fulfilled": {
|
|
var fulfilledValue = thenable.value;
|
|
return fulfilledValue;
|
|
}
|
|
|
|
case "rejected": {
|
|
var rejectedError = thenable.reason;
|
|
throw rejectedError;
|
|
}
|
|
|
|
default: {
|
|
if (typeof thenable.status === "string");
|
|
else {
|
|
var pendingThenable = thenable;
|
|
pendingThenable.status = "pending";
|
|
pendingThenable.then(
|
|
function(fulfilledValue) {
|
|
if (thenable.status === "pending") {
|
|
var fulfilledThenable = thenable;
|
|
fulfilledThenable.status = "fulfilled";
|
|
fulfilledThenable.value = fulfilledValue;
|
|
}
|
|
},
|
|
function(error) {
|
|
if (thenable.status === "pending") {
|
|
var rejectedThenable = thenable;
|
|
rejectedThenable.status = "rejected";
|
|
rejectedThenable.reason = error;
|
|
}
|
|
}
|
|
); // Check one more time in case the thenable resolved synchronously
|
|
|
|
switch (thenable.status) {
|
|
case "fulfilled": {
|
|
var fulfilledThenable = thenable;
|
|
return fulfilledThenable.value;
|
|
}
|
|
|
|
case "rejected": {
|
|
var rejectedThenable = thenable;
|
|
throw rejectedThenable.reason;
|
|
}
|
|
}
|
|
} // Suspend.
|
|
//
|
|
// Throwing here is an implementation detail that allows us to unwind the
|
|
// call stack. But we shouldn't allow it to leak into userspace. Throw an
|
|
// opaque placeholder value instead of the actual thenable. If it doesn't
|
|
// get captured by the work loop, log a warning, because that means
|
|
// something in userspace must have caught it.
|
|
|
|
suspendedThenable = thenable;
|
|
|
|
{
|
|
needsToResetSuspendedThenableDEV = true;
|
|
}
|
|
|
|
throw SuspenseException;
|
|
}
|
|
}
|
|
} // This is used to track the actual thenable that suspended so it can be
|
|
// passed to the rest of the Suspense implementation — which, for historical
|
|
// reasons, expects to receive a thenable.
|
|
|
|
var suspendedThenable = null;
|
|
var needsToResetSuspendedThenableDEV = false;
|
|
function getSuspendedThenable() {
|
|
// This is called right after `use` suspends by throwing an exception. `use`
|
|
// throws an opaque value instead of the thenable itself so that it can't be
|
|
// caught in userspace. Then the work loop accesses the actual thenable using
|
|
// this function.
|
|
if (suspendedThenable === null) {
|
|
throw new Error(
|
|
"Expected a suspended thenable. This is a bug in React. Please file " +
|
|
"an issue."
|
|
);
|
|
}
|
|
|
|
var thenable = suspendedThenable;
|
|
suspendedThenable = null;
|
|
|
|
{
|
|
needsToResetSuspendedThenableDEV = false;
|
|
}
|
|
|
|
return thenable;
|
|
}
|
|
function checkIfUseWrappedInTryCatch() {
|
|
{
|
|
// This was set right before SuspenseException was thrown, and it should
|
|
// have been cleared when the exception was handled. If it wasn't,
|
|
// it must have been caught by userspace.
|
|
if (needsToResetSuspendedThenableDEV) {
|
|
needsToResetSuspendedThenableDEV = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
var ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher,
|
|
ReactCurrentBatchConfig$1 = ReactSharedInternals.ReactCurrentBatchConfig;
|
|
var didWarnAboutMismatchedHooksForComponent;
|
|
var didWarnUncachedGetSnapshot;
|
|
var didWarnAboutUseWrappedInTryCatch;
|
|
|
|
{
|
|
didWarnAboutMismatchedHooksForComponent = new Set();
|
|
didWarnAboutUseWrappedInTryCatch = new Set();
|
|
} // These are set right before calling the component.
|
|
|
|
var renderLanes = NoLanes; // The work-in-progress fiber. I've named it differently to distinguish it from
|
|
// the work-in-progress hook.
|
|
|
|
var currentlyRenderingFiber = null; // Hooks are stored as a linked list on the fiber's memoizedState field. The
|
|
// current hook list is the list that belongs to the current fiber. The
|
|
// work-in-progress hook list is a new list that will be added to the
|
|
// work-in-progress fiber.
|
|
|
|
var currentHook = null;
|
|
var workInProgressHook = null; // Whether an update was scheduled at any point during the render phase. This
|
|
// does not get reset if we do another render pass; only when we're completely
|
|
// finished evaluating this component. This is an optimization so we know
|
|
// whether we need to clear render phase updates after a throw.
|
|
|
|
var didScheduleRenderPhaseUpdate = false; // Where an update was scheduled only during the current render pass. This
|
|
// gets reset after each attempt.
|
|
// TODO: Maybe there's some way to consolidate this with
|
|
// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.
|
|
|
|
var didScheduleRenderPhaseUpdateDuringThisPass = false;
|
|
var shouldDoubleInvokeUserFnsInHooksDEV = false; // Counts the number of useId hooks in this component.
|
|
|
|
var localIdCounter = 0; // Counts number of `use`-d thenables
|
|
|
|
var thenableIndexCounter = 0;
|
|
var thenableState = null; // Used for ids that are generated completely client-side (i.e. not during
|
|
// hydration). This counter is global, so client ids are not stable across
|
|
// render attempts.
|
|
|
|
var globalClientIdCounter = 0;
|
|
var RE_RENDER_LIMIT = 25; // In DEV, this is the name of the currently executing primitive hook
|
|
|
|
var currentHookNameInDev = null; // In DEV, this list ensures that hooks are called in the same order between renders.
|
|
// The list stores the order of hooks used during the initial render (mount).
|
|
// Subsequent renders (updates) reference this list.
|
|
|
|
var hookTypesDev = null;
|
|
var hookTypesUpdateIndexDev = -1; // In DEV, this tracks whether currently rendering component needs to ignore
|
|
// the dependencies for Hooks that need them (e.g. useEffect or useMemo).
|
|
// When true, such Hooks will always be "remounted". Only used during hot reload.
|
|
|
|
var ignorePreviousDependencies = false;
|
|
|
|
function mountHookTypesDev() {
|
|
{
|
|
var hookName = currentHookNameInDev;
|
|
|
|
if (hookTypesDev === null) {
|
|
hookTypesDev = [hookName];
|
|
} else {
|
|
hookTypesDev.push(hookName);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateHookTypesDev() {
|
|
{
|
|
var hookName = currentHookNameInDev;
|
|
|
|
if (hookTypesDev !== null) {
|
|
hookTypesUpdateIndexDev++;
|
|
|
|
if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {
|
|
warnOnHookMismatchInDev(hookName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkDepsAreArrayDev(deps) {
|
|
{
|
|
if (deps !== undefined && deps !== null && !isArray(deps)) {
|
|
// Verify deps, but only on mount to avoid extra checks.
|
|
// It's unlikely their type would change as usually you define them inline.
|
|
error(
|
|
"%s received a final argument that is not an array (instead, received `%s`). When " +
|
|
"specified, the final argument must be an array.",
|
|
currentHookNameInDev,
|
|
typeof deps
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function warnOnHookMismatchInDev(currentHookName) {
|
|
{
|
|
var componentName = getComponentNameFromFiber(currentlyRenderingFiber);
|
|
|
|
if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {
|
|
didWarnAboutMismatchedHooksForComponent.add(componentName);
|
|
|
|
if (hookTypesDev !== null) {
|
|
var table = "";
|
|
var secondColumnStart = 30;
|
|
|
|
for (var i = 0; i <= hookTypesUpdateIndexDev; i++) {
|
|
var oldHookName = hookTypesDev[i];
|
|
var newHookName =
|
|
i === hookTypesUpdateIndexDev ? currentHookName : oldHookName;
|
|
var row = i + 1 + ". " + oldHookName; // Extra space so second column lines up
|
|
// lol @ IE not supporting String#repeat
|
|
|
|
while (row.length < secondColumnStart) {
|
|
row += " ";
|
|
}
|
|
|
|
row += newHookName + "\n";
|
|
table += row;
|
|
}
|
|
|
|
error(
|
|
"React has detected a change in the order of Hooks called by %s. " +
|
|
"This will lead to bugs and errors if not fixed. " +
|
|
"For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks\n\n" +
|
|
" Previous render Next render\n" +
|
|
" ------------------------------------------------------\n" +
|
|
"%s" +
|
|
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
|
componentName,
|
|
table
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function throwInvalidHookError() {
|
|
throw new Error(
|
|
"Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for" +
|
|
" one of the following reasons:\n" +
|
|
"1. You might have mismatching versions of React and the renderer (such as React DOM)\n" +
|
|
"2. You might be breaking the Rules of Hooks\n" +
|
|
"3. You might have more than one copy of React in the same app\n" +
|
|
"See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem."
|
|
);
|
|
}
|
|
|
|
function areHookInputsEqual(nextDeps, prevDeps) {
|
|
{
|
|
if (ignorePreviousDependencies) {
|
|
// Only true when this component is being hot reloaded.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (prevDeps === null) {
|
|
{
|
|
error(
|
|
"%s received a final argument during this render, but not during " +
|
|
"the previous render. Even though the final argument is optional, " +
|
|
"its type cannot change between renders.",
|
|
currentHookNameInDev
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
{
|
|
// Don't bother comparing lengths in prod because these arrays should be
|
|
// passed inline.
|
|
if (nextDeps.length !== prevDeps.length) {
|
|
error(
|
|
"The final argument passed to %s changed size between renders. The " +
|
|
"order and size of this array must remain constant.\n\n" +
|
|
"Previous: %s\n" +
|
|
"Incoming: %s",
|
|
currentHookNameInDev,
|
|
"[" + prevDeps.join(", ") + "]",
|
|
"[" + nextDeps.join(", ") + "]"
|
|
);
|
|
}
|
|
} // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
for (var i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
|
|
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
if (objectIs(nextDeps[i], prevDeps[i])) {
|
|
continue;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function renderWithHooks(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
props,
|
|
secondArg,
|
|
nextRenderLanes
|
|
) {
|
|
renderLanes = nextRenderLanes;
|
|
currentlyRenderingFiber = workInProgress;
|
|
|
|
{
|
|
hookTypesDev = current !== null ? current._debugHookTypes : null;
|
|
hookTypesUpdateIndexDev = -1; // Used for hot reloading:
|
|
|
|
ignorePreviousDependencies =
|
|
current !== null && current.type !== workInProgress.type;
|
|
}
|
|
|
|
workInProgress.memoizedState = null;
|
|
workInProgress.updateQueue = null;
|
|
workInProgress.lanes = NoLanes; // The following should have already been reset
|
|
// currentHook = null;
|
|
// workInProgressHook = null;
|
|
// didScheduleRenderPhaseUpdate = false;
|
|
// localIdCounter = 0;
|
|
// thenableIndexCounter = 0;
|
|
// thenableState = null;
|
|
// TODO Warn if no hooks are used at all during mount, then some are used during update.
|
|
// Currently we will identify the update render as a mount because memoizedState === null.
|
|
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
|
|
// Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
|
|
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
|
|
// so memoizedState would be null during updates and mounts.
|
|
|
|
{
|
|
if (current !== null && current.memoizedState !== null) {
|
|
ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
|
|
} else if (hookTypesDev !== null) {
|
|
// This dispatcher handles an edge case where a component is updating,
|
|
// but no stateful hooks have been used.
|
|
// We want to match the production code behavior (which will use HooksDispatcherOnMount),
|
|
// but with the extra DEV validation to ensure hooks ordering hasn't changed.
|
|
// This dispatcher does that.
|
|
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV;
|
|
} else {
|
|
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
|
|
}
|
|
} // In Strict Mode, during development, user functions are double invoked to
|
|
// help detect side effects. The logic for how this is implemented for in
|
|
// hook components is a bit complex so let's break it down.
|
|
//
|
|
// We will invoke the entire component function twice. However, during the
|
|
// second invocation of the component, the hook state from the first
|
|
// invocation will be reused. That means things like `useMemo` functions won't
|
|
// run again, because the deps will match and the memoized result will
|
|
// be reused.
|
|
//
|
|
// We want memoized functions to run twice, too, so account for this, user
|
|
// functions are double invoked during the *first* invocation of the component
|
|
// function, and are *not* double invoked during the second incovation:
|
|
//
|
|
// - First execution of component function: user functions are double invoked
|
|
// - Second execution of component function (in Strict Mode, during
|
|
// development): user functions are not double invoked.
|
|
//
|
|
// This is intentional for a few reasons; most importantly, it's because of
|
|
// how `use` works when something suspends: it reuses the promise that was
|
|
// passed during the first attempt. This is itself a form of memoization.
|
|
// We need to be able to memoize the reactive inputs to the `use` call using
|
|
// a hook (i.e. `useMemo`), which means, the reactive inputs to `use` must
|
|
// come from the same component invocation as the output.
|
|
//
|
|
// There are plenty of tests to ensure this behavior is correct.
|
|
|
|
var shouldDoubleRenderDEV = debugRenderPhaseSideEffectsForStrictMode;
|
|
shouldDoubleInvokeUserFnsInHooksDEV = shouldDoubleRenderDEV;
|
|
var children = Component(props, secondArg);
|
|
shouldDoubleInvokeUserFnsInHooksDEV = false; // Check if there was a render phase update
|
|
|
|
if (didScheduleRenderPhaseUpdateDuringThisPass) {
|
|
// Keep rendering until the component stabilizes (there are no more render
|
|
// phase updates).
|
|
children = renderWithHooksAgain(
|
|
workInProgress,
|
|
Component,
|
|
props,
|
|
secondArg
|
|
);
|
|
}
|
|
|
|
finishRenderingHooks(current, workInProgress);
|
|
return children;
|
|
}
|
|
|
|
function finishRenderingHooks(current, workInProgress) {
|
|
// We can assume the previous dispatcher is always this one, since we set it
|
|
// at the beginning of the render phase and there's no re-entrance.
|
|
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
|
|
|
|
{
|
|
workInProgress._debugHookTypes = hookTypesDev;
|
|
} // This check uses currentHook so that it works the same in DEV and prod bundles.
|
|
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
|
|
|
|
var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null;
|
|
renderLanes = NoLanes;
|
|
currentlyRenderingFiber = null;
|
|
currentHook = null;
|
|
workInProgressHook = null;
|
|
|
|
{
|
|
currentHookNameInDev = null;
|
|
hookTypesDev = null;
|
|
hookTypesUpdateIndexDev = -1; // Confirm that a static flag was not added or removed since the last
|
|
// render. If this fires, it suggests that we incorrectly reset the static
|
|
// flags in some other part of the codebase. This has happened before, for
|
|
// example, in the SuspenseList implementation.
|
|
|
|
if (
|
|
current !== null &&
|
|
(current.flags & StaticMask) !== (workInProgress.flags & StaticMask) && // Disable this warning in legacy mode, because legacy Suspense is weird
|
|
// and creates false positives. To make this work in legacy mode, we'd
|
|
// need to mark fibers that commit in an incomplete state, somehow. For
|
|
// now I'll disable the warning that most of the bugs that would trigger
|
|
// it are either exclusive to concurrent mode or exist in both.
|
|
(current.mode & ConcurrentMode) !== NoMode
|
|
) {
|
|
error(
|
|
"Internal React error: Expected static flag was missing. Please " +
|
|
"notify the React team."
|
|
);
|
|
}
|
|
}
|
|
|
|
didScheduleRenderPhaseUpdate = false; // This is reset by checkDidRenderIdHook
|
|
// localIdCounter = 0;
|
|
|
|
thenableIndexCounter = 0;
|
|
thenableState = null;
|
|
|
|
if (didRenderTooFewHooks) {
|
|
throw new Error(
|
|
"Rendered fewer hooks than expected. This may be caused by an accidental " +
|
|
"early return statement."
|
|
);
|
|
}
|
|
|
|
{
|
|
if (checkIfUseWrappedInTryCatch()) {
|
|
var componentName =
|
|
getComponentNameFromFiber(workInProgress) || "Unknown";
|
|
|
|
if (!didWarnAboutUseWrappedInTryCatch.has(componentName)) {
|
|
didWarnAboutUseWrappedInTryCatch.add(componentName);
|
|
|
|
error(
|
|
"`use` was called from inside a try/catch block. This is not allowed " +
|
|
"and can lead to unexpected behavior. To handle errors triggered " +
|
|
"by `use`, wrap your component in a error boundary."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function replaySuspendedComponentWithHooks(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
props,
|
|
secondArg
|
|
) {
|
|
// This function is used to replay a component that previously suspended,
|
|
// after its data resolves.
|
|
//
|
|
// It's a simplified version of renderWithHooks, but it doesn't need to do
|
|
// most of the set up work because they weren't reset when we suspended; they
|
|
// only get reset when the component either completes (finishRenderingHooks)
|
|
// or unwinds (resetHooksOnUnwind).
|
|
{
|
|
hookTypesDev = current !== null ? current._debugHookTypes : null;
|
|
hookTypesUpdateIndexDev = -1; // Used for hot reloading:
|
|
|
|
ignorePreviousDependencies =
|
|
current !== null && current.type !== workInProgress.type;
|
|
}
|
|
|
|
var children = renderWithHooksAgain(
|
|
workInProgress,
|
|
Component,
|
|
props,
|
|
secondArg
|
|
);
|
|
finishRenderingHooks(current, workInProgress);
|
|
return children;
|
|
}
|
|
|
|
function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
|
|
// This is used to perform another render pass. It's used when setState is
|
|
// called during render, and for double invoking components in Strict Mode
|
|
// during development.
|
|
//
|
|
// The state from the previous pass is reused whenever possible. So, state
|
|
// updates that were already processed are not processed again, and memoized
|
|
// functions (`useMemo`) are not invoked again.
|
|
//
|
|
// Keep rendering in a loop for as long as render phase updates continue to
|
|
// be scheduled. Use a counter to prevent infinite loops.
|
|
var numberOfReRenders = 0;
|
|
var children;
|
|
|
|
do {
|
|
didScheduleRenderPhaseUpdateDuringThisPass = false;
|
|
thenableIndexCounter = 0;
|
|
|
|
if (numberOfReRenders >= RE_RENDER_LIMIT) {
|
|
throw new Error(
|
|
"Too many re-renders. React limits the number of renders to prevent " +
|
|
"an infinite loop."
|
|
);
|
|
}
|
|
|
|
numberOfReRenders += 1;
|
|
|
|
{
|
|
// Even when hot reloading, allow dependencies to stabilize
|
|
// after first render to prevent infinite render phase updates.
|
|
ignorePreviousDependencies = false;
|
|
} // Start over from the beginning of the list
|
|
|
|
currentHook = null;
|
|
workInProgressHook = null;
|
|
workInProgress.updateQueue = null;
|
|
|
|
{
|
|
// Also validate hook order for cascading updates.
|
|
hookTypesUpdateIndexDev = -1;
|
|
}
|
|
|
|
ReactCurrentDispatcher$1.current = HooksDispatcherOnRerenderInDEV;
|
|
children = Component(props, secondArg);
|
|
} while (didScheduleRenderPhaseUpdateDuringThisPass);
|
|
|
|
return children;
|
|
}
|
|
|
|
function checkDidRenderIdHook() {
|
|
// This should be called immediately after every renderWithHooks call.
|
|
// Conceptually, it's part of the return value of renderWithHooks; it's only a
|
|
// separate function to avoid using an array tuple.
|
|
var didRenderIdHook = localIdCounter !== 0;
|
|
localIdCounter = 0;
|
|
return didRenderIdHook;
|
|
}
|
|
function bailoutHooks(current, workInProgress, lanes) {
|
|
workInProgress.updateQueue = current.updateQueue; // TODO: Don't need to reset the flags here, because they're reset in the
|
|
// complete phase (bubbleProperties).
|
|
|
|
if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
|
|
workInProgress.flags &= ~(
|
|
MountPassiveDev |
|
|
MountLayoutDev |
|
|
Passive |
|
|
Update
|
|
);
|
|
} else {
|
|
workInProgress.flags &= ~(Passive | Update);
|
|
}
|
|
|
|
current.lanes = removeLanes(current.lanes, lanes);
|
|
}
|
|
function resetHooksAfterThrow() {
|
|
// This is called immediaetly after a throw. It shouldn't reset the entire
|
|
// module state, because the work loop might decide to replay the component
|
|
// again without rewinding.
|
|
//
|
|
// It should only reset things like the current dispatcher, to prevent hooks
|
|
// from being called outside of a component.
|
|
// We can assume the previous dispatcher is always this one, since we set it
|
|
// at the beginning of the render phase and there's no re-entrance.
|
|
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
|
|
}
|
|
function resetHooksOnUnwind() {
|
|
if (didScheduleRenderPhaseUpdate) {
|
|
// There were render phase updates. These are only valid for this render
|
|
// phase, which we are now aborting. Remove the updates from the queues so
|
|
// they do not persist to the next render. Do not remove updates from hooks
|
|
// that weren't processed.
|
|
//
|
|
// Only reset the updates from the queue if it has a clone. If it does
|
|
// not have a clone, that means it wasn't processed, and the updates were
|
|
// scheduled before we entered the render phase.
|
|
var hook = currentlyRenderingFiber.memoizedState;
|
|
|
|
while (hook !== null) {
|
|
var queue = hook.queue;
|
|
|
|
if (queue !== null) {
|
|
queue.pending = null;
|
|
}
|
|
|
|
hook = hook.next;
|
|
}
|
|
|
|
didScheduleRenderPhaseUpdate = false;
|
|
}
|
|
|
|
renderLanes = NoLanes;
|
|
currentlyRenderingFiber = null;
|
|
currentHook = null;
|
|
workInProgressHook = null;
|
|
|
|
{
|
|
hookTypesDev = null;
|
|
hookTypesUpdateIndexDev = -1;
|
|
currentHookNameInDev = null;
|
|
}
|
|
|
|
didScheduleRenderPhaseUpdateDuringThisPass = false;
|
|
localIdCounter = 0;
|
|
thenableIndexCounter = 0;
|
|
thenableState = null;
|
|
}
|
|
|
|
function mountWorkInProgressHook() {
|
|
var hook = {
|
|
memoizedState: null,
|
|
baseState: null,
|
|
baseQueue: null,
|
|
queue: null,
|
|
next: null
|
|
};
|
|
|
|
if (workInProgressHook === null) {
|
|
// This is the first hook in the list
|
|
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
|
|
} else {
|
|
// Append to the end of the list
|
|
workInProgressHook = workInProgressHook.next = hook;
|
|
}
|
|
|
|
return workInProgressHook;
|
|
}
|
|
|
|
function updateWorkInProgressHook() {
|
|
// This function is used both for updates and for re-renders triggered by a
|
|
// render phase update. It assumes there is either a current hook we can
|
|
// clone, or a work-in-progress hook from a previous render pass that we can
|
|
// use as a base. When we reach the end of the base list, we must switch to
|
|
// the dispatcher used for mounts.
|
|
var nextCurrentHook;
|
|
|
|
if (currentHook === null) {
|
|
var current = currentlyRenderingFiber.alternate;
|
|
|
|
if (current !== null) {
|
|
nextCurrentHook = current.memoizedState;
|
|
} else {
|
|
nextCurrentHook = null;
|
|
}
|
|
} else {
|
|
nextCurrentHook = currentHook.next;
|
|
}
|
|
|
|
var nextWorkInProgressHook;
|
|
|
|
if (workInProgressHook === null) {
|
|
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
|
|
} else {
|
|
nextWorkInProgressHook = workInProgressHook.next;
|
|
}
|
|
|
|
if (nextWorkInProgressHook !== null) {
|
|
// There's already a work-in-progress. Reuse it.
|
|
workInProgressHook = nextWorkInProgressHook;
|
|
nextWorkInProgressHook = workInProgressHook.next;
|
|
currentHook = nextCurrentHook;
|
|
} else {
|
|
// Clone from the current hook.
|
|
if (nextCurrentHook === null) {
|
|
var currentFiber = currentlyRenderingFiber.alternate;
|
|
|
|
if (currentFiber === null) {
|
|
// This is the initial render. This branch is reached when the component
|
|
// suspends, resumes, then renders an additional hook.
|
|
var _newHook = {
|
|
memoizedState: null,
|
|
baseState: null,
|
|
baseQueue: null,
|
|
queue: null,
|
|
next: null
|
|
};
|
|
nextCurrentHook = _newHook;
|
|
} else {
|
|
// This is an update. We should always have a current hook.
|
|
throw new Error("Rendered more hooks than during the previous render.");
|
|
}
|
|
}
|
|
|
|
currentHook = nextCurrentHook;
|
|
var newHook = {
|
|
memoizedState: currentHook.memoizedState,
|
|
baseState: currentHook.baseState,
|
|
baseQueue: currentHook.baseQueue,
|
|
queue: currentHook.queue,
|
|
next: null
|
|
};
|
|
|
|
if (workInProgressHook === null) {
|
|
// This is the first hook in the list.
|
|
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
|
|
} else {
|
|
// Append to the end of the list.
|
|
workInProgressHook = workInProgressHook.next = newHook;
|
|
}
|
|
}
|
|
|
|
return workInProgressHook;
|
|
} // NOTE: defining two versions of this function to avoid size impact when this feature is disabled.
|
|
// Previously this function was inlined, the additional `memoCache` property makes it not inlined.
|
|
|
|
var createFunctionComponentUpdateQueue;
|
|
|
|
{
|
|
createFunctionComponentUpdateQueue = function() {
|
|
return {
|
|
lastEffect: null,
|
|
events: null,
|
|
stores: null
|
|
};
|
|
};
|
|
}
|
|
|
|
function use(usable) {
|
|
if (usable !== null && typeof usable === "object") {
|
|
// $FlowFixMe[method-unbinding]
|
|
if (typeof usable.then === "function") {
|
|
// This is a thenable.
|
|
var thenable = usable; // Track the position of the thenable within this fiber.
|
|
|
|
var index = thenableIndexCounter;
|
|
thenableIndexCounter += 1;
|
|
|
|
if (thenableState === null) {
|
|
thenableState = createThenableState();
|
|
}
|
|
|
|
return trackUsedThenable(thenableState, thenable, index);
|
|
} else if (
|
|
usable.$$typeof === REACT_CONTEXT_TYPE ||
|
|
usable.$$typeof === REACT_SERVER_CONTEXT_TYPE
|
|
) {
|
|
var context = usable;
|
|
return readContext(context);
|
|
}
|
|
} // eslint-disable-next-line react-internal/safe-string-coercion
|
|
|
|
throw new Error("An unsupported type was passed to use(): " + String(usable));
|
|
}
|
|
|
|
function basicStateReducer(state, action) {
|
|
// $FlowFixMe: Flow doesn't like mixed types
|
|
return typeof action === "function" ? action(state) : action;
|
|
}
|
|
|
|
function mountReducer(reducer, initialArg, init) {
|
|
var hook = mountWorkInProgressHook();
|
|
var initialState;
|
|
|
|
if (init !== undefined) {
|
|
initialState = init(initialArg);
|
|
} else {
|
|
initialState = initialArg;
|
|
}
|
|
|
|
hook.memoizedState = hook.baseState = initialState;
|
|
var queue = {
|
|
pending: null,
|
|
lanes: NoLanes,
|
|
dispatch: null,
|
|
lastRenderedReducer: reducer,
|
|
lastRenderedState: initialState
|
|
};
|
|
hook.queue = queue;
|
|
var dispatch = (queue.dispatch = dispatchReducerAction.bind(
|
|
null,
|
|
currentlyRenderingFiber,
|
|
queue
|
|
));
|
|
return [hook.memoizedState, dispatch];
|
|
}
|
|
|
|
function updateReducer(reducer, initialArg, init) {
|
|
var hook = updateWorkInProgressHook();
|
|
var queue = hook.queue;
|
|
|
|
if (queue === null) {
|
|
throw new Error(
|
|
"Should have a queue. This is likely a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
queue.lastRenderedReducer = reducer;
|
|
var current = currentHook; // The last rebase update that is NOT part of the base state.
|
|
|
|
var baseQueue = current.baseQueue; // The last pending update that hasn't been processed yet.
|
|
|
|
var pendingQueue = queue.pending;
|
|
|
|
if (pendingQueue !== null) {
|
|
// We have new updates that haven't been processed yet.
|
|
// We'll add them to the base queue.
|
|
if (baseQueue !== null) {
|
|
// Merge the pending queue and the base queue.
|
|
var baseFirst = baseQueue.next;
|
|
var pendingFirst = pendingQueue.next;
|
|
baseQueue.next = pendingFirst;
|
|
pendingQueue.next = baseFirst;
|
|
}
|
|
|
|
{
|
|
if (current.baseQueue !== baseQueue) {
|
|
// Internal invariant that should never happen, but feasibly could in
|
|
// the future if we implement resuming, or some form of that.
|
|
error(
|
|
"Internal error: Expected work-in-progress queue to be a clone. " +
|
|
"This is a bug in React."
|
|
);
|
|
}
|
|
}
|
|
|
|
current.baseQueue = baseQueue = pendingQueue;
|
|
queue.pending = null;
|
|
}
|
|
|
|
if (baseQueue !== null) {
|
|
// We have a queue to process.
|
|
var first = baseQueue.next;
|
|
var newState = current.baseState;
|
|
var newBaseState = null;
|
|
var newBaseQueueFirst = null;
|
|
var newBaseQueueLast = null;
|
|
var update = first;
|
|
|
|
do {
|
|
// An extra OffscreenLane bit is added to updates that were made to
|
|
// a hidden tree, so that we can distinguish them from updates that were
|
|
// already there when the tree was hidden.
|
|
var updateLane = removeLanes(update.lane, OffscreenLane);
|
|
var isHiddenUpdate = updateLane !== update.lane; // Check if this update was made while the tree was hidden. If so, then
|
|
// it's not a "base" update and we should disregard the extra base lanes
|
|
// that were added to renderLanes when we entered the Offscreen tree.
|
|
|
|
var shouldSkipUpdate = isHiddenUpdate
|
|
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
|
|
: !isSubsetOfLanes(renderLanes, updateLane);
|
|
|
|
if (shouldSkipUpdate) {
|
|
// Priority is insufficient. Skip this update. If this is the first
|
|
// skipped update, the previous update/state is the new base
|
|
// update/state.
|
|
var clone = {
|
|
lane: updateLane,
|
|
action: update.action,
|
|
hasEagerState: update.hasEagerState,
|
|
eagerState: update.eagerState,
|
|
next: null
|
|
};
|
|
|
|
if (newBaseQueueLast === null) {
|
|
newBaseQueueFirst = newBaseQueueLast = clone;
|
|
newBaseState = newState;
|
|
} else {
|
|
newBaseQueueLast = newBaseQueueLast.next = clone;
|
|
} // Update the remaining priority in the queue.
|
|
// TODO: Don't need to accumulate this. Instead, we can remove
|
|
// renderLanes from the original lanes.
|
|
|
|
currentlyRenderingFiber.lanes = mergeLanes(
|
|
currentlyRenderingFiber.lanes,
|
|
updateLane
|
|
);
|
|
markSkippedUpdateLanes(updateLane);
|
|
} else {
|
|
// This update does have sufficient priority.
|
|
if (newBaseQueueLast !== null) {
|
|
var _clone = {
|
|
// This update is going to be committed so we never want uncommit
|
|
// it. Using NoLane works because 0 is a subset of all bitmasks, so
|
|
// this will never be skipped by the check above.
|
|
lane: NoLane,
|
|
action: update.action,
|
|
hasEagerState: update.hasEagerState,
|
|
eagerState: update.eagerState,
|
|
next: null
|
|
};
|
|
newBaseQueueLast = newBaseQueueLast.next = _clone;
|
|
} // Process this update.
|
|
|
|
var action = update.action;
|
|
|
|
if (shouldDoubleInvokeUserFnsInHooksDEV) {
|
|
reducer(newState, action);
|
|
}
|
|
|
|
if (update.hasEagerState) {
|
|
// If this update is a state update (not a reducer) and was processed eagerly,
|
|
// we can use the eagerly computed state
|
|
newState = update.eagerState;
|
|
} else {
|
|
newState = reducer(newState, action);
|
|
}
|
|
}
|
|
|
|
update = update.next;
|
|
} while (update !== null && update !== first);
|
|
|
|
if (newBaseQueueLast === null) {
|
|
newBaseState = newState;
|
|
} else {
|
|
newBaseQueueLast.next = newBaseQueueFirst;
|
|
} // Mark that the fiber performed work, but only if the new state is
|
|
// different from the current state.
|
|
|
|
if (!objectIs(newState, hook.memoizedState)) {
|
|
markWorkInProgressReceivedUpdate();
|
|
}
|
|
|
|
hook.memoizedState = newState;
|
|
hook.baseState = newBaseState;
|
|
hook.baseQueue = newBaseQueueLast;
|
|
queue.lastRenderedState = newState;
|
|
}
|
|
|
|
if (baseQueue === null) {
|
|
// `queue.lanes` is used for entangling transitions. We can set it back to
|
|
// zero once the queue is empty.
|
|
queue.lanes = NoLanes;
|
|
}
|
|
|
|
var dispatch = queue.dispatch;
|
|
return [hook.memoizedState, dispatch];
|
|
}
|
|
|
|
function rerenderReducer(reducer, initialArg, init) {
|
|
var hook = updateWorkInProgressHook();
|
|
var queue = hook.queue;
|
|
|
|
if (queue === null) {
|
|
throw new Error(
|
|
"Should have a queue. This is likely a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
queue.lastRenderedReducer = reducer; // This is a re-render. Apply the new render phase updates to the previous
|
|
// work-in-progress hook.
|
|
|
|
var dispatch = queue.dispatch;
|
|
var lastRenderPhaseUpdate = queue.pending;
|
|
var newState = hook.memoizedState;
|
|
|
|
if (lastRenderPhaseUpdate !== null) {
|
|
// The queue doesn't persist past this render pass.
|
|
queue.pending = null;
|
|
var firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;
|
|
var update = firstRenderPhaseUpdate;
|
|
|
|
do {
|
|
// Process this render phase update. We don't have to check the
|
|
// priority because it will always be the same as the current
|
|
// render's.
|
|
var action = update.action;
|
|
newState = reducer(newState, action);
|
|
update = update.next;
|
|
} while (update !== firstRenderPhaseUpdate); // Mark that the fiber performed work, but only if the new state is
|
|
// different from the current state.
|
|
|
|
if (!objectIs(newState, hook.memoizedState)) {
|
|
markWorkInProgressReceivedUpdate();
|
|
}
|
|
|
|
hook.memoizedState = newState; // Don't persist the state accumulated from the render phase updates to
|
|
// the base state unless the queue is empty.
|
|
// TODO: Not sure if this is the desired semantics, but it's what we
|
|
// do for gDSFP. I can't remember why.
|
|
|
|
if (hook.baseQueue === null) {
|
|
hook.baseState = newState;
|
|
}
|
|
|
|
queue.lastRenderedState = newState;
|
|
}
|
|
|
|
return [newState, dispatch];
|
|
}
|
|
|
|
function readFromUnsubscribedMutableSource(root, source, getSnapshot) {
|
|
{
|
|
warnAboutMultipleRenderersDEV(source);
|
|
}
|
|
|
|
var getVersion = source._getVersion;
|
|
var version = getVersion(source._source); // Is it safe for this component to read from this source during the current render?
|
|
|
|
var isSafeToReadFromSource = false; // Check the version first.
|
|
// If this render has already been started with a specific version,
|
|
// we can use it alone to determine if we can safely read from the source.
|
|
|
|
var currentRenderVersion = getWorkInProgressVersion(source);
|
|
|
|
if (currentRenderVersion !== null) {
|
|
// It's safe to read if the store hasn't been mutated since the last time
|
|
// we read something.
|
|
isSafeToReadFromSource = currentRenderVersion === version;
|
|
} else {
|
|
// If there's no version, then this is the first time we've read from the
|
|
// source during the current render pass, so we need to do a bit more work.
|
|
// What we need to determine is if there are any hooks that already
|
|
// subscribed to the source, and if so, whether there are any pending
|
|
// mutations that haven't been synchronized yet.
|
|
//
|
|
// If there are no pending mutations, then `root.mutableReadLanes` will be
|
|
// empty, and we know we can safely read.
|
|
//
|
|
// If there *are* pending mutations, we may still be able to safely read
|
|
// if the currently rendering lanes are inclusive of the pending mutation
|
|
// lanes, since that guarantees that the value we're about to read from
|
|
// the source is consistent with the values that we read during the most
|
|
// recent mutation.
|
|
isSafeToReadFromSource = isSubsetOfLanes(
|
|
renderLanes,
|
|
root.mutableReadLanes
|
|
);
|
|
|
|
if (isSafeToReadFromSource) {
|
|
// If it's safe to read from this source during the current render,
|
|
// store the version in case other components read from it.
|
|
// A changed version number will let those components know to throw and restart the render.
|
|
setWorkInProgressVersion(source, version);
|
|
}
|
|
}
|
|
|
|
if (isSafeToReadFromSource) {
|
|
var snapshot = getSnapshot(source._source);
|
|
|
|
{
|
|
if (typeof snapshot === "function") {
|
|
error(
|
|
"Mutable source should not return a function as the snapshot value. " +
|
|
"Functions may close over mutable values and cause tearing."
|
|
);
|
|
}
|
|
}
|
|
|
|
return snapshot;
|
|
} else {
|
|
// This handles the special case of a mutable source being shared between renderers.
|
|
// In that case, if the source is mutated between the first and second renderer,
|
|
// The second renderer don't know that it needs to reset the WIP version during unwind,
|
|
// (because the hook only marks sources as dirty if it's written to their WIP version).
|
|
// That would cause this tear check to throw again and eventually be visible to the user.
|
|
// We can avoid this infinite loop by explicitly marking the source as dirty.
|
|
//
|
|
// This can lead to tearing in the first renderer when it resumes,
|
|
// but there's nothing we can do about that (short of throwing here and refusing to continue the render).
|
|
markSourceAsDirty(source); // Intentioally throw an error to force React to retry synchronously. During
|
|
// the synchronous retry, it will block interleaved mutations, so we should
|
|
// get a consistent read. Therefore, the following error should never be
|
|
// visible to the user.
|
|
// We expect this error not to be thrown during the synchronous retry,
|
|
// because we blocked interleaved mutations.
|
|
|
|
throw new Error(
|
|
"Cannot read from mutable source during the current render without tearing. This may be a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
|
|
function useMutableSource(hook, source, getSnapshot, subscribe) {
|
|
var root = getWorkInProgressRoot();
|
|
|
|
if (root === null) {
|
|
throw new Error(
|
|
"Expected a work-in-progress root. This is a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
var getVersion = source._getVersion;
|
|
var version = getVersion(source._source);
|
|
var dispatcher = ReactCurrentDispatcher$1.current; // eslint-disable-next-line prefer-const
|
|
|
|
var _dispatcher$useState = dispatcher.useState(function() {
|
|
return readFromUnsubscribedMutableSource(root, source, getSnapshot);
|
|
}),
|
|
currentSnapshot = _dispatcher$useState[0],
|
|
setSnapshot = _dispatcher$useState[1];
|
|
|
|
var snapshot = currentSnapshot; // Grab a handle to the state hook as well.
|
|
// We use it to clear the pending update queue if we have a new source.
|
|
|
|
var stateHook = workInProgressHook;
|
|
var memoizedState = hook.memoizedState;
|
|
var refs = memoizedState.refs;
|
|
var prevGetSnapshot = refs.getSnapshot;
|
|
var prevSource = memoizedState.source;
|
|
var prevSubscribe = memoizedState.subscribe;
|
|
var fiber = currentlyRenderingFiber;
|
|
hook.memoizedState = {
|
|
refs: refs,
|
|
source: source,
|
|
subscribe: subscribe
|
|
}; // Sync the values needed by our subscription handler after each commit.
|
|
|
|
dispatcher.useEffect(
|
|
function() {
|
|
refs.getSnapshot = getSnapshot; // Normally the dispatch function for a state hook never changes,
|
|
// but this hook recreates the queue in certain cases to avoid updates from stale sources.
|
|
// handleChange() below needs to reference the dispatch function without re-subscribing,
|
|
// so we use a ref to ensure that it always has the latest version.
|
|
|
|
refs.setSnapshot = setSnapshot; // Check for a possible change between when we last rendered now.
|
|
|
|
var maybeNewVersion = getVersion(source._source);
|
|
|
|
if (!objectIs(version, maybeNewVersion)) {
|
|
var maybeNewSnapshot = getSnapshot(source._source);
|
|
|
|
{
|
|
if (typeof maybeNewSnapshot === "function") {
|
|
error(
|
|
"Mutable source should not return a function as the snapshot value. " +
|
|
"Functions may close over mutable values and cause tearing."
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!objectIs(snapshot, maybeNewSnapshot)) {
|
|
setSnapshot(maybeNewSnapshot);
|
|
var lane = requestUpdateLane(fiber);
|
|
markRootMutableRead(root, lane);
|
|
} // If the source mutated between render and now,
|
|
// there may be state updates already scheduled from the old source.
|
|
// Entangle the updates so that they render in the same batch.
|
|
|
|
markRootEntangled(root, root.mutableReadLanes);
|
|
}
|
|
},
|
|
[getSnapshot, source, subscribe]
|
|
); // If we got a new source or subscribe function, re-subscribe in a passive effect.
|
|
|
|
dispatcher.useEffect(
|
|
function() {
|
|
var handleChange = function() {
|
|
var latestGetSnapshot = refs.getSnapshot;
|
|
var latestSetSnapshot = refs.setSnapshot;
|
|
|
|
try {
|
|
latestSetSnapshot(latestGetSnapshot(source._source)); // Record a pending mutable source update with the same expiration time.
|
|
|
|
var lane = requestUpdateLane(fiber);
|
|
markRootMutableRead(root, lane);
|
|
} catch (error) {
|
|
// A selector might throw after a source mutation.
|
|
// e.g. it might try to read from a part of the store that no longer exists.
|
|
// In this case we should still schedule an update with React.
|
|
// Worst case the selector will throw again and then an error boundary will handle it.
|
|
latestSetSnapshot(function() {
|
|
throw error;
|
|
});
|
|
}
|
|
};
|
|
|
|
var unsubscribe = subscribe(source._source, handleChange);
|
|
|
|
{
|
|
if (typeof unsubscribe !== "function") {
|
|
error(
|
|
"Mutable source subscribe function must return an unsubscribe function."
|
|
);
|
|
}
|
|
}
|
|
|
|
return unsubscribe;
|
|
},
|
|
[source, subscribe]
|
|
); // If any of the inputs to useMutableSource change, reading is potentially unsafe.
|
|
//
|
|
// If either the source or the subscription have changed we can't can't trust the update queue.
|
|
// Maybe the source changed in a way that the old subscription ignored but the new one depends on.
|
|
//
|
|
// If the getSnapshot function changed, we also shouldn't rely on the update queue.
|
|
// It's possible that the underlying source was mutated between the when the last "change" event fired,
|
|
// and when the current render (with the new getSnapshot function) is processed.
|
|
//
|
|
// In both cases, we need to throw away pending updates (since they are no longer relevant)
|
|
// and treat reading from the source as we do in the mount case.
|
|
|
|
if (
|
|
!objectIs(prevGetSnapshot, getSnapshot) ||
|
|
!objectIs(prevSource, source) ||
|
|
!objectIs(prevSubscribe, subscribe)
|
|
) {
|
|
// Create a new queue and setState method,
|
|
// So if there are interleaved updates, they get pushed to the older queue.
|
|
// When this becomes current, the previous queue and dispatch method will be discarded,
|
|
// including any interleaving updates that occur.
|
|
var newQueue = {
|
|
pending: null,
|
|
lanes: NoLanes,
|
|
dispatch: null,
|
|
lastRenderedReducer: basicStateReducer,
|
|
lastRenderedState: snapshot
|
|
};
|
|
newQueue.dispatch = setSnapshot = dispatchSetState.bind(
|
|
null,
|
|
currentlyRenderingFiber,
|
|
newQueue
|
|
);
|
|
stateHook.queue = newQueue;
|
|
stateHook.baseQueue = null;
|
|
snapshot = readFromUnsubscribedMutableSource(root, source, getSnapshot);
|
|
stateHook.memoizedState = stateHook.baseState = snapshot;
|
|
}
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
function mountMutableSource(source, getSnapshot, subscribe) {
|
|
var hook = mountWorkInProgressHook();
|
|
hook.memoizedState = {
|
|
refs: {
|
|
getSnapshot: getSnapshot,
|
|
setSnapshot: null
|
|
},
|
|
source: source,
|
|
subscribe: subscribe
|
|
};
|
|
return useMutableSource(hook, source, getSnapshot, subscribe);
|
|
}
|
|
|
|
function updateMutableSource(source, getSnapshot, subscribe) {
|
|
var hook = updateWorkInProgressHook();
|
|
return useMutableSource(hook, source, getSnapshot, subscribe);
|
|
}
|
|
|
|
function mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
|
|
var fiber = currentlyRenderingFiber;
|
|
var hook = mountWorkInProgressHook();
|
|
var nextSnapshot;
|
|
var isHydrating = getIsHydrating();
|
|
|
|
if (isHydrating) {
|
|
if (getServerSnapshot === undefined) {
|
|
throw new Error(
|
|
"Missing getServerSnapshot, which is required for " +
|
|
"server-rendered content. Will revert to client rendering."
|
|
);
|
|
}
|
|
|
|
nextSnapshot = getServerSnapshot();
|
|
|
|
{
|
|
if (!didWarnUncachedGetSnapshot) {
|
|
if (nextSnapshot !== getServerSnapshot()) {
|
|
error(
|
|
"The result of getServerSnapshot should be cached to avoid an infinite loop"
|
|
);
|
|
|
|
didWarnUncachedGetSnapshot = true;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
nextSnapshot = getSnapshot();
|
|
|
|
{
|
|
if (!didWarnUncachedGetSnapshot) {
|
|
var cachedSnapshot = getSnapshot();
|
|
|
|
if (!objectIs(nextSnapshot, cachedSnapshot)) {
|
|
error(
|
|
"The result of getSnapshot should be cached to avoid an infinite loop"
|
|
);
|
|
|
|
didWarnUncachedGetSnapshot = true;
|
|
}
|
|
}
|
|
} // Unless we're rendering a blocking lane, schedule a consistency check.
|
|
// Right before committing, we will walk the tree and check if any of the
|
|
// stores were mutated.
|
|
//
|
|
// We won't do this if we're hydrating server-rendered content, because if
|
|
// the content is stale, it's already visible anyway. Instead we'll patch
|
|
// it up in a passive effect.
|
|
|
|
var root = getWorkInProgressRoot();
|
|
|
|
if (root === null) {
|
|
throw new Error(
|
|
"Expected a work-in-progress root. This is a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
if (!includesBlockingLane(root, renderLanes)) {
|
|
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
|
|
}
|
|
} // Read the current snapshot from the store on every render. This breaks the
|
|
// normal rules of React, and only works because store updates are
|
|
// always synchronous.
|
|
|
|
hook.memoizedState = nextSnapshot;
|
|
var inst = {
|
|
value: nextSnapshot,
|
|
getSnapshot: getSnapshot
|
|
};
|
|
hook.queue = inst; // Schedule an effect to subscribe to the store.
|
|
|
|
mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); // Schedule an effect to update the mutable instance fields. We will update
|
|
// this whenever subscribe, getSnapshot, or value changes. Because there's no
|
|
// clean-up function, and we track the deps correctly, we can call pushEffect
|
|
// directly, without storing any additional state. For the same reason, we
|
|
// don't need to set a static flag, either.
|
|
// TODO: We can move this to the passive phase once we add a pre-commit
|
|
// consistency check. See the next comment.
|
|
|
|
fiber.flags |= Passive;
|
|
pushEffect(
|
|
HasEffect | Passive$1,
|
|
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
|
|
undefined,
|
|
null
|
|
);
|
|
return nextSnapshot;
|
|
}
|
|
|
|
function updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
|
|
var fiber = currentlyRenderingFiber;
|
|
var hook = updateWorkInProgressHook(); // Read the current snapshot from the store on every render. This breaks the
|
|
// normal rules of React, and only works because store updates are
|
|
// always synchronous.
|
|
|
|
var nextSnapshot = getSnapshot();
|
|
|
|
{
|
|
if (!didWarnUncachedGetSnapshot) {
|
|
var cachedSnapshot = getSnapshot();
|
|
|
|
if (!objectIs(nextSnapshot, cachedSnapshot)) {
|
|
error(
|
|
"The result of getSnapshot should be cached to avoid an infinite loop"
|
|
);
|
|
|
|
didWarnUncachedGetSnapshot = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
var prevSnapshot = (currentHook || hook).memoizedState;
|
|
var snapshotChanged = !objectIs(prevSnapshot, nextSnapshot);
|
|
|
|
if (snapshotChanged) {
|
|
hook.memoizedState = nextSnapshot;
|
|
markWorkInProgressReceivedUpdate();
|
|
}
|
|
|
|
var inst = hook.queue;
|
|
updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [
|
|
subscribe
|
|
]); // Whenever getSnapshot or subscribe changes, we need to check in the
|
|
// commit phase if there was an interleaved mutation. In concurrent mode
|
|
// this can happen all the time, but even in synchronous mode, an earlier
|
|
// effect may have mutated the store.
|
|
|
|
if (
|
|
inst.getSnapshot !== getSnapshot ||
|
|
snapshotChanged || // Check if the susbcribe function changed. We can save some memory by
|
|
// checking whether we scheduled a subscription effect above.
|
|
(workInProgressHook !== null &&
|
|
workInProgressHook.memoizedState.tag & HasEffect)
|
|
) {
|
|
fiber.flags |= Passive;
|
|
pushEffect(
|
|
HasEffect | Passive$1,
|
|
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
|
|
undefined,
|
|
null
|
|
); // Unless we're rendering a blocking lane, schedule a consistency check.
|
|
// Right before committing, we will walk the tree and check if any of the
|
|
// stores were mutated.
|
|
|
|
var root = getWorkInProgressRoot();
|
|
|
|
if (root === null) {
|
|
throw new Error(
|
|
"Expected a work-in-progress root. This is a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
if (!includesBlockingLane(root, renderLanes)) {
|
|
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
|
|
}
|
|
}
|
|
|
|
return nextSnapshot;
|
|
}
|
|
|
|
function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) {
|
|
fiber.flags |= StoreConsistency;
|
|
var check = {
|
|
getSnapshot: getSnapshot,
|
|
value: renderedSnapshot
|
|
};
|
|
var componentUpdateQueue = currentlyRenderingFiber.updateQueue;
|
|
|
|
if (componentUpdateQueue === null) {
|
|
componentUpdateQueue = createFunctionComponentUpdateQueue();
|
|
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
|
|
componentUpdateQueue.stores = [check];
|
|
} else {
|
|
var stores = componentUpdateQueue.stores;
|
|
|
|
if (stores === null) {
|
|
componentUpdateQueue.stores = [check];
|
|
} else {
|
|
stores.push(check);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) {
|
|
// These are updated in the passive phase
|
|
inst.value = nextSnapshot;
|
|
inst.getSnapshot = getSnapshot; // Something may have been mutated in between render and commit. This could
|
|
// have been in an event that fired before the passive effects, or it could
|
|
// have been in a layout effect. In that case, we would have used the old
|
|
// snapsho and getSnapshot values to bail out. We need to check one more time.
|
|
|
|
if (checkIfSnapshotChanged(inst)) {
|
|
// Force a re-render.
|
|
forceStoreRerender(fiber);
|
|
}
|
|
}
|
|
|
|
function subscribeToStore(fiber, inst, subscribe) {
|
|
var handleStoreChange = function() {
|
|
// The store changed. Check if the snapshot changed since the last time we
|
|
// read from the store.
|
|
if (checkIfSnapshotChanged(inst)) {
|
|
// Force a re-render.
|
|
forceStoreRerender(fiber);
|
|
}
|
|
}; // Subscribe to the store and return a clean-up function.
|
|
|
|
return subscribe(handleStoreChange);
|
|
}
|
|
|
|
function checkIfSnapshotChanged(inst) {
|
|
var latestGetSnapshot = inst.getSnapshot;
|
|
var prevValue = inst.value;
|
|
|
|
try {
|
|
var nextValue = latestGetSnapshot();
|
|
return !objectIs(prevValue, nextValue);
|
|
} catch (error) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function forceStoreRerender(fiber) {
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
}
|
|
|
|
function mountState(initialState) {
|
|
var hook = mountWorkInProgressHook();
|
|
|
|
if (typeof initialState === "function") {
|
|
// $FlowFixMe: Flow doesn't like mixed types
|
|
initialState = initialState();
|
|
}
|
|
|
|
hook.memoizedState = hook.baseState = initialState;
|
|
var queue = {
|
|
pending: null,
|
|
lanes: NoLanes,
|
|
dispatch: null,
|
|
lastRenderedReducer: basicStateReducer,
|
|
lastRenderedState: initialState
|
|
};
|
|
hook.queue = queue;
|
|
var dispatch = (queue.dispatch = dispatchSetState.bind(
|
|
null,
|
|
currentlyRenderingFiber,
|
|
queue
|
|
));
|
|
return [hook.memoizedState, dispatch];
|
|
}
|
|
|
|
function updateState(initialState) {
|
|
return updateReducer(basicStateReducer);
|
|
}
|
|
|
|
function rerenderState(initialState) {
|
|
return rerenderReducer(basicStateReducer);
|
|
}
|
|
|
|
function pushEffect(tag, create, destroy, deps) {
|
|
var effect = {
|
|
tag: tag,
|
|
create: create,
|
|
destroy: destroy,
|
|
deps: deps,
|
|
// Circular
|
|
next: null
|
|
};
|
|
var componentUpdateQueue = currentlyRenderingFiber.updateQueue;
|
|
|
|
if (componentUpdateQueue === null) {
|
|
componentUpdateQueue = createFunctionComponentUpdateQueue();
|
|
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
|
|
componentUpdateQueue.lastEffect = effect.next = effect;
|
|
} else {
|
|
var lastEffect = componentUpdateQueue.lastEffect;
|
|
|
|
if (lastEffect === null) {
|
|
componentUpdateQueue.lastEffect = effect.next = effect;
|
|
} else {
|
|
var firstEffect = lastEffect.next;
|
|
lastEffect.next = effect;
|
|
effect.next = firstEffect;
|
|
componentUpdateQueue.lastEffect = effect;
|
|
}
|
|
}
|
|
|
|
return effect;
|
|
}
|
|
|
|
function mountRef(initialValue) {
|
|
var hook = mountWorkInProgressHook();
|
|
|
|
{
|
|
var _ref2 = {
|
|
current: initialValue
|
|
};
|
|
hook.memoizedState = _ref2;
|
|
return _ref2;
|
|
}
|
|
}
|
|
|
|
function updateRef(initialValue) {
|
|
var hook = updateWorkInProgressHook();
|
|
return hook.memoizedState;
|
|
}
|
|
|
|
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
|
|
var hook = mountWorkInProgressHook();
|
|
var nextDeps = deps === undefined ? null : deps;
|
|
currentlyRenderingFiber.flags |= fiberFlags;
|
|
hook.memoizedState = pushEffect(
|
|
HasEffect | hookFlags,
|
|
create,
|
|
undefined,
|
|
nextDeps
|
|
);
|
|
}
|
|
|
|
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
|
|
var hook = updateWorkInProgressHook();
|
|
var nextDeps = deps === undefined ? null : deps;
|
|
var destroy = undefined;
|
|
|
|
if (currentHook !== null) {
|
|
var prevEffect = currentHook.memoizedState;
|
|
destroy = prevEffect.destroy;
|
|
|
|
if (nextDeps !== null) {
|
|
var prevDeps = prevEffect.deps;
|
|
|
|
if (areHookInputsEqual(nextDeps, prevDeps)) {
|
|
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
currentlyRenderingFiber.flags |= fiberFlags;
|
|
hook.memoizedState = pushEffect(
|
|
HasEffect | hookFlags,
|
|
create,
|
|
destroy,
|
|
nextDeps
|
|
);
|
|
}
|
|
|
|
function mountEffect(create, deps) {
|
|
if ((currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode) {
|
|
mountEffectImpl(
|
|
MountPassiveDev | Passive | PassiveStatic,
|
|
Passive$1,
|
|
create,
|
|
deps
|
|
);
|
|
} else {
|
|
mountEffectImpl(Passive | PassiveStatic, Passive$1, create, deps);
|
|
}
|
|
}
|
|
|
|
function updateEffect(create, deps) {
|
|
updateEffectImpl(Passive, Passive$1, create, deps);
|
|
}
|
|
|
|
function mountInsertionEffect(create, deps) {
|
|
mountEffectImpl(Update, Insertion, create, deps);
|
|
}
|
|
|
|
function updateInsertionEffect(create, deps) {
|
|
return updateEffectImpl(Update, Insertion, create, deps);
|
|
}
|
|
|
|
function mountLayoutEffect(create, deps) {
|
|
var fiberFlags = Update | LayoutStatic;
|
|
|
|
if ((currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode) {
|
|
fiberFlags |= MountLayoutDev;
|
|
}
|
|
|
|
return mountEffectImpl(fiberFlags, Layout, create, deps);
|
|
}
|
|
|
|
function updateLayoutEffect(create, deps) {
|
|
return updateEffectImpl(Update, Layout, create, deps);
|
|
}
|
|
|
|
function imperativeHandleEffect(create, ref) {
|
|
if (typeof ref === "function") {
|
|
var refCallback = ref;
|
|
var inst = create();
|
|
refCallback(inst);
|
|
return function() {
|
|
refCallback(null);
|
|
};
|
|
} else if (ref !== null && ref !== undefined) {
|
|
var refObject = ref;
|
|
|
|
{
|
|
if (!refObject.hasOwnProperty("current")) {
|
|
error(
|
|
"Expected useImperativeHandle() first argument to either be a " +
|
|
"ref callback or React.createRef() object. Instead received: %s.",
|
|
"an object with keys {" + Object.keys(refObject).join(", ") + "}"
|
|
);
|
|
}
|
|
}
|
|
|
|
var _inst = create();
|
|
|
|
refObject.current = _inst;
|
|
return function() {
|
|
refObject.current = null;
|
|
};
|
|
}
|
|
}
|
|
|
|
function mountImperativeHandle(ref, create, deps) {
|
|
{
|
|
if (typeof create !== "function") {
|
|
error(
|
|
"Expected useImperativeHandle() second argument to be a function " +
|
|
"that creates a handle. Instead received: %s.",
|
|
create !== null ? typeof create : "null"
|
|
);
|
|
}
|
|
} // TODO: If deps are provided, should we skip comparing the ref itself?
|
|
|
|
var effectDeps =
|
|
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
|
|
var fiberFlags = Update | LayoutStatic;
|
|
|
|
if ((currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode) {
|
|
fiberFlags |= MountLayoutDev;
|
|
}
|
|
|
|
mountEffectImpl(
|
|
fiberFlags,
|
|
Layout,
|
|
imperativeHandleEffect.bind(null, create, ref),
|
|
effectDeps
|
|
);
|
|
}
|
|
|
|
function updateImperativeHandle(ref, create, deps) {
|
|
{
|
|
if (typeof create !== "function") {
|
|
error(
|
|
"Expected useImperativeHandle() second argument to be a function " +
|
|
"that creates a handle. Instead received: %s.",
|
|
create !== null ? typeof create : "null"
|
|
);
|
|
}
|
|
} // TODO: If deps are provided, should we skip comparing the ref itself?
|
|
|
|
var effectDeps =
|
|
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
|
|
updateEffectImpl(
|
|
Update,
|
|
Layout,
|
|
imperativeHandleEffect.bind(null, create, ref),
|
|
effectDeps
|
|
);
|
|
}
|
|
|
|
function mountDebugValue(value, formatterFn) {
|
|
// This hook is normally a no-op.
|
|
// The react-debug-hooks package injects its own implementation
|
|
// so that e.g. DevTools can display custom hook values.
|
|
}
|
|
|
|
var updateDebugValue = mountDebugValue;
|
|
|
|
function mountCallback(callback, deps) {
|
|
var hook = mountWorkInProgressHook();
|
|
var nextDeps = deps === undefined ? null : deps;
|
|
hook.memoizedState = [callback, nextDeps];
|
|
return callback;
|
|
}
|
|
|
|
function updateCallback(callback, deps) {
|
|
var hook = updateWorkInProgressHook();
|
|
var nextDeps = deps === undefined ? null : deps;
|
|
var prevState = hook.memoizedState;
|
|
|
|
if (prevState !== null) {
|
|
if (nextDeps !== null) {
|
|
var prevDeps = prevState[1];
|
|
|
|
if (areHookInputsEqual(nextDeps, prevDeps)) {
|
|
return prevState[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
hook.memoizedState = [callback, nextDeps];
|
|
return callback;
|
|
}
|
|
|
|
function mountMemo(nextCreate, deps) {
|
|
var hook = mountWorkInProgressHook();
|
|
var nextDeps = deps === undefined ? null : deps;
|
|
|
|
if (shouldDoubleInvokeUserFnsInHooksDEV) {
|
|
nextCreate();
|
|
}
|
|
|
|
var nextValue = nextCreate();
|
|
hook.memoizedState = [nextValue, nextDeps];
|
|
return nextValue;
|
|
}
|
|
|
|
function updateMemo(nextCreate, deps) {
|
|
var hook = updateWorkInProgressHook();
|
|
var nextDeps = deps === undefined ? null : deps;
|
|
var prevState = hook.memoizedState;
|
|
|
|
if (prevState !== null) {
|
|
// Assume these are defined. If they're not, areHookInputsEqual will warn.
|
|
if (nextDeps !== null) {
|
|
var prevDeps = prevState[1];
|
|
|
|
if (areHookInputsEqual(nextDeps, prevDeps)) {
|
|
return prevState[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shouldDoubleInvokeUserFnsInHooksDEV) {
|
|
nextCreate();
|
|
}
|
|
|
|
var nextValue = nextCreate();
|
|
hook.memoizedState = [nextValue, nextDeps];
|
|
return nextValue;
|
|
}
|
|
|
|
function mountDeferredValue(value) {
|
|
var hook = mountWorkInProgressHook();
|
|
hook.memoizedState = value;
|
|
return value;
|
|
}
|
|
|
|
function updateDeferredValue(value) {
|
|
var hook = updateWorkInProgressHook();
|
|
var resolvedCurrentHook = currentHook;
|
|
var prevValue = resolvedCurrentHook.memoizedState;
|
|
return updateDeferredValueImpl(hook, prevValue, value);
|
|
}
|
|
|
|
function rerenderDeferredValue(value) {
|
|
var hook = updateWorkInProgressHook();
|
|
|
|
if (currentHook === null) {
|
|
// This is a rerender during a mount.
|
|
hook.memoizedState = value;
|
|
return value;
|
|
} else {
|
|
// This is a rerender during an update.
|
|
var prevValue = currentHook.memoizedState;
|
|
return updateDeferredValueImpl(hook, prevValue, value);
|
|
}
|
|
}
|
|
|
|
function updateDeferredValueImpl(hook, prevValue, value) {
|
|
var shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
|
|
|
|
if (shouldDeferValue) {
|
|
// This is an urgent update. If the value has changed, keep using the
|
|
// previous value and spawn a deferred render to update it later.
|
|
if (!objectIs(value, prevValue)) {
|
|
// Schedule a deferred render
|
|
var deferredLane = claimNextTransitionLane();
|
|
currentlyRenderingFiber.lanes = mergeLanes(
|
|
currentlyRenderingFiber.lanes,
|
|
deferredLane
|
|
);
|
|
markSkippedUpdateLanes(deferredLane); // Set this to true to indicate that the rendered value is inconsistent
|
|
// from the latest value. The name "baseState" doesn't really match how we
|
|
// use it because we're reusing a state hook field instead of creating a
|
|
// new one.
|
|
|
|
hook.baseState = true;
|
|
} // Reuse the previous value
|
|
|
|
return prevValue;
|
|
} else {
|
|
// This is not an urgent update, so we can use the latest value regardless
|
|
// of what it is. No need to defer it.
|
|
// However, if we're currently inside a spawned render, then we need to mark
|
|
// this as an update to prevent the fiber from bailing out.
|
|
//
|
|
// `baseState` is true when the current value is different from the rendered
|
|
// value. The name doesn't really match how we use it because we're reusing
|
|
// a state hook field instead of creating a new one.
|
|
if (hook.baseState) {
|
|
// Flip this back to false.
|
|
hook.baseState = false;
|
|
markWorkInProgressReceivedUpdate();
|
|
}
|
|
|
|
hook.memoizedState = value;
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function startTransition(setPending, callback, options) {
|
|
var previousPriority = getCurrentUpdatePriority();
|
|
setCurrentUpdatePriority(
|
|
higherEventPriority(previousPriority, ContinuousEventPriority)
|
|
);
|
|
setPending(true);
|
|
var prevTransition = ReactCurrentBatchConfig$1.transition;
|
|
ReactCurrentBatchConfig$1.transition = {};
|
|
var currentTransition = ReactCurrentBatchConfig$1.transition;
|
|
|
|
{
|
|
ReactCurrentBatchConfig$1.transition._updatedFibers = new Set();
|
|
}
|
|
|
|
try {
|
|
setPending(false);
|
|
callback();
|
|
} finally {
|
|
setCurrentUpdatePriority(previousPriority);
|
|
ReactCurrentBatchConfig$1.transition = prevTransition;
|
|
|
|
{
|
|
if (prevTransition === null && currentTransition._updatedFibers) {
|
|
var updatedFibersCount = currentTransition._updatedFibers.size;
|
|
|
|
currentTransition._updatedFibers.clear();
|
|
|
|
if (updatedFibersCount > 10) {
|
|
warn(
|
|
"Detected a large number of updates inside startTransition. " +
|
|
"If this is due to a subscription please re-write it to use React provided hooks. " +
|
|
"Otherwise concurrent mode guarantees are off the table."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function mountTransition() {
|
|
var _mountState = mountState(false),
|
|
isPending = _mountState[0],
|
|
setPending = _mountState[1]; // The `start` method never changes.
|
|
|
|
var start = startTransition.bind(null, setPending);
|
|
var hook = mountWorkInProgressHook();
|
|
hook.memoizedState = start;
|
|
return [isPending, start];
|
|
}
|
|
|
|
function updateTransition() {
|
|
var _updateState = updateState(),
|
|
isPending = _updateState[0];
|
|
|
|
var hook = updateWorkInProgressHook();
|
|
var start = hook.memoizedState;
|
|
return [isPending, start];
|
|
}
|
|
|
|
function rerenderTransition() {
|
|
var _rerenderState = rerenderState(),
|
|
isPending = _rerenderState[0];
|
|
|
|
var hook = updateWorkInProgressHook();
|
|
var start = hook.memoizedState;
|
|
return [isPending, start];
|
|
}
|
|
|
|
function mountId() {
|
|
var hook = mountWorkInProgressHook();
|
|
var root = getWorkInProgressRoot(); // TODO: In Fizz, id generation is specific to each server config. Maybe we
|
|
// should do this in Fiber, too? Deferring this decision for now because
|
|
// there's no other place to store the prefix except for an internal field on
|
|
// the public createRoot object, which the fiber tree does not currently have
|
|
// a reference to.
|
|
|
|
var identifierPrefix = root.identifierPrefix;
|
|
var id;
|
|
|
|
if (getIsHydrating()) {
|
|
var treeId = getTreeId(); // Use a captial R prefix for server-generated ids.
|
|
|
|
id = ":" + identifierPrefix + "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.
|
|
|
|
var localId = localIdCounter++;
|
|
|
|
if (localId > 0) {
|
|
id += "H" + localId.toString(32);
|
|
}
|
|
|
|
id += ":";
|
|
} else {
|
|
// Use a lowercase r prefix for client-generated ids.
|
|
var globalClientId = globalClientIdCounter++;
|
|
id = ":" + identifierPrefix + "r" + globalClientId.toString(32) + ":";
|
|
}
|
|
|
|
hook.memoizedState = id;
|
|
return id;
|
|
}
|
|
|
|
function updateId() {
|
|
var hook = updateWorkInProgressHook();
|
|
var id = hook.memoizedState;
|
|
return id;
|
|
}
|
|
|
|
function mountRefresh() {
|
|
var hook = mountWorkInProgressHook();
|
|
var refresh = (hook.memoizedState = refreshCache.bind(
|
|
null,
|
|
currentlyRenderingFiber
|
|
));
|
|
return refresh;
|
|
}
|
|
|
|
function updateRefresh() {
|
|
var hook = updateWorkInProgressHook();
|
|
return hook.memoizedState;
|
|
}
|
|
|
|
function refreshCache(fiber, seedKey, seedValue) {
|
|
// TODO: Consider warning if the refresh is at discrete priority, or if we
|
|
// otherwise suspect that it wasn't batched properly.
|
|
|
|
var provider = fiber.return;
|
|
|
|
while (provider !== null) {
|
|
switch (provider.tag) {
|
|
case CacheComponent:
|
|
case HostRoot: {
|
|
// Schedule an update on the cache boundary to trigger a refresh.
|
|
var lane = requestUpdateLane(provider);
|
|
var eventTime = requestEventTime();
|
|
var refreshUpdate = createUpdate(eventTime, lane);
|
|
var root = enqueueUpdate$1(provider, refreshUpdate, lane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, provider, lane, eventTime);
|
|
entangleTransitions(root, provider, lane);
|
|
} // TODO: If a refresh never commits, the new cache created here must be
|
|
// released. A simple case is start refreshing a cache boundary, but then
|
|
// unmount that boundary before the refresh completes.
|
|
|
|
var seededCache = createCache();
|
|
|
|
if (seedKey !== null && seedKey !== undefined && root !== null) {
|
|
{
|
|
// Seed the cache with the value passed by the caller. This could be
|
|
// from a server mutation, or it could be a streaming response.
|
|
seededCache.data.set(seedKey, seedValue);
|
|
}
|
|
}
|
|
|
|
var payload = {
|
|
cache: seededCache
|
|
};
|
|
refreshUpdate.payload = payload;
|
|
return;
|
|
}
|
|
}
|
|
|
|
provider = provider.return;
|
|
} // TODO: Warn if unmounted?
|
|
}
|
|
|
|
function dispatchReducerAction(fiber, queue, action) {
|
|
{
|
|
if (typeof arguments[3] === "function") {
|
|
error(
|
|
"State updates from the useState() and useReducer() Hooks don't support the " +
|
|
"second callback argument. To execute a side effect after " +
|
|
"rendering, declare it in the component body with useEffect()."
|
|
);
|
|
}
|
|
}
|
|
|
|
var lane = requestUpdateLane(fiber);
|
|
var update = {
|
|
lane: lane,
|
|
action: action,
|
|
hasEagerState: false,
|
|
eagerState: null,
|
|
next: null
|
|
};
|
|
|
|
if (isRenderPhaseUpdate(fiber)) {
|
|
enqueueRenderPhaseUpdate(queue, update);
|
|
} else {
|
|
var root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
|
|
|
|
if (root !== null) {
|
|
var eventTime = requestEventTime();
|
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
|
entangleTransitionUpdate(root, queue, lane);
|
|
}
|
|
}
|
|
}
|
|
|
|
function dispatchSetState(fiber, queue, action) {
|
|
{
|
|
if (typeof arguments[3] === "function") {
|
|
error(
|
|
"State updates from the useState() and useReducer() Hooks don't support the " +
|
|
"second callback argument. To execute a side effect after " +
|
|
"rendering, declare it in the component body with useEffect()."
|
|
);
|
|
}
|
|
}
|
|
|
|
var lane = requestUpdateLane(fiber);
|
|
var update = {
|
|
lane: lane,
|
|
action: action,
|
|
hasEagerState: false,
|
|
eagerState: null,
|
|
next: null
|
|
};
|
|
|
|
if (isRenderPhaseUpdate(fiber)) {
|
|
enqueueRenderPhaseUpdate(queue, update);
|
|
} else {
|
|
var alternate = fiber.alternate;
|
|
|
|
if (
|
|
fiber.lanes === NoLanes &&
|
|
(alternate === null || alternate.lanes === NoLanes)
|
|
) {
|
|
// The queue is currently empty, which means we can eagerly compute the
|
|
// next state before entering the render phase. If the new state is the
|
|
// same as the current state, we may be able to bail out entirely.
|
|
var lastRenderedReducer = queue.lastRenderedReducer;
|
|
|
|
if (lastRenderedReducer !== null) {
|
|
var prevDispatcher;
|
|
|
|
{
|
|
prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
}
|
|
|
|
try {
|
|
var currentState = queue.lastRenderedState;
|
|
var eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute
|
|
// it, on the update object. If the reducer hasn't changed by the
|
|
// time we enter the render phase, then the eager state can be used
|
|
// without calling the reducer again.
|
|
|
|
update.hasEagerState = true;
|
|
update.eagerState = eagerState;
|
|
|
|
if (objectIs(eagerState, currentState)) {
|
|
// Fast path. We can bail out without scheduling React to re-render.
|
|
// It's still possible that we'll need to rebase this update later,
|
|
// if the component re-renders for a different reason and by that
|
|
// time the reducer has changed.
|
|
// TODO: Do we still need to entangle transitions in this case?
|
|
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
// Suppress the error. It will throw again in the render phase.
|
|
} finally {
|
|
{
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
|
|
|
|
if (root !== null) {
|
|
var eventTime = requestEventTime();
|
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
|
entangleTransitionUpdate(root, queue, lane);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isRenderPhaseUpdate(fiber) {
|
|
var alternate = fiber.alternate;
|
|
return (
|
|
fiber === currentlyRenderingFiber ||
|
|
(alternate !== null && alternate === currentlyRenderingFiber)
|
|
);
|
|
}
|
|
|
|
function enqueueRenderPhaseUpdate(queue, update) {
|
|
// This is a render phase update. Stash it in a lazily-created map of
|
|
// queue -> linked list of updates. After this render pass, we'll restart
|
|
// and apply the stashed updates on top of the work-in-progress hook.
|
|
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
|
|
var pending = queue.pending;
|
|
|
|
if (pending === null) {
|
|
// This is the first update. Create a circular list.
|
|
update.next = update;
|
|
} else {
|
|
update.next = pending.next;
|
|
pending.next = update;
|
|
}
|
|
|
|
queue.pending = update;
|
|
} // TODO: Move to ReactFiberConcurrentUpdates?
|
|
|
|
function entangleTransitionUpdate(root, queue, lane) {
|
|
if (isTransitionLane(lane)) {
|
|
var queueLanes = queue.lanes; // If any entangled lanes are no longer pending on the root, then they
|
|
// must have finished. We can remove them from the shared queue, which
|
|
// represents a superset of the actually pending lanes. In some cases we
|
|
// may entangle more than we need to, but that's OK. In fact it's worse if
|
|
// we *don't* entangle when we should.
|
|
|
|
queueLanes = intersectLanes(queueLanes, root.pendingLanes); // Entangle the new transition lane with the other transition lanes.
|
|
|
|
var newQueueLanes = mergeLanes(queueLanes, lane);
|
|
queue.lanes = newQueueLanes; // Even if queue.lanes already include lane, we don't know for certain if
|
|
// the lane finished since the last time we entangled it. So we need to
|
|
// entangle it again, just to be sure.
|
|
|
|
markRootEntangled(root, newQueueLanes);
|
|
}
|
|
}
|
|
|
|
var ContextOnlyDispatcher = {
|
|
readContext: readContext,
|
|
useCallback: throwInvalidHookError,
|
|
useContext: throwInvalidHookError,
|
|
useEffect: throwInvalidHookError,
|
|
useImperativeHandle: throwInvalidHookError,
|
|
useInsertionEffect: throwInvalidHookError,
|
|
useLayoutEffect: throwInvalidHookError,
|
|
useMemo: throwInvalidHookError,
|
|
useReducer: throwInvalidHookError,
|
|
useRef: throwInvalidHookError,
|
|
useState: throwInvalidHookError,
|
|
useDebugValue: throwInvalidHookError,
|
|
useDeferredValue: throwInvalidHookError,
|
|
useTransition: throwInvalidHookError,
|
|
useMutableSource: throwInvalidHookError,
|
|
useSyncExternalStore: throwInvalidHookError,
|
|
useId: throwInvalidHookError
|
|
};
|
|
|
|
{
|
|
ContextOnlyDispatcher.useCacheRefresh = throwInvalidHookError;
|
|
}
|
|
|
|
{
|
|
ContextOnlyDispatcher.use = throwInvalidHookError;
|
|
}
|
|
|
|
var HooksDispatcherOnMountInDEV = null;
|
|
var HooksDispatcherOnMountWithHookTypesInDEV = null;
|
|
var HooksDispatcherOnUpdateInDEV = null;
|
|
var HooksDispatcherOnRerenderInDEV = null;
|
|
var InvalidNestedHooksDispatcherOnMountInDEV = null;
|
|
var InvalidNestedHooksDispatcherOnUpdateInDEV = null;
|
|
var InvalidNestedHooksDispatcherOnRerenderInDEV = null;
|
|
|
|
{
|
|
var warnInvalidContextAccess = function() {
|
|
error(
|
|
"Context can only be read while React is rendering. " +
|
|
"In classes, you can read it in the render method or getDerivedStateFromProps. " +
|
|
"In function components, you can read it directly in the function body, but not " +
|
|
"inside Hooks like useReducer() or useMemo()."
|
|
);
|
|
};
|
|
|
|
var warnInvalidHookAccess = function() {
|
|
error(
|
|
"Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. " +
|
|
"You can only call Hooks at the top level of your React function. " +
|
|
"For more information, see " +
|
|
"https://reactjs.org/link/rules-of-hooks"
|
|
);
|
|
};
|
|
|
|
HooksDispatcherOnMountInDEV = {
|
|
readContext: function(context) {
|
|
return readContext(context);
|
|
},
|
|
useCallback: function(callback, deps) {
|
|
currentHookNameInDev = "useCallback";
|
|
mountHookTypesDev();
|
|
checkDepsAreArrayDev(deps);
|
|
return mountCallback(callback, deps);
|
|
},
|
|
useContext: function(context) {
|
|
currentHookNameInDev = "useContext";
|
|
mountHookTypesDev();
|
|
return readContext(context);
|
|
},
|
|
useEffect: function(create, deps) {
|
|
currentHookNameInDev = "useEffect";
|
|
mountHookTypesDev();
|
|
checkDepsAreArrayDev(deps);
|
|
return mountEffect(create, deps);
|
|
},
|
|
useImperativeHandle: function(ref, create, deps) {
|
|
currentHookNameInDev = "useImperativeHandle";
|
|
mountHookTypesDev();
|
|
checkDepsAreArrayDev(deps);
|
|
return mountImperativeHandle(ref, create, deps);
|
|
},
|
|
useInsertionEffect: function(create, deps) {
|
|
currentHookNameInDev = "useInsertionEffect";
|
|
mountHookTypesDev();
|
|
checkDepsAreArrayDev(deps);
|
|
return mountInsertionEffect(create, deps);
|
|
},
|
|
useLayoutEffect: function(create, deps) {
|
|
currentHookNameInDev = "useLayoutEffect";
|
|
mountHookTypesDev();
|
|
checkDepsAreArrayDev(deps);
|
|
return mountLayoutEffect(create, deps);
|
|
},
|
|
useMemo: function(create, deps) {
|
|
currentHookNameInDev = "useMemo";
|
|
mountHookTypesDev();
|
|
checkDepsAreArrayDev(deps);
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountMemo(create, deps);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useReducer: function(reducer, initialArg, init) {
|
|
currentHookNameInDev = "useReducer";
|
|
mountHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountReducer(reducer, initialArg, init);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useRef: function(initialValue) {
|
|
currentHookNameInDev = "useRef";
|
|
mountHookTypesDev();
|
|
return mountRef(initialValue);
|
|
},
|
|
useState: function(initialState) {
|
|
currentHookNameInDev = "useState";
|
|
mountHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountState(initialState);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useDebugValue: function(value, formatterFn) {
|
|
currentHookNameInDev = "useDebugValue";
|
|
mountHookTypesDev();
|
|
return mountDebugValue();
|
|
},
|
|
useDeferredValue: function(value) {
|
|
currentHookNameInDev = "useDeferredValue";
|
|
mountHookTypesDev();
|
|
return mountDeferredValue(value);
|
|
},
|
|
useTransition: function() {
|
|
currentHookNameInDev = "useTransition";
|
|
mountHookTypesDev();
|
|
return mountTransition();
|
|
},
|
|
useMutableSource: function(source, getSnapshot, subscribe) {
|
|
currentHookNameInDev = "useMutableSource";
|
|
mountHookTypesDev();
|
|
return mountMutableSource(source, getSnapshot, subscribe);
|
|
},
|
|
useSyncExternalStore: function(subscribe, getSnapshot, getServerSnapshot) {
|
|
currentHookNameInDev = "useSyncExternalStore";
|
|
mountHookTypesDev();
|
|
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
},
|
|
useId: function() {
|
|
currentHookNameInDev = "useId";
|
|
mountHookTypesDev();
|
|
return mountId();
|
|
}
|
|
};
|
|
|
|
{
|
|
HooksDispatcherOnMountInDEV.useCacheRefresh = function useCacheRefresh() {
|
|
currentHookNameInDev = "useCacheRefresh";
|
|
mountHookTypesDev();
|
|
return mountRefresh();
|
|
};
|
|
}
|
|
|
|
{
|
|
HooksDispatcherOnMountInDEV.use = use;
|
|
}
|
|
|
|
HooksDispatcherOnMountWithHookTypesInDEV = {
|
|
readContext: function(context) {
|
|
return readContext(context);
|
|
},
|
|
useCallback: function(callback, deps) {
|
|
currentHookNameInDev = "useCallback";
|
|
updateHookTypesDev();
|
|
return mountCallback(callback, deps);
|
|
},
|
|
useContext: function(context) {
|
|
currentHookNameInDev = "useContext";
|
|
updateHookTypesDev();
|
|
return readContext(context);
|
|
},
|
|
useEffect: function(create, deps) {
|
|
currentHookNameInDev = "useEffect";
|
|
updateHookTypesDev();
|
|
return mountEffect(create, deps);
|
|
},
|
|
useImperativeHandle: function(ref, create, deps) {
|
|
currentHookNameInDev = "useImperativeHandle";
|
|
updateHookTypesDev();
|
|
return mountImperativeHandle(ref, create, deps);
|
|
},
|
|
useInsertionEffect: function(create, deps) {
|
|
currentHookNameInDev = "useInsertionEffect";
|
|
updateHookTypesDev();
|
|
return mountInsertionEffect(create, deps);
|
|
},
|
|
useLayoutEffect: function(create, deps) {
|
|
currentHookNameInDev = "useLayoutEffect";
|
|
updateHookTypesDev();
|
|
return mountLayoutEffect(create, deps);
|
|
},
|
|
useMemo: function(create, deps) {
|
|
currentHookNameInDev = "useMemo";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountMemo(create, deps);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useReducer: function(reducer, initialArg, init) {
|
|
currentHookNameInDev = "useReducer";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountReducer(reducer, initialArg, init);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useRef: function(initialValue) {
|
|
currentHookNameInDev = "useRef";
|
|
updateHookTypesDev();
|
|
return mountRef(initialValue);
|
|
},
|
|
useState: function(initialState) {
|
|
currentHookNameInDev = "useState";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountState(initialState);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useDebugValue: function(value, formatterFn) {
|
|
currentHookNameInDev = "useDebugValue";
|
|
updateHookTypesDev();
|
|
return mountDebugValue();
|
|
},
|
|
useDeferredValue: function(value) {
|
|
currentHookNameInDev = "useDeferredValue";
|
|
updateHookTypesDev();
|
|
return mountDeferredValue(value);
|
|
},
|
|
useTransition: function() {
|
|
currentHookNameInDev = "useTransition";
|
|
updateHookTypesDev();
|
|
return mountTransition();
|
|
},
|
|
useMutableSource: function(source, getSnapshot, subscribe) {
|
|
currentHookNameInDev = "useMutableSource";
|
|
updateHookTypesDev();
|
|
return mountMutableSource(source, getSnapshot, subscribe);
|
|
},
|
|
useSyncExternalStore: function(subscribe, getSnapshot, getServerSnapshot) {
|
|
currentHookNameInDev = "useSyncExternalStore";
|
|
updateHookTypesDev();
|
|
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
},
|
|
useId: function() {
|
|
currentHookNameInDev = "useId";
|
|
updateHookTypesDev();
|
|
return mountId();
|
|
}
|
|
};
|
|
|
|
{
|
|
HooksDispatcherOnMountWithHookTypesInDEV.useCacheRefresh = function useCacheRefresh() {
|
|
currentHookNameInDev = "useCacheRefresh";
|
|
updateHookTypesDev();
|
|
return mountRefresh();
|
|
};
|
|
}
|
|
|
|
{
|
|
HooksDispatcherOnMountWithHookTypesInDEV.use = use;
|
|
}
|
|
|
|
HooksDispatcherOnUpdateInDEV = {
|
|
readContext: function(context) {
|
|
return readContext(context);
|
|
},
|
|
useCallback: function(callback, deps) {
|
|
currentHookNameInDev = "useCallback";
|
|
updateHookTypesDev();
|
|
return updateCallback(callback, deps);
|
|
},
|
|
useContext: function(context) {
|
|
currentHookNameInDev = "useContext";
|
|
updateHookTypesDev();
|
|
return readContext(context);
|
|
},
|
|
useEffect: function(create, deps) {
|
|
currentHookNameInDev = "useEffect";
|
|
updateHookTypesDev();
|
|
return updateEffect(create, deps);
|
|
},
|
|
useImperativeHandle: function(ref, create, deps) {
|
|
currentHookNameInDev = "useImperativeHandle";
|
|
updateHookTypesDev();
|
|
return updateImperativeHandle(ref, create, deps);
|
|
},
|
|
useInsertionEffect: function(create, deps) {
|
|
currentHookNameInDev = "useInsertionEffect";
|
|
updateHookTypesDev();
|
|
return updateInsertionEffect(create, deps);
|
|
},
|
|
useLayoutEffect: function(create, deps) {
|
|
currentHookNameInDev = "useLayoutEffect";
|
|
updateHookTypesDev();
|
|
return updateLayoutEffect(create, deps);
|
|
},
|
|
useMemo: function(create, deps) {
|
|
currentHookNameInDev = "useMemo";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return updateMemo(create, deps);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useReducer: function(reducer, initialArg, init) {
|
|
currentHookNameInDev = "useReducer";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return updateReducer(reducer, initialArg, init);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useRef: function(initialValue) {
|
|
currentHookNameInDev = "useRef";
|
|
updateHookTypesDev();
|
|
return updateRef();
|
|
},
|
|
useState: function(initialState) {
|
|
currentHookNameInDev = "useState";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return updateState(initialState);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useDebugValue: function(value, formatterFn) {
|
|
currentHookNameInDev = "useDebugValue";
|
|
updateHookTypesDev();
|
|
return updateDebugValue();
|
|
},
|
|
useDeferredValue: function(value) {
|
|
currentHookNameInDev = "useDeferredValue";
|
|
updateHookTypesDev();
|
|
return updateDeferredValue(value);
|
|
},
|
|
useTransition: function() {
|
|
currentHookNameInDev = "useTransition";
|
|
updateHookTypesDev();
|
|
return updateTransition();
|
|
},
|
|
useMutableSource: function(source, getSnapshot, subscribe) {
|
|
currentHookNameInDev = "useMutableSource";
|
|
updateHookTypesDev();
|
|
return updateMutableSource(source, getSnapshot, subscribe);
|
|
},
|
|
useSyncExternalStore: function(subscribe, getSnapshot, getServerSnapshot) {
|
|
currentHookNameInDev = "useSyncExternalStore";
|
|
updateHookTypesDev();
|
|
return updateSyncExternalStore(subscribe, getSnapshot);
|
|
},
|
|
useId: function() {
|
|
currentHookNameInDev = "useId";
|
|
updateHookTypesDev();
|
|
return updateId();
|
|
}
|
|
};
|
|
|
|
{
|
|
HooksDispatcherOnUpdateInDEV.useCacheRefresh = function useCacheRefresh() {
|
|
currentHookNameInDev = "useCacheRefresh";
|
|
updateHookTypesDev();
|
|
return updateRefresh();
|
|
};
|
|
}
|
|
|
|
{
|
|
HooksDispatcherOnUpdateInDEV.use = use;
|
|
}
|
|
|
|
HooksDispatcherOnRerenderInDEV = {
|
|
readContext: function(context) {
|
|
return readContext(context);
|
|
},
|
|
useCallback: function(callback, deps) {
|
|
currentHookNameInDev = "useCallback";
|
|
updateHookTypesDev();
|
|
return updateCallback(callback, deps);
|
|
},
|
|
useContext: function(context) {
|
|
currentHookNameInDev = "useContext";
|
|
updateHookTypesDev();
|
|
return readContext(context);
|
|
},
|
|
useEffect: function(create, deps) {
|
|
currentHookNameInDev = "useEffect";
|
|
updateHookTypesDev();
|
|
return updateEffect(create, deps);
|
|
},
|
|
useImperativeHandle: function(ref, create, deps) {
|
|
currentHookNameInDev = "useImperativeHandle";
|
|
updateHookTypesDev();
|
|
return updateImperativeHandle(ref, create, deps);
|
|
},
|
|
useInsertionEffect: function(create, deps) {
|
|
currentHookNameInDev = "useInsertionEffect";
|
|
updateHookTypesDev();
|
|
return updateInsertionEffect(create, deps);
|
|
},
|
|
useLayoutEffect: function(create, deps) {
|
|
currentHookNameInDev = "useLayoutEffect";
|
|
updateHookTypesDev();
|
|
return updateLayoutEffect(create, deps);
|
|
},
|
|
useMemo: function(create, deps) {
|
|
currentHookNameInDev = "useMemo";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
|
|
|
|
try {
|
|
return updateMemo(create, deps);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useReducer: function(reducer, initialArg, init) {
|
|
currentHookNameInDev = "useReducer";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
|
|
|
|
try {
|
|
return rerenderReducer(reducer, initialArg, init);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useRef: function(initialValue) {
|
|
currentHookNameInDev = "useRef";
|
|
updateHookTypesDev();
|
|
return updateRef();
|
|
},
|
|
useState: function(initialState) {
|
|
currentHookNameInDev = "useState";
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
|
|
|
|
try {
|
|
return rerenderState(initialState);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useDebugValue: function(value, formatterFn) {
|
|
currentHookNameInDev = "useDebugValue";
|
|
updateHookTypesDev();
|
|
return updateDebugValue();
|
|
},
|
|
useDeferredValue: function(value) {
|
|
currentHookNameInDev = "useDeferredValue";
|
|
updateHookTypesDev();
|
|
return rerenderDeferredValue(value);
|
|
},
|
|
useTransition: function() {
|
|
currentHookNameInDev = "useTransition";
|
|
updateHookTypesDev();
|
|
return rerenderTransition();
|
|
},
|
|
useMutableSource: function(source, getSnapshot, subscribe) {
|
|
currentHookNameInDev = "useMutableSource";
|
|
updateHookTypesDev();
|
|
return updateMutableSource(source, getSnapshot, subscribe);
|
|
},
|
|
useSyncExternalStore: function(subscribe, getSnapshot, getServerSnapshot) {
|
|
currentHookNameInDev = "useSyncExternalStore";
|
|
updateHookTypesDev();
|
|
return updateSyncExternalStore(subscribe, getSnapshot);
|
|
},
|
|
useId: function() {
|
|
currentHookNameInDev = "useId";
|
|
updateHookTypesDev();
|
|
return updateId();
|
|
}
|
|
};
|
|
|
|
{
|
|
HooksDispatcherOnRerenderInDEV.useCacheRefresh = function useCacheRefresh() {
|
|
currentHookNameInDev = "useCacheRefresh";
|
|
updateHookTypesDev();
|
|
return updateRefresh();
|
|
};
|
|
}
|
|
|
|
{
|
|
HooksDispatcherOnRerenderInDEV.use = use;
|
|
}
|
|
|
|
InvalidNestedHooksDispatcherOnMountInDEV = {
|
|
readContext: function(context) {
|
|
warnInvalidContextAccess();
|
|
return readContext(context);
|
|
},
|
|
useCallback: function(callback, deps) {
|
|
currentHookNameInDev = "useCallback";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountCallback(callback, deps);
|
|
},
|
|
useContext: function(context) {
|
|
currentHookNameInDev = "useContext";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return readContext(context);
|
|
},
|
|
useEffect: function(create, deps) {
|
|
currentHookNameInDev = "useEffect";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountEffect(create, deps);
|
|
},
|
|
useImperativeHandle: function(ref, create, deps) {
|
|
currentHookNameInDev = "useImperativeHandle";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountImperativeHandle(ref, create, deps);
|
|
},
|
|
useInsertionEffect: function(create, deps) {
|
|
currentHookNameInDev = "useInsertionEffect";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountInsertionEffect(create, deps);
|
|
},
|
|
useLayoutEffect: function(create, deps) {
|
|
currentHookNameInDev = "useLayoutEffect";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountLayoutEffect(create, deps);
|
|
},
|
|
useMemo: function(create, deps) {
|
|
currentHookNameInDev = "useMemo";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountMemo(create, deps);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useReducer: function(reducer, initialArg, init) {
|
|
currentHookNameInDev = "useReducer";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountReducer(reducer, initialArg, init);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useRef: function(initialValue) {
|
|
currentHookNameInDev = "useRef";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountRef(initialValue);
|
|
},
|
|
useState: function(initialState) {
|
|
currentHookNameInDev = "useState";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
|
|
|
try {
|
|
return mountState(initialState);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useDebugValue: function(value, formatterFn) {
|
|
currentHookNameInDev = "useDebugValue";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountDebugValue();
|
|
},
|
|
useDeferredValue: function(value) {
|
|
currentHookNameInDev = "useDeferredValue";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountDeferredValue(value);
|
|
},
|
|
useTransition: function() {
|
|
currentHookNameInDev = "useTransition";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountTransition();
|
|
},
|
|
useMutableSource: function(source, getSnapshot, subscribe) {
|
|
currentHookNameInDev = "useMutableSource";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountMutableSource(source, getSnapshot, subscribe);
|
|
},
|
|
useSyncExternalStore: function(subscribe, getSnapshot, getServerSnapshot) {
|
|
currentHookNameInDev = "useSyncExternalStore";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
},
|
|
useId: function() {
|
|
currentHookNameInDev = "useId";
|
|
warnInvalidHookAccess();
|
|
mountHookTypesDev();
|
|
return mountId();
|
|
}
|
|
};
|
|
|
|
{
|
|
InvalidNestedHooksDispatcherOnMountInDEV.useCacheRefresh = function useCacheRefresh() {
|
|
currentHookNameInDev = "useCacheRefresh";
|
|
mountHookTypesDev();
|
|
return mountRefresh();
|
|
};
|
|
}
|
|
|
|
{
|
|
InvalidNestedHooksDispatcherOnMountInDEV.use = function(usable) {
|
|
warnInvalidHookAccess();
|
|
return use(usable);
|
|
};
|
|
}
|
|
|
|
InvalidNestedHooksDispatcherOnUpdateInDEV = {
|
|
readContext: function(context) {
|
|
warnInvalidContextAccess();
|
|
return readContext(context);
|
|
},
|
|
useCallback: function(callback, deps) {
|
|
currentHookNameInDev = "useCallback";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateCallback(callback, deps);
|
|
},
|
|
useContext: function(context) {
|
|
currentHookNameInDev = "useContext";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return readContext(context);
|
|
},
|
|
useEffect: function(create, deps) {
|
|
currentHookNameInDev = "useEffect";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateEffect(create, deps);
|
|
},
|
|
useImperativeHandle: function(ref, create, deps) {
|
|
currentHookNameInDev = "useImperativeHandle";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateImperativeHandle(ref, create, deps);
|
|
},
|
|
useInsertionEffect: function(create, deps) {
|
|
currentHookNameInDev = "useInsertionEffect";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateInsertionEffect(create, deps);
|
|
},
|
|
useLayoutEffect: function(create, deps) {
|
|
currentHookNameInDev = "useLayoutEffect";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateLayoutEffect(create, deps);
|
|
},
|
|
useMemo: function(create, deps) {
|
|
currentHookNameInDev = "useMemo";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return updateMemo(create, deps);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useReducer: function(reducer, initialArg, init) {
|
|
currentHookNameInDev = "useReducer";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return updateReducer(reducer, initialArg, init);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useRef: function(initialValue) {
|
|
currentHookNameInDev = "useRef";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateRef();
|
|
},
|
|
useState: function(initialState) {
|
|
currentHookNameInDev = "useState";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return updateState(initialState);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useDebugValue: function(value, formatterFn) {
|
|
currentHookNameInDev = "useDebugValue";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateDebugValue();
|
|
},
|
|
useDeferredValue: function(value) {
|
|
currentHookNameInDev = "useDeferredValue";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateDeferredValue(value);
|
|
},
|
|
useTransition: function() {
|
|
currentHookNameInDev = "useTransition";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateTransition();
|
|
},
|
|
useMutableSource: function(source, getSnapshot, subscribe) {
|
|
currentHookNameInDev = "useMutableSource";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateMutableSource(source, getSnapshot, subscribe);
|
|
},
|
|
useSyncExternalStore: function(subscribe, getSnapshot, getServerSnapshot) {
|
|
currentHookNameInDev = "useSyncExternalStore";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateSyncExternalStore(subscribe, getSnapshot);
|
|
},
|
|
useId: function() {
|
|
currentHookNameInDev = "useId";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateId();
|
|
}
|
|
};
|
|
|
|
{
|
|
InvalidNestedHooksDispatcherOnUpdateInDEV.useCacheRefresh = function useCacheRefresh() {
|
|
currentHookNameInDev = "useCacheRefresh";
|
|
updateHookTypesDev();
|
|
return updateRefresh();
|
|
};
|
|
}
|
|
|
|
{
|
|
InvalidNestedHooksDispatcherOnUpdateInDEV.use = function(usable) {
|
|
warnInvalidHookAccess();
|
|
return use(usable);
|
|
};
|
|
}
|
|
|
|
InvalidNestedHooksDispatcherOnRerenderInDEV = {
|
|
readContext: function(context) {
|
|
warnInvalidContextAccess();
|
|
return readContext(context);
|
|
},
|
|
useCallback: function(callback, deps) {
|
|
currentHookNameInDev = "useCallback";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateCallback(callback, deps);
|
|
},
|
|
useContext: function(context) {
|
|
currentHookNameInDev = "useContext";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return readContext(context);
|
|
},
|
|
useEffect: function(create, deps) {
|
|
currentHookNameInDev = "useEffect";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateEffect(create, deps);
|
|
},
|
|
useImperativeHandle: function(ref, create, deps) {
|
|
currentHookNameInDev = "useImperativeHandle";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateImperativeHandle(ref, create, deps);
|
|
},
|
|
useInsertionEffect: function(create, deps) {
|
|
currentHookNameInDev = "useInsertionEffect";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateInsertionEffect(create, deps);
|
|
},
|
|
useLayoutEffect: function(create, deps) {
|
|
currentHookNameInDev = "useLayoutEffect";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateLayoutEffect(create, deps);
|
|
},
|
|
useMemo: function(create, deps) {
|
|
currentHookNameInDev = "useMemo";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return updateMemo(create, deps);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useReducer: function(reducer, initialArg, init) {
|
|
currentHookNameInDev = "useReducer";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return rerenderReducer(reducer, initialArg, init);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useRef: function(initialValue) {
|
|
currentHookNameInDev = "useRef";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateRef();
|
|
},
|
|
useState: function(initialState) {
|
|
currentHookNameInDev = "useState";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
var prevDispatcher = ReactCurrentDispatcher$1.current;
|
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
|
|
|
try {
|
|
return rerenderState(initialState);
|
|
} finally {
|
|
ReactCurrentDispatcher$1.current = prevDispatcher;
|
|
}
|
|
},
|
|
useDebugValue: function(value, formatterFn) {
|
|
currentHookNameInDev = "useDebugValue";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateDebugValue();
|
|
},
|
|
useDeferredValue: function(value) {
|
|
currentHookNameInDev = "useDeferredValue";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return rerenderDeferredValue(value);
|
|
},
|
|
useTransition: function() {
|
|
currentHookNameInDev = "useTransition";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return rerenderTransition();
|
|
},
|
|
useMutableSource: function(source, getSnapshot, subscribe) {
|
|
currentHookNameInDev = "useMutableSource";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateMutableSource(source, getSnapshot, subscribe);
|
|
},
|
|
useSyncExternalStore: function(subscribe, getSnapshot, getServerSnapshot) {
|
|
currentHookNameInDev = "useSyncExternalStore";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateSyncExternalStore(subscribe, getSnapshot);
|
|
},
|
|
useId: function() {
|
|
currentHookNameInDev = "useId";
|
|
warnInvalidHookAccess();
|
|
updateHookTypesDev();
|
|
return updateId();
|
|
}
|
|
};
|
|
|
|
{
|
|
InvalidNestedHooksDispatcherOnRerenderInDEV.useCacheRefresh = function useCacheRefresh() {
|
|
currentHookNameInDev = "useCacheRefresh";
|
|
updateHookTypesDev();
|
|
return updateRefresh();
|
|
};
|
|
}
|
|
|
|
{
|
|
InvalidNestedHooksDispatcherOnRerenderInDEV.use = function(usable) {
|
|
warnInvalidHookAccess();
|
|
return use(usable);
|
|
};
|
|
}
|
|
}
|
|
|
|
function recordLayoutEffectDuration(fiber) {
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
function startLayoutEffectTimer() {
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
function resolveDefaultProps(Component, baseProps) {
|
|
if (Component && Component.defaultProps) {
|
|
// Resolve default props. Taken from ReactElement
|
|
var props = assign({}, baseProps);
|
|
var defaultProps = Component.defaultProps;
|
|
|
|
for (var propName in defaultProps) {
|
|
if (props[propName] === undefined) {
|
|
props[propName] = defaultProps[propName];
|
|
}
|
|
}
|
|
|
|
return props;
|
|
}
|
|
|
|
return baseProps;
|
|
}
|
|
|
|
var fakeInternalInstance = {};
|
|
var didWarnAboutStateAssignmentForComponent;
|
|
var didWarnAboutUninitializedState;
|
|
var didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
|
|
var didWarnAboutLegacyLifecyclesAndDerivedState;
|
|
var didWarnAboutUndefinedDerivedState;
|
|
var warnOnUndefinedDerivedState;
|
|
var warnOnInvalidCallback;
|
|
var didWarnAboutDirectlyAssigningPropsToState;
|
|
var didWarnAboutInvalidateContextType;
|
|
|
|
{
|
|
didWarnAboutStateAssignmentForComponent = new Set();
|
|
didWarnAboutUninitializedState = new Set();
|
|
didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set();
|
|
didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
|
|
didWarnAboutDirectlyAssigningPropsToState = new Set();
|
|
didWarnAboutUndefinedDerivedState = new Set();
|
|
didWarnAboutInvalidateContextType = new Set();
|
|
var didWarnOnInvalidCallback = new Set();
|
|
|
|
warnOnInvalidCallback = function(callback, callerName) {
|
|
if (callback === null || typeof callback === "function") {
|
|
return;
|
|
}
|
|
|
|
var key = callerName + "_" + callback;
|
|
|
|
if (!didWarnOnInvalidCallback.has(key)) {
|
|
didWarnOnInvalidCallback.add(key);
|
|
|
|
error(
|
|
"%s(...): Expected the last optional `callback` argument to be a " +
|
|
"function. Instead received: %s.",
|
|
callerName,
|
|
callback
|
|
);
|
|
}
|
|
};
|
|
|
|
warnOnUndefinedDerivedState = function(type, partialState) {
|
|
if (partialState === undefined) {
|
|
var componentName = getComponentNameFromType(type) || "Component";
|
|
|
|
if (!didWarnAboutUndefinedDerivedState.has(componentName)) {
|
|
didWarnAboutUndefinedDerivedState.add(componentName);
|
|
|
|
error(
|
|
"%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. " +
|
|
"You have returned undefined.",
|
|
componentName
|
|
);
|
|
}
|
|
}
|
|
}; // This is so gross but it's at least non-critical and can be removed if
|
|
// it causes problems. This is meant to give a nicer error message for
|
|
// ReactDOM15.unstable_renderSubtreeIntoContainer(reactDOM16Component,
|
|
// ...)) which otherwise throws a "_processChildContext is not a function"
|
|
// exception.
|
|
|
|
Object.defineProperty(fakeInternalInstance, "_processChildContext", {
|
|
enumerable: false,
|
|
value: function() {
|
|
throw new Error(
|
|
"_processChildContext is not available in React 16+. This likely " +
|
|
"means you have multiple copies of React and are attempting to nest " +
|
|
"a React 15 tree inside a React 16 tree using " +
|
|
"unstable_renderSubtreeIntoContainer, which isn't supported. Try " +
|
|
"to make sure you have only one copy of React (and ideally, switch " +
|
|
"to ReactDOM.createPortal)."
|
|
);
|
|
}
|
|
});
|
|
Object.freeze(fakeInternalInstance);
|
|
}
|
|
|
|
function applyDerivedStateFromProps(
|
|
workInProgress,
|
|
ctor,
|
|
getDerivedStateFromProps,
|
|
nextProps
|
|
) {
|
|
var prevState = workInProgress.memoizedState;
|
|
var partialState = getDerivedStateFromProps(nextProps, prevState);
|
|
|
|
{
|
|
warnOnUndefinedDerivedState(ctor, partialState);
|
|
} // Merge the partial state and the previous state.
|
|
|
|
var memoizedState =
|
|
partialState === null || partialState === undefined
|
|
? prevState
|
|
: assign({}, prevState, partialState);
|
|
workInProgress.memoizedState = memoizedState; // Once the update queue is empty, persist the derived state onto the
|
|
// base state.
|
|
|
|
if (workInProgress.lanes === NoLanes) {
|
|
// Queue is always non-null for classes
|
|
var updateQueue = workInProgress.updateQueue;
|
|
updateQueue.baseState = memoizedState;
|
|
}
|
|
}
|
|
|
|
var classComponentUpdater = {
|
|
isMounted: isMounted,
|
|
// $FlowFixMe[missing-local-annot]
|
|
enqueueSetState: function(inst, payload, callback) {
|
|
var fiber = get(inst);
|
|
var eventTime = requestEventTime();
|
|
var lane = requestUpdateLane(fiber);
|
|
var update = createUpdate(eventTime, lane);
|
|
update.payload = payload;
|
|
|
|
if (callback !== undefined && callback !== null) {
|
|
{
|
|
warnOnInvalidCallback(callback, "setState");
|
|
}
|
|
|
|
update.callback = callback;
|
|
}
|
|
|
|
var root = enqueueUpdate$1(fiber, update, lane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
|
entangleTransitions(root, fiber, lane);
|
|
}
|
|
},
|
|
enqueueReplaceState: function(inst, payload, callback) {
|
|
var fiber = get(inst);
|
|
var eventTime = requestEventTime();
|
|
var lane = requestUpdateLane(fiber);
|
|
var update = createUpdate(eventTime, lane);
|
|
update.tag = ReplaceState;
|
|
update.payload = payload;
|
|
|
|
if (callback !== undefined && callback !== null) {
|
|
{
|
|
warnOnInvalidCallback(callback, "replaceState");
|
|
}
|
|
|
|
update.callback = callback;
|
|
}
|
|
|
|
var root = enqueueUpdate$1(fiber, update, lane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
|
entangleTransitions(root, fiber, lane);
|
|
}
|
|
},
|
|
// $FlowFixMe[missing-local-annot]
|
|
enqueueForceUpdate: function(inst, callback) {
|
|
var fiber = get(inst);
|
|
var eventTime = requestEventTime();
|
|
var lane = requestUpdateLane(fiber);
|
|
var update = createUpdate(eventTime, lane);
|
|
update.tag = ForceUpdate;
|
|
|
|
if (callback !== undefined && callback !== null) {
|
|
{
|
|
warnOnInvalidCallback(callback, "forceUpdate");
|
|
}
|
|
|
|
update.callback = callback;
|
|
}
|
|
|
|
var root = enqueueUpdate$1(fiber, update, lane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
|
entangleTransitions(root, fiber, lane);
|
|
}
|
|
}
|
|
};
|
|
|
|
function checkShouldComponentUpdate(
|
|
workInProgress,
|
|
ctor,
|
|
oldProps,
|
|
newProps,
|
|
oldState,
|
|
newState,
|
|
nextContext
|
|
) {
|
|
var instance = workInProgress.stateNode;
|
|
|
|
if (typeof instance.shouldComponentUpdate === "function") {
|
|
var shouldUpdate = instance.shouldComponentUpdate(
|
|
newProps,
|
|
newState,
|
|
nextContext
|
|
);
|
|
|
|
{
|
|
if (shouldUpdate === undefined) {
|
|
error(
|
|
"%s.shouldComponentUpdate(): Returned undefined instead of a " +
|
|
"boolean value. Make sure to return true or false.",
|
|
getComponentNameFromType(ctor) || "Component"
|
|
);
|
|
}
|
|
}
|
|
|
|
return shouldUpdate;
|
|
}
|
|
|
|
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
|
|
return (
|
|
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function checkClassInstance(workInProgress, ctor, newProps) {
|
|
var instance = workInProgress.stateNode;
|
|
|
|
{
|
|
var name = getComponentNameFromType(ctor) || "Component";
|
|
var renderPresent = instance.render;
|
|
|
|
if (!renderPresent) {
|
|
if (ctor.prototype && typeof ctor.prototype.render === "function") {
|
|
error(
|
|
"%s(...): No `render` method found on the returned component " +
|
|
"instance: did you accidentally return an object from the constructor?",
|
|
name
|
|
);
|
|
} else {
|
|
error(
|
|
"%s(...): No `render` method found on the returned component " +
|
|
"instance: you may have forgotten to define `render`.",
|
|
name
|
|
);
|
|
}
|
|
}
|
|
|
|
if (
|
|
instance.getInitialState &&
|
|
!instance.getInitialState.isReactClassApproved &&
|
|
!instance.state
|
|
) {
|
|
error(
|
|
"getInitialState was defined on %s, a plain JavaScript class. " +
|
|
"This is only supported for classes created using React.createClass. " +
|
|
"Did you mean to define a state property instead?",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (
|
|
instance.getDefaultProps &&
|
|
!instance.getDefaultProps.isReactClassApproved
|
|
) {
|
|
error(
|
|
"getDefaultProps was defined on %s, a plain JavaScript class. " +
|
|
"This is only supported for classes created using React.createClass. " +
|
|
"Use a static property to define defaultProps instead.",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (instance.propTypes) {
|
|
error(
|
|
"propTypes was defined as an instance property on %s. Use a static " +
|
|
"property to define propTypes instead.",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (instance.contextType) {
|
|
error(
|
|
"contextType was defined as an instance property on %s. Use a static " +
|
|
"property to define contextType instead.",
|
|
name
|
|
);
|
|
}
|
|
|
|
{
|
|
if (ctor.childContextTypes) {
|
|
error(
|
|
"%s uses the legacy childContextTypes API which is no longer supported. " +
|
|
"Use React.createContext() instead.",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (ctor.contextTypes) {
|
|
error(
|
|
"%s uses the legacy contextTypes API which is no longer supported. " +
|
|
"Use React.createContext() with static contextType instead.",
|
|
name
|
|
);
|
|
}
|
|
}
|
|
|
|
if (typeof instance.componentShouldUpdate === "function") {
|
|
error(
|
|
"%s has a method called " +
|
|
"componentShouldUpdate(). Did you mean shouldComponentUpdate()? " +
|
|
"The name is phrased as a question because the function is " +
|
|
"expected to return a value.",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (
|
|
ctor.prototype &&
|
|
ctor.prototype.isPureReactComponent &&
|
|
typeof instance.shouldComponentUpdate !== "undefined"
|
|
) {
|
|
error(
|
|
"%s has a method called shouldComponentUpdate(). " +
|
|
"shouldComponentUpdate should not be used when extending React.PureComponent. " +
|
|
"Please extend React.Component if shouldComponentUpdate is used.",
|
|
getComponentNameFromType(ctor) || "A pure component"
|
|
);
|
|
}
|
|
|
|
if (typeof instance.componentDidUnmount === "function") {
|
|
error(
|
|
"%s has a method called " +
|
|
"componentDidUnmount(). But there is no such lifecycle method. " +
|
|
"Did you mean componentWillUnmount()?",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (typeof instance.componentDidReceiveProps === "function") {
|
|
error(
|
|
"%s has a method called " +
|
|
"componentDidReceiveProps(). But there is no such lifecycle method. " +
|
|
"If you meant to update the state in response to changing props, " +
|
|
"use componentWillReceiveProps(). If you meant to fetch data or " +
|
|
"run side-effects or mutations after React has updated the UI, use componentDidUpdate().",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (typeof instance.componentWillRecieveProps === "function") {
|
|
error(
|
|
"%s has a method called " +
|
|
"componentWillRecieveProps(). Did you mean componentWillReceiveProps()?",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (typeof instance.UNSAFE_componentWillRecieveProps === "function") {
|
|
error(
|
|
"%s has a method called " +
|
|
"UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?",
|
|
name
|
|
);
|
|
}
|
|
|
|
var hasMutatedProps = instance.props !== newProps;
|
|
|
|
if (instance.props !== undefined && hasMutatedProps) {
|
|
error(
|
|
"%s(...): When calling super() in `%s`, make sure to pass " +
|
|
"up the same props that your component's constructor was passed.",
|
|
name,
|
|
name
|
|
);
|
|
}
|
|
|
|
if (instance.defaultProps) {
|
|
error(
|
|
"Setting defaultProps as an instance property on %s is not supported and will be ignored." +
|
|
" Instead, define defaultProps as a static property on %s.",
|
|
name,
|
|
name
|
|
);
|
|
}
|
|
|
|
if (
|
|
typeof instance.getSnapshotBeforeUpdate === "function" &&
|
|
typeof instance.componentDidUpdate !== "function" &&
|
|
!didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(ctor)
|
|
) {
|
|
didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(ctor);
|
|
|
|
error(
|
|
"%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). " +
|
|
"This component defines getSnapshotBeforeUpdate() only.",
|
|
getComponentNameFromType(ctor)
|
|
);
|
|
}
|
|
|
|
if (typeof instance.getDerivedStateFromProps === "function") {
|
|
error(
|
|
"%s: getDerivedStateFromProps() is defined as an instance method " +
|
|
"and will be ignored. Instead, declare it as a static method.",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (typeof instance.getDerivedStateFromError === "function") {
|
|
error(
|
|
"%s: getDerivedStateFromError() is defined as an instance method " +
|
|
"and will be ignored. Instead, declare it as a static method.",
|
|
name
|
|
);
|
|
}
|
|
|
|
if (typeof ctor.getSnapshotBeforeUpdate === "function") {
|
|
error(
|
|
"%s: getSnapshotBeforeUpdate() is defined as a static method " +
|
|
"and will be ignored. Instead, declare it as an instance method.",
|
|
name
|
|
);
|
|
}
|
|
|
|
var state = instance.state;
|
|
|
|
if (state && (typeof state !== "object" || isArray(state))) {
|
|
error("%s.state: must be set to an object or null", name);
|
|
}
|
|
|
|
if (
|
|
typeof instance.getChildContext === "function" &&
|
|
typeof ctor.childContextTypes !== "object"
|
|
) {
|
|
error(
|
|
"%s.getChildContext(): childContextTypes must be defined in order to " +
|
|
"use getChildContext().",
|
|
name
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function adoptClassInstance(workInProgress, instance) {
|
|
instance.updater = classComponentUpdater;
|
|
workInProgress.stateNode = instance; // The instance needs access to the fiber so that it can schedule updates
|
|
|
|
set(instance, workInProgress);
|
|
|
|
{
|
|
instance._reactInternalInstance = fakeInternalInstance;
|
|
}
|
|
}
|
|
|
|
function constructClassInstance(workInProgress, ctor, props) {
|
|
var context = emptyContextObject;
|
|
var contextType = ctor.contextType;
|
|
|
|
{
|
|
if ("contextType" in ctor) {
|
|
var isValid = // Allow null for conditional declaration
|
|
contextType === null ||
|
|
(contextType !== undefined &&
|
|
contextType.$$typeof === REACT_CONTEXT_TYPE &&
|
|
contextType._context === undefined); // Not a <Context.Consumer>
|
|
|
|
if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
|
|
didWarnAboutInvalidateContextType.add(ctor);
|
|
var addendum = "";
|
|
|
|
if (contextType === undefined) {
|
|
addendum =
|
|
" However, it is set to undefined. " +
|
|
"This can be caused by a typo or by mixing up named and default imports. " +
|
|
"This can also happen due to a circular dependency, so " +
|
|
"try moving the createContext() call to a separate file.";
|
|
} else if (typeof contextType !== "object") {
|
|
addendum = " However, it is set to a " + typeof contextType + ".";
|
|
} else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
|
|
addendum = " Did you accidentally pass the Context.Provider instead?";
|
|
} else if (contextType._context !== undefined) {
|
|
// <Context.Consumer>
|
|
addendum = " Did you accidentally pass the Context.Consumer instead?";
|
|
} else {
|
|
addendum =
|
|
" However, it is set to an object with keys {" +
|
|
Object.keys(contextType).join(", ") +
|
|
"}.";
|
|
}
|
|
|
|
error(
|
|
"%s defines an invalid contextType. " +
|
|
"contextType should point to the Context object returned by React.createContext().%s",
|
|
getComponentNameFromType(ctor) || "Component",
|
|
addendum
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof contextType === "object" && contextType !== null) {
|
|
context = readContext(contextType);
|
|
}
|
|
|
|
var instance = new ctor(props, context); // Instantiate twice to help detect side-effects.
|
|
|
|
var state = (workInProgress.memoizedState =
|
|
instance.state !== null && instance.state !== undefined
|
|
? instance.state
|
|
: null);
|
|
adoptClassInstance(workInProgress, instance);
|
|
|
|
{
|
|
if (typeof ctor.getDerivedStateFromProps === "function" && state === null) {
|
|
var componentName = getComponentNameFromType(ctor) || "Component";
|
|
|
|
if (!didWarnAboutUninitializedState.has(componentName)) {
|
|
didWarnAboutUninitializedState.add(componentName);
|
|
|
|
error(
|
|
"`%s` uses `getDerivedStateFromProps` but its initial state is " +
|
|
"%s. This is not recommended. Instead, define the initial state by " +
|
|
"assigning an object to `this.state` in the constructor of `%s`. " +
|
|
"This ensures that `getDerivedStateFromProps` arguments have a consistent shape.",
|
|
componentName,
|
|
instance.state === null ? "null" : "undefined",
|
|
componentName
|
|
);
|
|
}
|
|
} // If new component APIs are defined, "unsafe" lifecycles won't be called.
|
|
// Warn about these lifecycles if they are present.
|
|
// Don't warn about react-lifecycles-compat polyfilled methods though.
|
|
|
|
if (
|
|
typeof ctor.getDerivedStateFromProps === "function" ||
|
|
typeof instance.getSnapshotBeforeUpdate === "function"
|
|
) {
|
|
var foundWillMountName = null;
|
|
var foundWillReceivePropsName = null;
|
|
var foundWillUpdateName = null;
|
|
|
|
if (
|
|
typeof instance.componentWillMount === "function" &&
|
|
instance.componentWillMount.__suppressDeprecationWarning !== true
|
|
) {
|
|
foundWillMountName = "componentWillMount";
|
|
} else if (typeof instance.UNSAFE_componentWillMount === "function") {
|
|
foundWillMountName = "UNSAFE_componentWillMount";
|
|
}
|
|
|
|
if (
|
|
typeof instance.componentWillReceiveProps === "function" &&
|
|
instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
|
|
) {
|
|
foundWillReceivePropsName = "componentWillReceiveProps";
|
|
} else if (
|
|
typeof instance.UNSAFE_componentWillReceiveProps === "function"
|
|
) {
|
|
foundWillReceivePropsName = "UNSAFE_componentWillReceiveProps";
|
|
}
|
|
|
|
if (
|
|
typeof instance.componentWillUpdate === "function" &&
|
|
instance.componentWillUpdate.__suppressDeprecationWarning !== true
|
|
) {
|
|
foundWillUpdateName = "componentWillUpdate";
|
|
} else if (typeof instance.UNSAFE_componentWillUpdate === "function") {
|
|
foundWillUpdateName = "UNSAFE_componentWillUpdate";
|
|
}
|
|
|
|
if (
|
|
foundWillMountName !== null ||
|
|
foundWillReceivePropsName !== null ||
|
|
foundWillUpdateName !== null
|
|
) {
|
|
var _componentName = getComponentNameFromType(ctor) || "Component";
|
|
|
|
var newApiName =
|
|
typeof ctor.getDerivedStateFromProps === "function"
|
|
? "getDerivedStateFromProps()"
|
|
: "getSnapshotBeforeUpdate()";
|
|
|
|
if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(_componentName)) {
|
|
didWarnAboutLegacyLifecyclesAndDerivedState.add(_componentName);
|
|
|
|
error(
|
|
"Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n" +
|
|
"%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n" +
|
|
"The above lifecycles should be removed. Learn more about this warning here:\n" +
|
|
"https://reactjs.org/link/unsafe-component-lifecycles",
|
|
_componentName,
|
|
newApiName,
|
|
foundWillMountName !== null ? "\n " + foundWillMountName : "",
|
|
foundWillReceivePropsName !== null
|
|
? "\n " + foundWillReceivePropsName
|
|
: "",
|
|
foundWillUpdateName !== null ? "\n " + foundWillUpdateName : ""
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} // Cache unmasked context so we can avoid recreating masked context unless necessary.
|
|
|
|
return instance;
|
|
}
|
|
|
|
function callComponentWillMount(workInProgress, instance) {
|
|
var oldState = instance.state;
|
|
|
|
if (typeof instance.componentWillMount === "function") {
|
|
instance.componentWillMount();
|
|
}
|
|
|
|
if (typeof instance.UNSAFE_componentWillMount === "function") {
|
|
instance.UNSAFE_componentWillMount();
|
|
}
|
|
|
|
if (oldState !== instance.state) {
|
|
{
|
|
error(
|
|
"%s.componentWillMount(): Assigning directly to this.state is " +
|
|
"deprecated (except inside a component's " +
|
|
"constructor). Use setState instead.",
|
|
getComponentNameFromFiber(workInProgress) || "Component"
|
|
);
|
|
}
|
|
|
|
classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
|
|
}
|
|
}
|
|
|
|
function callComponentWillReceiveProps(
|
|
workInProgress,
|
|
instance,
|
|
newProps,
|
|
nextContext
|
|
) {
|
|
var oldState = instance.state;
|
|
|
|
if (typeof instance.componentWillReceiveProps === "function") {
|
|
instance.componentWillReceiveProps(newProps, nextContext);
|
|
}
|
|
|
|
if (typeof instance.UNSAFE_componentWillReceiveProps === "function") {
|
|
instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
|
|
}
|
|
|
|
if (instance.state !== oldState) {
|
|
{
|
|
var componentName =
|
|
getComponentNameFromFiber(workInProgress) || "Component";
|
|
|
|
if (!didWarnAboutStateAssignmentForComponent.has(componentName)) {
|
|
didWarnAboutStateAssignmentForComponent.add(componentName);
|
|
|
|
error(
|
|
"%s.componentWillReceiveProps(): Assigning directly to " +
|
|
"this.state is deprecated (except inside a component's " +
|
|
"constructor). Use setState instead.",
|
|
componentName
|
|
);
|
|
}
|
|
}
|
|
|
|
classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
|
|
}
|
|
} // Invokes the mount life-cycles on a previously never rendered instance.
|
|
|
|
function mountClassInstance(workInProgress, ctor, newProps, renderLanes) {
|
|
{
|
|
checkClassInstance(workInProgress, ctor, newProps);
|
|
}
|
|
|
|
var instance = workInProgress.stateNode;
|
|
instance.props = newProps;
|
|
instance.state = workInProgress.memoizedState;
|
|
instance.refs = {};
|
|
initializeUpdateQueue(workInProgress);
|
|
var contextType = ctor.contextType;
|
|
|
|
if (typeof contextType === "object" && contextType !== null) {
|
|
instance.context = readContext(contextType);
|
|
} else {
|
|
instance.context = emptyContextObject;
|
|
}
|
|
|
|
{
|
|
if (instance.state === newProps) {
|
|
var componentName = getComponentNameFromType(ctor) || "Component";
|
|
|
|
if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) {
|
|
didWarnAboutDirectlyAssigningPropsToState.add(componentName);
|
|
|
|
error(
|
|
"%s: It is not recommended to assign props directly to state " +
|
|
"because updates to props won't be reflected in state. " +
|
|
"In most cases, it is better to use props directly.",
|
|
componentName
|
|
);
|
|
}
|
|
}
|
|
|
|
if (workInProgress.mode & StrictLegacyMode) {
|
|
ReactStrictModeWarnings.recordLegacyContextWarning(
|
|
workInProgress,
|
|
instance
|
|
);
|
|
}
|
|
|
|
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
|
|
workInProgress,
|
|
instance
|
|
);
|
|
}
|
|
|
|
instance.state = workInProgress.memoizedState;
|
|
var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
|
|
|
|
if (typeof getDerivedStateFromProps === "function") {
|
|
applyDerivedStateFromProps(
|
|
workInProgress,
|
|
ctor,
|
|
getDerivedStateFromProps,
|
|
newProps
|
|
);
|
|
instance.state = workInProgress.memoizedState;
|
|
} // In order to support react-lifecycles-compat polyfilled components,
|
|
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
|
|
|
if (
|
|
typeof ctor.getDerivedStateFromProps !== "function" &&
|
|
typeof instance.getSnapshotBeforeUpdate !== "function" &&
|
|
(typeof instance.UNSAFE_componentWillMount === "function" ||
|
|
typeof instance.componentWillMount === "function")
|
|
) {
|
|
callComponentWillMount(workInProgress, instance); // If we had additional state updates during this life-cycle, let's
|
|
// process them now.
|
|
|
|
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
|
|
instance.state = workInProgress.memoizedState;
|
|
}
|
|
|
|
if (typeof instance.componentDidMount === "function") {
|
|
var fiberFlags = Update | LayoutStatic;
|
|
|
|
if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
|
|
fiberFlags |= MountLayoutDev;
|
|
}
|
|
|
|
workInProgress.flags |= fiberFlags;
|
|
}
|
|
}
|
|
|
|
function resumeMountClassInstance(workInProgress, ctor, newProps, renderLanes) {
|
|
var instance = workInProgress.stateNode;
|
|
var oldProps = workInProgress.memoizedProps;
|
|
instance.props = oldProps;
|
|
var oldContext = instance.context;
|
|
var contextType = ctor.contextType;
|
|
var nextContext = emptyContextObject;
|
|
|
|
if (typeof contextType === "object" && contextType !== null) {
|
|
nextContext = readContext(contextType);
|
|
}
|
|
|
|
var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
|
|
var hasNewLifecycles =
|
|
typeof getDerivedStateFromProps === "function" ||
|
|
typeof instance.getSnapshotBeforeUpdate === "function"; // Note: During these life-cycles, instance.props/instance.state are what
|
|
// ever the previously attempted to render - not the "current". However,
|
|
// during componentDidUpdate we pass the "current" props.
|
|
// In order to support react-lifecycles-compat polyfilled components,
|
|
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
|
|
|
if (
|
|
!hasNewLifecycles &&
|
|
(typeof instance.UNSAFE_componentWillReceiveProps === "function" ||
|
|
typeof instance.componentWillReceiveProps === "function")
|
|
) {
|
|
if (oldProps !== newProps || oldContext !== nextContext) {
|
|
callComponentWillReceiveProps(
|
|
workInProgress,
|
|
instance,
|
|
newProps,
|
|
nextContext
|
|
);
|
|
}
|
|
}
|
|
|
|
resetHasForceUpdateBeforeProcessing();
|
|
var oldState = workInProgress.memoizedState;
|
|
var newState = (instance.state = oldState);
|
|
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
|
|
newState = workInProgress.memoizedState;
|
|
|
|
if (
|
|
oldProps === newProps &&
|
|
oldState === newState &&
|
|
!hasContextChanged() &&
|
|
!checkHasForceUpdateAfterProcessing()
|
|
) {
|
|
// If an update was already in progress, we should schedule an Update
|
|
// effect even though we're bailing out, so that cWU/cDU are called.
|
|
if (typeof instance.componentDidMount === "function") {
|
|
var fiberFlags = Update | LayoutStatic;
|
|
|
|
if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
|
|
fiberFlags |= MountLayoutDev;
|
|
}
|
|
|
|
workInProgress.flags |= fiberFlags;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (typeof getDerivedStateFromProps === "function") {
|
|
applyDerivedStateFromProps(
|
|
workInProgress,
|
|
ctor,
|
|
getDerivedStateFromProps,
|
|
newProps
|
|
);
|
|
newState = workInProgress.memoizedState;
|
|
}
|
|
|
|
var shouldUpdate =
|
|
checkHasForceUpdateAfterProcessing() ||
|
|
checkShouldComponentUpdate(
|
|
workInProgress,
|
|
ctor,
|
|
oldProps,
|
|
newProps,
|
|
oldState,
|
|
newState,
|
|
nextContext
|
|
);
|
|
|
|
if (shouldUpdate) {
|
|
// In order to support react-lifecycles-compat polyfilled components,
|
|
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
|
if (
|
|
!hasNewLifecycles &&
|
|
(typeof instance.UNSAFE_componentWillMount === "function" ||
|
|
typeof instance.componentWillMount === "function")
|
|
) {
|
|
if (typeof instance.componentWillMount === "function") {
|
|
instance.componentWillMount();
|
|
}
|
|
|
|
if (typeof instance.UNSAFE_componentWillMount === "function") {
|
|
instance.UNSAFE_componentWillMount();
|
|
}
|
|
}
|
|
|
|
if (typeof instance.componentDidMount === "function") {
|
|
var _fiberFlags = Update | LayoutStatic;
|
|
|
|
if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
|
|
_fiberFlags |= MountLayoutDev;
|
|
}
|
|
|
|
workInProgress.flags |= _fiberFlags;
|
|
}
|
|
} else {
|
|
// If an update was already in progress, we should schedule an Update
|
|
// effect even though we're bailing out, so that cWU/cDU are called.
|
|
if (typeof instance.componentDidMount === "function") {
|
|
var _fiberFlags2 = Update | LayoutStatic;
|
|
|
|
if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
|
|
_fiberFlags2 |= MountLayoutDev;
|
|
}
|
|
|
|
workInProgress.flags |= _fiberFlags2;
|
|
} // If shouldComponentUpdate returned false, we should still update the
|
|
// memoized state to indicate that this work can be reused.
|
|
|
|
workInProgress.memoizedProps = newProps;
|
|
workInProgress.memoizedState = newState;
|
|
} // Update the existing instance's state, props, and context pointers even
|
|
// if shouldComponentUpdate returns false.
|
|
|
|
instance.props = newProps;
|
|
instance.state = newState;
|
|
instance.context = nextContext;
|
|
return shouldUpdate;
|
|
} // Invokes the update life-cycles and returns false if it shouldn't rerender.
|
|
|
|
function updateClassInstance(
|
|
current,
|
|
workInProgress,
|
|
ctor,
|
|
newProps,
|
|
renderLanes
|
|
) {
|
|
var instance = workInProgress.stateNode;
|
|
cloneUpdateQueue(current, workInProgress);
|
|
var unresolvedOldProps = workInProgress.memoizedProps;
|
|
var oldProps =
|
|
workInProgress.type === workInProgress.elementType
|
|
? unresolvedOldProps
|
|
: resolveDefaultProps(workInProgress.type, unresolvedOldProps);
|
|
instance.props = oldProps;
|
|
var unresolvedNewProps = workInProgress.pendingProps;
|
|
var oldContext = instance.context;
|
|
var contextType = ctor.contextType;
|
|
var nextContext = emptyContextObject;
|
|
|
|
if (typeof contextType === "object" && contextType !== null) {
|
|
nextContext = readContext(contextType);
|
|
}
|
|
|
|
var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
|
|
var hasNewLifecycles =
|
|
typeof getDerivedStateFromProps === "function" ||
|
|
typeof instance.getSnapshotBeforeUpdate === "function"; // Note: During these life-cycles, instance.props/instance.state are what
|
|
// ever the previously attempted to render - not the "current". However,
|
|
// during componentDidUpdate we pass the "current" props.
|
|
// In order to support react-lifecycles-compat polyfilled components,
|
|
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
|
|
|
if (
|
|
!hasNewLifecycles &&
|
|
(typeof instance.UNSAFE_componentWillReceiveProps === "function" ||
|
|
typeof instance.componentWillReceiveProps === "function")
|
|
) {
|
|
if (
|
|
unresolvedOldProps !== unresolvedNewProps ||
|
|
oldContext !== nextContext
|
|
) {
|
|
callComponentWillReceiveProps(
|
|
workInProgress,
|
|
instance,
|
|
newProps,
|
|
nextContext
|
|
);
|
|
}
|
|
}
|
|
|
|
resetHasForceUpdateBeforeProcessing();
|
|
var oldState = workInProgress.memoizedState;
|
|
var newState = (instance.state = oldState);
|
|
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
|
|
newState = workInProgress.memoizedState;
|
|
|
|
if (
|
|
unresolvedOldProps === unresolvedNewProps &&
|
|
oldState === newState &&
|
|
!hasContextChanged() &&
|
|
!checkHasForceUpdateAfterProcessing() &&
|
|
!enableLazyContextPropagation
|
|
) {
|
|
// If an update was already in progress, we should schedule an Update
|
|
// effect even though we're bailing out, so that cWU/cDU are called.
|
|
if (typeof instance.componentDidUpdate === "function") {
|
|
if (
|
|
unresolvedOldProps !== current.memoizedProps ||
|
|
oldState !== current.memoizedState
|
|
) {
|
|
workInProgress.flags |= Update;
|
|
}
|
|
}
|
|
|
|
if (typeof instance.getSnapshotBeforeUpdate === "function") {
|
|
if (
|
|
unresolvedOldProps !== current.memoizedProps ||
|
|
oldState !== current.memoizedState
|
|
) {
|
|
workInProgress.flags |= Snapshot;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (typeof getDerivedStateFromProps === "function") {
|
|
applyDerivedStateFromProps(
|
|
workInProgress,
|
|
ctor,
|
|
getDerivedStateFromProps,
|
|
newProps
|
|
);
|
|
newState = workInProgress.memoizedState;
|
|
}
|
|
|
|
var shouldUpdate =
|
|
checkHasForceUpdateAfterProcessing() ||
|
|
checkShouldComponentUpdate(
|
|
workInProgress,
|
|
ctor,
|
|
oldProps,
|
|
newProps,
|
|
oldState,
|
|
newState,
|
|
nextContext
|
|
) || // TODO: In some cases, we'll end up checking if context has changed twice,
|
|
// both before and after `shouldComponentUpdate` has been called. Not ideal,
|
|
// but I'm loath to refactor this function. This only happens for memoized
|
|
// components so it's not that common.
|
|
enableLazyContextPropagation;
|
|
|
|
if (shouldUpdate) {
|
|
// In order to support react-lifecycles-compat polyfilled components,
|
|
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
|
if (
|
|
!hasNewLifecycles &&
|
|
(typeof instance.UNSAFE_componentWillUpdate === "function" ||
|
|
typeof instance.componentWillUpdate === "function")
|
|
) {
|
|
if (typeof instance.componentWillUpdate === "function") {
|
|
instance.componentWillUpdate(newProps, newState, nextContext);
|
|
}
|
|
|
|
if (typeof instance.UNSAFE_componentWillUpdate === "function") {
|
|
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
|
|
}
|
|
}
|
|
|
|
if (typeof instance.componentDidUpdate === "function") {
|
|
workInProgress.flags |= Update;
|
|
}
|
|
|
|
if (typeof instance.getSnapshotBeforeUpdate === "function") {
|
|
workInProgress.flags |= Snapshot;
|
|
}
|
|
} else {
|
|
// If an update was already in progress, we should schedule an Update
|
|
// effect even though we're bailing out, so that cWU/cDU are called.
|
|
if (typeof instance.componentDidUpdate === "function") {
|
|
if (
|
|
unresolvedOldProps !== current.memoizedProps ||
|
|
oldState !== current.memoizedState
|
|
) {
|
|
workInProgress.flags |= Update;
|
|
}
|
|
}
|
|
|
|
if (typeof instance.getSnapshotBeforeUpdate === "function") {
|
|
if (
|
|
unresolvedOldProps !== current.memoizedProps ||
|
|
oldState !== current.memoizedState
|
|
) {
|
|
workInProgress.flags |= Snapshot;
|
|
}
|
|
} // If shouldComponentUpdate returned false, we should still update the
|
|
// memoized props/state to indicate that this work can be reused.
|
|
|
|
workInProgress.memoizedProps = newProps;
|
|
workInProgress.memoizedState = newState;
|
|
} // Update the existing instance's state, props, and context pointers even
|
|
// if shouldComponentUpdate returns false.
|
|
|
|
instance.props = newProps;
|
|
instance.state = newState;
|
|
instance.context = nextContext;
|
|
return shouldUpdate;
|
|
}
|
|
|
|
function createCapturedValueAtFiber(value, source) {
|
|
// If the value is an error, call this function immediately after it is thrown
|
|
// so the stack is accurate.
|
|
return {
|
|
value: value,
|
|
source: source,
|
|
stack: getStackByFiberInDevAndProd(source),
|
|
digest: null
|
|
};
|
|
}
|
|
function createCapturedValue(value, digest, stack) {
|
|
return {
|
|
value: value,
|
|
source: null,
|
|
stack: stack != null ? stack : null,
|
|
digest: digest != null ? digest : null
|
|
};
|
|
}
|
|
|
|
var ReactFiberErrorDialogWWW = require("ReactFiberErrorDialog");
|
|
|
|
if (typeof ReactFiberErrorDialogWWW.showErrorDialog !== "function") {
|
|
throw new Error(
|
|
"Expected ReactFiberErrorDialog.showErrorDialog to be a function."
|
|
);
|
|
}
|
|
|
|
function showErrorDialog(boundary, errorInfo) {
|
|
var capturedError = {
|
|
componentStack: errorInfo.stack !== null ? errorInfo.stack : "",
|
|
error: errorInfo.value,
|
|
errorBoundary:
|
|
boundary !== null && boundary.tag === ClassComponent
|
|
? boundary.stateNode
|
|
: null
|
|
};
|
|
return ReactFiberErrorDialogWWW.showErrorDialog(capturedError);
|
|
}
|
|
|
|
function logCapturedError(boundary, errorInfo) {
|
|
try {
|
|
var logError = showErrorDialog(boundary, errorInfo); // Allow injected showErrorDialog() to prevent default console.error logging.
|
|
// This enables renderers like ReactNative to better manage redbox behavior.
|
|
|
|
if (logError === false) {
|
|
return;
|
|
}
|
|
|
|
var error = errorInfo.value;
|
|
|
|
if (true) {
|
|
var source = errorInfo.source;
|
|
var stack = errorInfo.stack;
|
|
var componentStack = stack !== null ? stack : ""; // Browsers support silencing uncaught errors by calling
|
|
// `preventDefault()` in window `error` handler.
|
|
// We record this information as an expando on the error.
|
|
|
|
if (error != null && error._suppressLogging) {
|
|
if (boundary.tag === ClassComponent) {
|
|
// The error is recoverable and was silenced.
|
|
// Ignore it and don't print the stack addendum.
|
|
// This is handy for testing error boundaries without noise.
|
|
return;
|
|
} // The error is fatal. Since the silencing might have
|
|
// been accidental, we'll surface it anyway.
|
|
// However, the browser would have silenced the original error
|
|
// so we'll print it first, and then print the stack addendum.
|
|
|
|
console["error"](error); // Don't transform to our wrapper
|
|
// For a more detailed description of this block, see:
|
|
// https://github.com/facebook/react/pull/13384
|
|
}
|
|
|
|
var componentName = source ? getComponentNameFromFiber(source) : null;
|
|
var componentNameMessage = componentName
|
|
? "The above error occurred in the <" + componentName + "> component:"
|
|
: "The above error occurred in one of your React components:";
|
|
var errorBoundaryMessage;
|
|
|
|
if (boundary.tag === HostRoot) {
|
|
errorBoundaryMessage =
|
|
"Consider adding an error boundary to your tree to customize error handling behavior.\n" +
|
|
"Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.";
|
|
} else {
|
|
var errorBoundaryName =
|
|
getComponentNameFromFiber(boundary) || "Anonymous";
|
|
errorBoundaryMessage =
|
|
"React will try to recreate this component tree from scratch " +
|
|
("using the error boundary you provided, " + errorBoundaryName + ".");
|
|
}
|
|
|
|
var combinedMessage =
|
|
componentNameMessage +
|
|
"\n" +
|
|
componentStack +
|
|
"\n\n" +
|
|
("" + errorBoundaryMessage); // In development, we provide our own message with just the component stack.
|
|
// We don't include the original error message and JS stack because the browser
|
|
// has already printed it. Even if the application swallows the error, it is still
|
|
// displayed by the browser thanks to the DEV-only fake event trick in ReactErrorUtils.
|
|
|
|
console["error"](combinedMessage); // Don't transform to our wrapper
|
|
} else {
|
|
// In production, we print the error directly.
|
|
// This will include the message, the JS stack, and anything the browser wants to show.
|
|
// We pass the error object instead of custom message so that the browser displays the error natively.
|
|
console["error"](error); // Don't transform to our wrapper
|
|
}
|
|
} catch (e) {
|
|
// This method must not throw, or React internal state will get messed up.
|
|
// If console.error is overridden, or logCapturedError() shows a dialog that throws,
|
|
// we want to report this error outside of the normal stack as a last resort.
|
|
// https://github.com/facebook/react/issues/13188
|
|
setTimeout(function() {
|
|
throw e;
|
|
});
|
|
}
|
|
}
|
|
|
|
function createRootErrorUpdate(fiber, errorInfo, lane) {
|
|
var update = createUpdate(NoTimestamp, lane); // Unmount the root by rendering null.
|
|
|
|
update.tag = CaptureUpdate; // Caution: React DevTools currently depends on this property
|
|
// being called "element".
|
|
|
|
update.payload = {
|
|
element: null
|
|
};
|
|
var error = errorInfo.value;
|
|
|
|
update.callback = function() {
|
|
onUncaughtError(error);
|
|
logCapturedError(fiber, errorInfo);
|
|
};
|
|
|
|
return update;
|
|
}
|
|
|
|
function createClassErrorUpdate(fiber, errorInfo, lane) {
|
|
var update = createUpdate(NoTimestamp, lane);
|
|
update.tag = CaptureUpdate;
|
|
var getDerivedStateFromError = fiber.type.getDerivedStateFromError;
|
|
|
|
if (typeof getDerivedStateFromError === "function") {
|
|
var error$1 = errorInfo.value;
|
|
|
|
update.payload = function() {
|
|
return getDerivedStateFromError(error$1);
|
|
};
|
|
|
|
update.callback = function() {
|
|
{
|
|
markFailedErrorBoundaryForHotReloading(fiber);
|
|
}
|
|
|
|
logCapturedError(fiber, errorInfo);
|
|
};
|
|
}
|
|
|
|
var inst = fiber.stateNode;
|
|
|
|
if (inst !== null && typeof inst.componentDidCatch === "function") {
|
|
// $FlowFixMe[missing-this-annot]
|
|
update.callback = function callback() {
|
|
{
|
|
markFailedErrorBoundaryForHotReloading(fiber);
|
|
}
|
|
|
|
logCapturedError(fiber, errorInfo);
|
|
|
|
if (typeof getDerivedStateFromError !== "function") {
|
|
// To preserve the preexisting retry behavior of error boundaries,
|
|
// we keep track of which ones already failed during this batch.
|
|
// This gets reset before we yield back to the browser.
|
|
// TODO: Warn in strict mode if getDerivedStateFromError is
|
|
// not defined.
|
|
markLegacyErrorBoundaryAsFailed(this);
|
|
}
|
|
|
|
var error$1 = errorInfo.value;
|
|
var stack = errorInfo.stack;
|
|
this.componentDidCatch(error$1, {
|
|
componentStack: stack !== null ? stack : ""
|
|
});
|
|
|
|
{
|
|
if (typeof getDerivedStateFromError !== "function") {
|
|
// If componentDidCatch is the only error boundary method defined,
|
|
// then it needs to call setState to recover from errors.
|
|
// If no state update is scheduled then the boundary will swallow the error.
|
|
if (!includesSomeLane(fiber.lanes, SyncLane)) {
|
|
error(
|
|
"%s: Error boundaries should implement getDerivedStateFromError(). " +
|
|
"In that method, return a state update to display an error message or fallback UI.",
|
|
getComponentNameFromFiber(fiber) || "Unknown"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
return update;
|
|
}
|
|
|
|
function resetSuspendedComponent(sourceFiber, rootRenderLanes) {
|
|
// A legacy mode Suspense quirk, only relevant to hook components.
|
|
|
|
var tag = sourceFiber.tag;
|
|
|
|
if (
|
|
(sourceFiber.mode & ConcurrentMode) === NoMode &&
|
|
(tag === FunctionComponent ||
|
|
tag === ForwardRef ||
|
|
tag === SimpleMemoComponent)
|
|
) {
|
|
var currentSource = sourceFiber.alternate;
|
|
|
|
if (currentSource) {
|
|
sourceFiber.updateQueue = currentSource.updateQueue;
|
|
sourceFiber.memoizedState = currentSource.memoizedState;
|
|
sourceFiber.lanes = currentSource.lanes;
|
|
} else {
|
|
sourceFiber.updateQueue = null;
|
|
sourceFiber.memoizedState = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function markSuspenseBoundaryShouldCapture(
|
|
suspenseBoundary,
|
|
returnFiber,
|
|
sourceFiber,
|
|
root,
|
|
rootRenderLanes
|
|
) {
|
|
// This marks a Suspense boundary so that when we're unwinding the stack,
|
|
// it captures the suspended "exception" and does a second (fallback) pass.
|
|
if ((suspenseBoundary.mode & ConcurrentMode) === NoMode) {
|
|
// Legacy Mode Suspense
|
|
//
|
|
// If the boundary is in legacy mode, we should *not*
|
|
// suspend the commit. Pretend as if the suspended component rendered
|
|
// null and keep rendering. When the Suspense boundary completes,
|
|
// we'll do a second pass to render the fallback.
|
|
if (suspenseBoundary === returnFiber) {
|
|
// Special case where we suspended while reconciling the children of
|
|
// a Suspense boundary's inner Offscreen wrapper fiber. This happens
|
|
// when a React.lazy component is a direct child of a
|
|
// Suspense boundary.
|
|
//
|
|
// Suspense boundaries are implemented as multiple fibers, but they
|
|
// are a single conceptual unit. The legacy mode behavior where we
|
|
// pretend the suspended fiber committed as `null` won't work,
|
|
// because in this case the "suspended" fiber is the inner
|
|
// Offscreen wrapper.
|
|
//
|
|
// Because the contents of the boundary haven't started rendering
|
|
// yet (i.e. nothing in the tree has partially rendered) we can
|
|
// switch to the regular, concurrent mode behavior: mark the
|
|
// boundary with ShouldCapture and enter the unwind phase.
|
|
suspenseBoundary.flags |= ShouldCapture;
|
|
} else {
|
|
suspenseBoundary.flags |= DidCapture;
|
|
sourceFiber.flags |= ForceUpdateForLegacySuspense; // We're going to commit this fiber even though it didn't complete.
|
|
// But we shouldn't call any lifecycle methods or callbacks. Remove
|
|
// all lifecycle effect tags.
|
|
|
|
sourceFiber.flags &= ~(LifecycleEffectMask | Incomplete);
|
|
|
|
if (sourceFiber.tag === ClassComponent) {
|
|
var currentSourceFiber = sourceFiber.alternate;
|
|
|
|
if (currentSourceFiber === null) {
|
|
// This is a new mount. Change the tag so it's not mistaken for a
|
|
// completed class component. For example, we should not call
|
|
// componentWillUnmount if it is deleted.
|
|
sourceFiber.tag = IncompleteClassComponent;
|
|
} else {
|
|
// When we try rendering again, we should not reuse the current fiber,
|
|
// since it's known to be in an inconsistent state. Use a force update to
|
|
// prevent a bail out.
|
|
var update = createUpdate(NoTimestamp, SyncLane);
|
|
update.tag = ForceUpdate;
|
|
enqueueUpdate$1(sourceFiber, update, SyncLane);
|
|
}
|
|
} // The source fiber did not complete. Mark it with Sync priority to
|
|
// indicate that it still has pending work.
|
|
|
|
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, SyncLane);
|
|
}
|
|
|
|
return suspenseBoundary;
|
|
} // Confirmed that the boundary is in a concurrent mode tree. Continue
|
|
// with the normal suspend path.
|
|
//
|
|
// After this we'll use a set of heuristics to determine whether this
|
|
// render pass will run to completion or restart or "suspend" the commit.
|
|
// The actual logic for this is spread out in different places.
|
|
//
|
|
// This first principle is that if we're going to suspend when we complete
|
|
// a root, then we should also restart if we get an update or ping that
|
|
// might unsuspend it, and vice versa. The only reason to suspend is
|
|
// because you think you might want to restart before committing. However,
|
|
// it doesn't make sense to restart only while in the period we're suspended.
|
|
//
|
|
// Restarting too aggressively is also not good because it starves out any
|
|
// intermediate loading state. So we use heuristics to determine when.
|
|
// Suspense Heuristics
|
|
//
|
|
// If nothing threw a Promise or all the same fallbacks are already showing,
|
|
// then don't suspend/restart.
|
|
//
|
|
// If this is an initial render of a new tree of Suspense boundaries and
|
|
// those trigger a fallback, then don't suspend/restart. We want to ensure
|
|
// that we can show the initial loading state as quickly as possible.
|
|
//
|
|
// If we hit a "Delayed" case, such as when we'd switch from content back into
|
|
// a fallback, then we should always suspend/restart. Transitions apply
|
|
// to this case. If none is defined, JND is used instead.
|
|
//
|
|
// If we're already showing a fallback and it gets "retried", allowing us to show
|
|
// another level, but there's still an inner boundary that would show a fallback,
|
|
// then we suspend/restart for 500ms since the last time we showed a fallback
|
|
// anywhere in the tree. This effectively throttles progressive loading into a
|
|
// consistent train of commits. This also gives us an opportunity to restart to
|
|
// get to the completed state slightly earlier.
|
|
//
|
|
// If there's ambiguity due to batching it's resolved in preference of:
|
|
// 1) "delayed", 2) "initial render", 3) "retry".
|
|
//
|
|
// We want to ensure that a "busy" state doesn't get force committed. We want to
|
|
// ensure that new initial loading states can commit as soon as possible.
|
|
|
|
suspenseBoundary.flags |= ShouldCapture; // TODO: I think we can remove this, since we now use `DidCapture` in
|
|
// the begin phase to prevent an early bailout.
|
|
|
|
suspenseBoundary.lanes = rootRenderLanes;
|
|
return suspenseBoundary;
|
|
}
|
|
|
|
function throwException(
|
|
root,
|
|
returnFiber,
|
|
sourceFiber,
|
|
value,
|
|
rootRenderLanes
|
|
) {
|
|
// The source fiber did not complete.
|
|
sourceFiber.flags |= Incomplete;
|
|
|
|
if (
|
|
value !== null &&
|
|
typeof value === "object" &&
|
|
typeof value.then === "function"
|
|
) {
|
|
// This is a wakeable. The component suspended.
|
|
var wakeable = value;
|
|
resetSuspendedComponent(sourceFiber);
|
|
|
|
{
|
|
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
|
|
markDidThrowWhileHydratingDEV();
|
|
}
|
|
}
|
|
|
|
var suspenseBoundary = getSuspenseHandler();
|
|
|
|
if (suspenseBoundary !== null) {
|
|
switch (suspenseBoundary.tag) {
|
|
case SuspenseComponent: {
|
|
// If this suspense boundary is not already showing a fallback, mark
|
|
// the in-progress render as suspended. We try to perform this logic
|
|
// as soon as soon as possible during the render phase, so the work
|
|
// loop can know things like whether it's OK to switch to other tasks,
|
|
// or whether it can wait for data to resolve before continuing.
|
|
// TODO: Most of these checks are already performed when entering a
|
|
// Suspense boundary. We should track the information on the stack so
|
|
// we don't have to recompute it on demand. This would also allow us
|
|
// to unify with `use` which needs to perform this logic even sooner,
|
|
// before `throwException` is called.
|
|
if (sourceFiber.mode & ConcurrentMode) {
|
|
if (getShellBoundary() === null) {
|
|
// Suspended in the "shell" of the app. This is an undesirable
|
|
// loading state. We should avoid committing this tree.
|
|
renderDidSuspendDelayIfPossible();
|
|
} else {
|
|
// If we suspended deeper than the shell, we don't need to delay
|
|
// the commmit. However, we still call renderDidSuspend if this is
|
|
// a new boundary, to tell the work loop that a new fallback has
|
|
// appeared during this render.
|
|
// TODO: Theoretically we should be able to delete this branch.
|
|
// It's currently used for two things: 1) to throttle the
|
|
// appearance of successive loading states, and 2) in
|
|
// SuspenseList, to determine whether the children include any
|
|
// pending fallbacks. For 1, we should apply throttling to all
|
|
// retries, not just ones that render an additional fallback. For
|
|
// 2, we should check subtreeFlags instead. Then we can delete
|
|
// this branch.
|
|
var current = suspenseBoundary.alternate;
|
|
|
|
if (current === null) {
|
|
renderDidSuspend();
|
|
}
|
|
}
|
|
}
|
|
|
|
suspenseBoundary.flags &= ~ForceClientRender;
|
|
markSuspenseBoundaryShouldCapture(
|
|
suspenseBoundary,
|
|
returnFiber,
|
|
sourceFiber,
|
|
root,
|
|
rootRenderLanes
|
|
); // Retry listener
|
|
//
|
|
// If the fallback does commit, we need to attach a different type of
|
|
// listener. This one schedules an update on the Suspense boundary to
|
|
// turn the fallback state off.
|
|
//
|
|
// Stash the wakeable on the boundary fiber so we can access it in the
|
|
// commit phase.
|
|
//
|
|
// When the wakeable resolves, we'll attempt to render the boundary
|
|
// again ("retry").
|
|
|
|
var wakeables = suspenseBoundary.updateQueue;
|
|
|
|
if (wakeables === null) {
|
|
suspenseBoundary.updateQueue = new Set([wakeable]);
|
|
} else {
|
|
wakeables.add(wakeable);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
if (suspenseBoundary.mode & ConcurrentMode) {
|
|
suspenseBoundary.flags |= ShouldCapture;
|
|
var offscreenQueue = suspenseBoundary.updateQueue;
|
|
|
|
if (offscreenQueue === null) {
|
|
var newOffscreenQueue = {
|
|
transitions: null,
|
|
markerInstances: null,
|
|
wakeables: new Set([wakeable])
|
|
};
|
|
suspenseBoundary.updateQueue = newOffscreenQueue;
|
|
} else {
|
|
var _wakeables = offscreenQueue.wakeables;
|
|
|
|
if (_wakeables === null) {
|
|
offscreenQueue.wakeables = new Set([wakeable]);
|
|
} else {
|
|
_wakeables.add(wakeable);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
default: {
|
|
throw new Error(
|
|
"Unexpected Suspense handler tag (" +
|
|
suspenseBoundary.tag +
|
|
"). This " +
|
|
"is a bug in React."
|
|
);
|
|
}
|
|
} // We only attach ping listeners in concurrent mode. Legacy Suspense always
|
|
// commits fallbacks synchronously, so there are no pings.
|
|
|
|
if (suspenseBoundary.mode & ConcurrentMode) {
|
|
attachPingListener(root, wakeable, rootRenderLanes);
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
// No boundary was found. Unless this is a sync update, this is OK.
|
|
// We can suspend and wait for more data to arrive.
|
|
if (root.tag === ConcurrentRoot) {
|
|
// In a concurrent root, suspending without a Suspense boundary is
|
|
// allowed. It will suspend indefinitely without committing.
|
|
//
|
|
// TODO: Should we have different behavior for discrete updates? What
|
|
// about flushSync? Maybe it should put the tree into an inert state,
|
|
// and potentially log a warning. Revisit this for a future release.
|
|
attachPingListener(root, wakeable, rootRenderLanes);
|
|
renderDidSuspendDelayIfPossible();
|
|
return;
|
|
} else {
|
|
// In a legacy root, suspending without a boundary is always an error.
|
|
var uncaughtSuspenseError = new Error(
|
|
"A component suspended while responding to synchronous input. This " +
|
|
"will cause the UI to be replaced with a loading indicator. To " +
|
|
"fix, updates that suspend should be wrapped " +
|
|
"with startTransition."
|
|
);
|
|
value = uncaughtSuspenseError;
|
|
}
|
|
}
|
|
} else {
|
|
// This is a regular error, not a Suspense wakeable.
|
|
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
|
|
markDidThrowWhileHydratingDEV();
|
|
|
|
var _suspenseBoundary = getSuspenseHandler(); // If the error was thrown during hydration, we may be able to recover by
|
|
// discarding the dehydrated content and switching to a client render.
|
|
// Instead of surfacing the error, find the nearest Suspense boundary
|
|
// and render it again without hydration.
|
|
|
|
if (_suspenseBoundary !== null) {
|
|
if ((_suspenseBoundary.flags & ShouldCapture) === NoFlags) {
|
|
// Set a flag to indicate that we should try rendering the normal
|
|
// children again, not the fallback.
|
|
_suspenseBoundary.flags |= ForceClientRender;
|
|
}
|
|
|
|
markSuspenseBoundaryShouldCapture(
|
|
_suspenseBoundary,
|
|
returnFiber,
|
|
sourceFiber,
|
|
root,
|
|
rootRenderLanes
|
|
); // Even though the user may not be affected by this error, we should
|
|
// still log it so it can be fixed.
|
|
|
|
queueHydrationError(createCapturedValueAtFiber(value, sourceFiber));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
value = createCapturedValueAtFiber(value, sourceFiber);
|
|
renderDidError(value); // We didn't find a boundary that could handle this type of exception. Start
|
|
// over and traverse parent path again, this time treating the exception
|
|
// as an error.
|
|
|
|
var workInProgress = returnFiber;
|
|
|
|
do {
|
|
switch (workInProgress.tag) {
|
|
case HostRoot: {
|
|
var _errorInfo = value;
|
|
workInProgress.flags |= ShouldCapture;
|
|
var lane = pickArbitraryLane(rootRenderLanes);
|
|
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
|
|
var update = createRootErrorUpdate(workInProgress, _errorInfo, lane);
|
|
enqueueCapturedUpdate(workInProgress, update);
|
|
return;
|
|
}
|
|
|
|
case ClassComponent:
|
|
// Capture and retry
|
|
var errorInfo = value;
|
|
var ctor = workInProgress.type;
|
|
var instance = workInProgress.stateNode;
|
|
|
|
if (
|
|
(workInProgress.flags & DidCapture) === NoFlags &&
|
|
(typeof ctor.getDerivedStateFromError === "function" ||
|
|
(instance !== null &&
|
|
typeof instance.componentDidCatch === "function" &&
|
|
!isAlreadyFailedLegacyErrorBoundary(instance)))
|
|
) {
|
|
workInProgress.flags |= ShouldCapture;
|
|
|
|
var _lane = pickArbitraryLane(rootRenderLanes);
|
|
|
|
workInProgress.lanes = mergeLanes(workInProgress.lanes, _lane); // Schedule the error boundary to re-render using updated state
|
|
|
|
var _update = createClassErrorUpdate(
|
|
workInProgress,
|
|
errorInfo,
|
|
_lane
|
|
);
|
|
|
|
enqueueCapturedUpdate(workInProgress, _update);
|
|
return;
|
|
}
|
|
|
|
break;
|
|
} // $FlowFixMe[incompatible-type] we bail out when we get a null
|
|
|
|
workInProgress = workInProgress.return;
|
|
} while (workInProgress !== null);
|
|
}
|
|
|
|
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner; // A special exception that's used to unwind the stack when an update flows
|
|
// into a dehydrated boundary.
|
|
|
|
var SelectiveHydrationException = new Error(
|
|
"This is not a real error. It's an implementation detail of React's " +
|
|
"selective hydration feature. If this leaks into userspace, it's a bug in " +
|
|
"React. Please file an issue."
|
|
);
|
|
var didReceiveUpdate = false;
|
|
var didWarnAboutBadClass;
|
|
var didWarnAboutModulePatternComponent;
|
|
var didWarnAboutContextTypeOnFunctionComponent;
|
|
var didWarnAboutGetDerivedStateOnFunctionComponent;
|
|
var didWarnAboutFunctionRefs;
|
|
var didWarnAboutReassigningProps;
|
|
var didWarnAboutRevealOrder;
|
|
var didWarnAboutTailOptions;
|
|
var didWarnAboutDefaultPropsOnFunctionComponent;
|
|
|
|
{
|
|
didWarnAboutBadClass = {};
|
|
didWarnAboutModulePatternComponent = {};
|
|
didWarnAboutContextTypeOnFunctionComponent = {};
|
|
didWarnAboutGetDerivedStateOnFunctionComponent = {};
|
|
didWarnAboutFunctionRefs = {};
|
|
didWarnAboutReassigningProps = false;
|
|
didWarnAboutRevealOrder = {};
|
|
didWarnAboutTailOptions = {};
|
|
didWarnAboutDefaultPropsOnFunctionComponent = {};
|
|
}
|
|
|
|
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
|
|
if (current === null) {
|
|
// If this is a fresh new component that hasn't been rendered yet, we
|
|
// won't update its child set by applying minimal side-effects. Instead,
|
|
// we will add them all to the child before it gets rendered. That means
|
|
// we can optimize this reconciliation pass by not tracking side-effects.
|
|
workInProgress.child = mountChildFibers(
|
|
workInProgress,
|
|
null,
|
|
nextChildren,
|
|
renderLanes
|
|
);
|
|
} else {
|
|
// If the current child is the same as the work in progress, it means that
|
|
// we haven't yet started any work on these children. Therefore, we use
|
|
// the clone algorithm to create a copy of all the current children.
|
|
// If we had any progressed work already, that is invalid at this point so
|
|
// let's throw it out.
|
|
workInProgress.child = reconcileChildFibers(
|
|
workInProgress,
|
|
current.child,
|
|
nextChildren,
|
|
renderLanes
|
|
);
|
|
}
|
|
}
|
|
|
|
function forceUnmountCurrentAndReconcile(
|
|
current,
|
|
workInProgress,
|
|
nextChildren,
|
|
renderLanes
|
|
) {
|
|
// This function is fork of reconcileChildren. It's used in cases where we
|
|
// want to reconcile without matching against the existing set. This has the
|
|
// effect of all current children being unmounted; even if the type and key
|
|
// are the same, the old child is unmounted and a new child is created.
|
|
//
|
|
// To do this, we're going to go through the reconcile algorithm twice. In
|
|
// the first pass, we schedule a deletion for all the current children by
|
|
// passing null.
|
|
workInProgress.child = reconcileChildFibers(
|
|
workInProgress,
|
|
current.child,
|
|
null,
|
|
renderLanes
|
|
); // In the second pass, we mount the new children. The trick here is that we
|
|
// pass null in place of where we usually pass the current child set. This has
|
|
// the effect of remounting all children regardless of whether their
|
|
// identities match.
|
|
|
|
workInProgress.child = reconcileChildFibers(
|
|
workInProgress,
|
|
null,
|
|
nextChildren,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
function updateForwardRef(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
) {
|
|
// TODO: current can be non-null here even if the component
|
|
// hasn't yet mounted. This happens after the first render suspends.
|
|
// We'll need to figure out if this is fine or can cause issues.
|
|
{
|
|
if (workInProgress.type !== workInProgress.elementType) {
|
|
// Lazy component props can't be validated in createElement
|
|
// because they're only guaranteed to be resolved here.
|
|
var innerPropTypes = Component.propTypes;
|
|
|
|
if (innerPropTypes) {
|
|
checkPropTypes(
|
|
innerPropTypes,
|
|
nextProps, // Resolved props
|
|
"prop",
|
|
getComponentNameFromType(Component)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
var render = Component.render;
|
|
var ref = workInProgress.ref; // The rest is a fork of updateFunctionComponent
|
|
|
|
var nextChildren;
|
|
var hasId;
|
|
prepareToReadContext(workInProgress, renderLanes);
|
|
|
|
{
|
|
ReactCurrentOwner$1.current = workInProgress;
|
|
setIsRendering(true);
|
|
nextChildren = renderWithHooks(
|
|
current,
|
|
workInProgress,
|
|
render,
|
|
nextProps,
|
|
ref,
|
|
renderLanes
|
|
);
|
|
hasId = checkDidRenderIdHook();
|
|
setIsRendering(false);
|
|
}
|
|
|
|
if (current !== null && !didReceiveUpdate) {
|
|
bailoutHooks(current, workInProgress, renderLanes);
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
if (getIsHydrating() && hasId) {
|
|
pushMaterializedTreeId(workInProgress);
|
|
} // React DevTools reads this flag.
|
|
|
|
workInProgress.flags |= PerformedWork;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateMemoComponent(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
) {
|
|
if (current === null) {
|
|
var type = Component.type;
|
|
|
|
if (
|
|
isSimpleFunctionComponent(type) &&
|
|
Component.compare === null && // SimpleMemoComponent codepath doesn't resolve outer props either.
|
|
Component.defaultProps === undefined
|
|
) {
|
|
var resolvedType = type;
|
|
|
|
{
|
|
resolvedType = resolveFunctionForHotReloading(type);
|
|
} // If this is a plain function component without default props,
|
|
// and with only the default shallow comparison, we upgrade it
|
|
// to a SimpleMemoComponent to allow fast path updates.
|
|
|
|
workInProgress.tag = SimpleMemoComponent;
|
|
workInProgress.type = resolvedType;
|
|
|
|
{
|
|
validateFunctionComponentInDev(workInProgress, type);
|
|
}
|
|
|
|
return updateSimpleMemoComponent(
|
|
current,
|
|
workInProgress,
|
|
resolvedType,
|
|
nextProps,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
{
|
|
var innerPropTypes = type.propTypes;
|
|
|
|
if (innerPropTypes) {
|
|
// Inner memo component props aren't currently validated in createElement.
|
|
// We could move it there, but we'd still need this for lazy code path.
|
|
checkPropTypes(
|
|
innerPropTypes,
|
|
nextProps, // Resolved props
|
|
"prop",
|
|
getComponentNameFromType(type)
|
|
);
|
|
}
|
|
|
|
if (Component.defaultProps !== undefined) {
|
|
var componentName = getComponentNameFromType(type) || "Unknown";
|
|
|
|
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
|
|
error(
|
|
"%s: Support for defaultProps will be removed from memo components " +
|
|
"in a future major release. Use JavaScript default parameters instead.",
|
|
componentName
|
|
);
|
|
|
|
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
var child = createFiberFromTypeAndProps(
|
|
Component.type,
|
|
null,
|
|
nextProps,
|
|
workInProgress,
|
|
workInProgress.mode,
|
|
renderLanes
|
|
);
|
|
child.ref = workInProgress.ref;
|
|
child.return = workInProgress;
|
|
workInProgress.child = child;
|
|
return child;
|
|
}
|
|
|
|
{
|
|
var _type = Component.type;
|
|
var _innerPropTypes = _type.propTypes;
|
|
|
|
if (_innerPropTypes) {
|
|
// Inner memo component props aren't currently validated in createElement.
|
|
// We could move it there, but we'd still need this for lazy code path.
|
|
checkPropTypes(
|
|
_innerPropTypes,
|
|
nextProps, // Resolved props
|
|
"prop",
|
|
getComponentNameFromType(_type)
|
|
);
|
|
}
|
|
}
|
|
|
|
var currentChild = current.child; // This is always exactly one child
|
|
|
|
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
|
|
current,
|
|
renderLanes
|
|
);
|
|
|
|
if (!hasScheduledUpdateOrContext) {
|
|
// This will be the props with resolved defaultProps,
|
|
// unlike current.memoizedProps which will be the unresolved ones.
|
|
var prevProps = currentChild.memoizedProps; // Default to shallow comparison
|
|
|
|
var compare = Component.compare;
|
|
compare = compare !== null ? compare : shallowEqual;
|
|
|
|
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
|
|
}
|
|
} // React DevTools reads this flag.
|
|
|
|
workInProgress.flags |= PerformedWork;
|
|
var newChild = createWorkInProgress(currentChild, nextProps);
|
|
newChild.ref = workInProgress.ref;
|
|
newChild.return = workInProgress;
|
|
workInProgress.child = newChild;
|
|
return newChild;
|
|
}
|
|
|
|
function updateSimpleMemoComponent(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
) {
|
|
// TODO: current can be non-null here even if the component
|
|
// hasn't yet mounted. This happens when the inner render suspends.
|
|
// We'll need to figure out if this is fine or can cause issues.
|
|
{
|
|
if (workInProgress.type !== workInProgress.elementType) {
|
|
// Lazy component props can't be validated in createElement
|
|
// because they're only guaranteed to be resolved here.
|
|
var outerMemoType = workInProgress.elementType;
|
|
|
|
if (outerMemoType.$$typeof === REACT_LAZY_TYPE) {
|
|
// We warn when you define propTypes on lazy()
|
|
// so let's just skip over it to find memo() outer wrapper.
|
|
// Inner props for memo are validated later.
|
|
var lazyComponent = outerMemoType;
|
|
var payload = lazyComponent._payload;
|
|
var init = lazyComponent._init;
|
|
|
|
try {
|
|
outerMemoType = init(payload);
|
|
} catch (x) {
|
|
outerMemoType = null;
|
|
} // Inner propTypes will be validated in the function component path.
|
|
|
|
var outerPropTypes = outerMemoType && outerMemoType.propTypes;
|
|
|
|
if (outerPropTypes) {
|
|
checkPropTypes(
|
|
outerPropTypes,
|
|
nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
|
|
"prop",
|
|
getComponentNameFromType(outerMemoType)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (current !== null) {
|
|
var prevProps = current.memoizedProps;
|
|
|
|
if (
|
|
shallowEqual(prevProps, nextProps) &&
|
|
current.ref === workInProgress.ref && // Prevent bailout if the implementation changed due to hot reload.
|
|
workInProgress.type === current.type
|
|
) {
|
|
didReceiveUpdate = false; // The props are shallowly equal. Reuse the previous props object, like we
|
|
// would during a normal fiber bailout.
|
|
//
|
|
// We don't have strong guarantees that the props object is referentially
|
|
// equal during updates where we can't bail out anyway — like if the props
|
|
// are shallowly equal, but there's a local state or context update in the
|
|
// same batch.
|
|
//
|
|
// However, as a principle, we should aim to make the behavior consistent
|
|
// across different ways of memoizing a component. For example, React.memo
|
|
// has a different internal Fiber layout if you pass a normal function
|
|
// component (SimpleMemoComponent) versus if you pass a different type
|
|
// like forwardRef (MemoComponent). But this is an implementation detail.
|
|
// Wrapping a component in forwardRef (or React.lazy, etc) shouldn't
|
|
// affect whether the props object is reused during a bailout.
|
|
|
|
workInProgress.pendingProps = nextProps = prevProps;
|
|
|
|
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
|
|
// The pending lanes were cleared at the beginning of beginWork. We're
|
|
// about to bail out, but there might be other lanes that weren't
|
|
// included in the current render. Usually, the priority level of the
|
|
// remaining updates is accumulated during the evaluation of the
|
|
// component (i.e. when processing the update queue). But since since
|
|
// we're bailing out early *without* evaluating the component, we need
|
|
// to account for it here, too. Reset to the value of the current fiber.
|
|
// NOTE: This only applies to SimpleMemoComponent, not MemoComponent,
|
|
// because a MemoComponent fiber does not have hooks or an update queue;
|
|
// rather, it wraps around an inner component, which may or may not
|
|
// contains hooks.
|
|
// TODO: Move the reset at in beginWork out of the common path so that
|
|
// this is no longer necessary.
|
|
workInProgress.lanes = current.lanes;
|
|
return bailoutOnAlreadyFinishedWork(
|
|
current,
|
|
workInProgress,
|
|
renderLanes
|
|
);
|
|
} else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
|
|
// This is a special case that only exists for legacy mode.
|
|
// See https://github.com/facebook/react/pull/19216.
|
|
didReceiveUpdate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return updateFunctionComponent(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
function updateOffscreenComponent(current, workInProgress, renderLanes) {
|
|
var nextProps = workInProgress.pendingProps;
|
|
var nextChildren = nextProps.children;
|
|
var nextIsDetached =
|
|
(workInProgress.stateNode._pendingVisibility & OffscreenDetached) !== 0;
|
|
var prevState = current !== null ? current.memoizedState : null;
|
|
markRef(current, workInProgress);
|
|
|
|
if (nextProps.mode === "hidden" || enableLegacyHidden || nextIsDetached) {
|
|
// Rendering a hidden tree.
|
|
var didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
|
|
|
|
if (didSuspend) {
|
|
// Something suspended inside a hidden tree
|
|
// Include the base lanes from the last render
|
|
var nextBaseLanes =
|
|
prevState !== null
|
|
? mergeLanes(prevState.baseLanes, renderLanes)
|
|
: renderLanes;
|
|
|
|
if (current !== null) {
|
|
// Reset to the current children
|
|
var currentChild = (workInProgress.child = current.child); // The current render suspended, but there may be other lanes with
|
|
// pending work. We can't read `childLanes` from the current Offscreen
|
|
// fiber because we reset it when it was deferred; however, we can read
|
|
// the pending lanes from the child fibers.
|
|
|
|
var currentChildLanes = NoLanes;
|
|
|
|
while (currentChild !== null) {
|
|
currentChildLanes = mergeLanes(
|
|
mergeLanes(currentChildLanes, currentChild.lanes),
|
|
currentChild.childLanes
|
|
);
|
|
currentChild = currentChild.sibling;
|
|
}
|
|
|
|
var lanesWeJustAttempted = nextBaseLanes;
|
|
var remainingChildLanes = removeLanes(
|
|
currentChildLanes,
|
|
lanesWeJustAttempted
|
|
);
|
|
workInProgress.childLanes = remainingChildLanes;
|
|
} else {
|
|
workInProgress.childLanes = NoLanes;
|
|
workInProgress.child = null;
|
|
}
|
|
|
|
return deferHiddenOffscreenComponent(
|
|
current,
|
|
workInProgress,
|
|
nextBaseLanes
|
|
);
|
|
}
|
|
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
|
// In legacy sync mode, don't defer the subtree. Render it now.
|
|
// TODO: Consider how Offscreen should work with transitions in the future
|
|
var nextState = {
|
|
baseLanes: NoLanes,
|
|
cachePool: null
|
|
};
|
|
workInProgress.memoizedState = nextState;
|
|
|
|
{
|
|
// push the cache pool even though we're going to bail out
|
|
// because otherwise there'd be a context mismatch
|
|
if (current !== null) {
|
|
pushTransition(workInProgress, null);
|
|
}
|
|
}
|
|
|
|
reuseHiddenContextOnStack(workInProgress);
|
|
pushOffscreenSuspenseHandler(workInProgress);
|
|
} else if (!includesSomeLane(renderLanes, OffscreenLane)) {
|
|
// We're hidden, and we're not rendering at Offscreen. We will bail out
|
|
// and resume this tree later.
|
|
// Schedule this fiber to re-render at Offscreen priority
|
|
workInProgress.lanes = workInProgress.childLanes = laneToLanes(
|
|
OffscreenLane
|
|
); // Include the base lanes from the last render
|
|
|
|
var _nextBaseLanes =
|
|
prevState !== null
|
|
? mergeLanes(prevState.baseLanes, renderLanes)
|
|
: renderLanes;
|
|
|
|
return deferHiddenOffscreenComponent(
|
|
current,
|
|
workInProgress,
|
|
_nextBaseLanes
|
|
);
|
|
} else {
|
|
// This is the second render. The surrounding visible content has already
|
|
// committed. Now we resume rendering the hidden tree.
|
|
// Rendering at offscreen, so we can clear the base lanes.
|
|
var _nextState = {
|
|
baseLanes: NoLanes,
|
|
cachePool: null
|
|
};
|
|
workInProgress.memoizedState = _nextState;
|
|
|
|
if (current !== null) {
|
|
// If the render that spawned this one accessed the cache pool, resume
|
|
// using the same cache. Unless the parent changed, since that means
|
|
// there was a refresh.
|
|
var prevCachePool = prevState !== null ? prevState.cachePool : null; // TODO: Consider if and how Offscreen pre-rendering should
|
|
// be attributed to the transition that spawned it
|
|
|
|
pushTransition(workInProgress, prevCachePool);
|
|
} // Push the lanes that were skipped when we bailed out.
|
|
|
|
if (prevState !== null) {
|
|
pushHiddenContext(workInProgress, prevState);
|
|
} else {
|
|
reuseHiddenContextOnStack(workInProgress);
|
|
}
|
|
|
|
pushOffscreenSuspenseHandler(workInProgress);
|
|
}
|
|
} else {
|
|
// Rendering a visible tree.
|
|
if (prevState !== null) {
|
|
// We're going from hidden -> visible.
|
|
var _prevCachePool = null;
|
|
|
|
{
|
|
// If the render that spawned this one accessed the cache pool, resume
|
|
// using the same cache. Unless the parent changed, since that means
|
|
// there was a refresh.
|
|
_prevCachePool = prevState.cachePool;
|
|
}
|
|
|
|
pushTransition(workInProgress, _prevCachePool); // Push the lanes that were skipped when we bailed out.
|
|
|
|
pushHiddenContext(workInProgress, prevState);
|
|
reuseSuspenseHandlerOnStack(workInProgress); // Since we're not hidden anymore, reset the state
|
|
|
|
workInProgress.memoizedState = null;
|
|
} else {
|
|
// We weren't previously hidden, and we still aren't, so there's nothing
|
|
// special to do. Need to push to the stack regardless, though, to avoid
|
|
// a push/pop misalignment.
|
|
{
|
|
// If the render that spawned this one accessed the cache pool, resume
|
|
// using the same cache. Unless the parent changed, since that means
|
|
// there was a refresh.
|
|
if (current !== null) {
|
|
pushTransition(workInProgress, null);
|
|
}
|
|
} // We're about to bail out, but we need to push this to the stack anyway
|
|
// to avoid a push/pop misalignment.
|
|
|
|
reuseHiddenContextOnStack(workInProgress);
|
|
reuseSuspenseHandlerOnStack(workInProgress);
|
|
}
|
|
}
|
|
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function deferHiddenOffscreenComponent(
|
|
current,
|
|
workInProgress,
|
|
nextBaseLanes,
|
|
renderLanes
|
|
) {
|
|
var nextState = {
|
|
baseLanes: nextBaseLanes,
|
|
// Save the cache pool so we can resume later.
|
|
cachePool: getOffscreenDeferredCache()
|
|
};
|
|
workInProgress.memoizedState = nextState;
|
|
|
|
{
|
|
// push the cache pool even though we're going to bail out
|
|
// because otherwise there'd be a context mismatch
|
|
if (current !== null) {
|
|
pushTransition(workInProgress, null);
|
|
}
|
|
} // We're about to bail out, but we need to push this to the stack anyway
|
|
// to avoid a push/pop misalignment.
|
|
|
|
reuseHiddenContextOnStack(workInProgress);
|
|
pushOffscreenSuspenseHandler(workInProgress);
|
|
|
|
return null;
|
|
} // Note: These happen to have identical begin phases, for now. We shouldn't hold
|
|
|
|
function updateCacheComponent(current, workInProgress, renderLanes) {
|
|
prepareToReadContext(workInProgress, renderLanes);
|
|
var parentCache = readContext(CacheContext);
|
|
|
|
if (current === null) {
|
|
// Initial mount. Request a fresh cache from the pool.
|
|
var freshCache = requestCacheFromPool(renderLanes);
|
|
var initialState = {
|
|
parent: parentCache,
|
|
cache: freshCache
|
|
};
|
|
workInProgress.memoizedState = initialState;
|
|
initializeUpdateQueue(workInProgress);
|
|
pushCacheProvider(workInProgress, freshCache);
|
|
} else {
|
|
// Check for updates
|
|
if (includesSomeLane(current.lanes, renderLanes)) {
|
|
cloneUpdateQueue(current, workInProgress);
|
|
processUpdateQueue(workInProgress, null, null, renderLanes);
|
|
}
|
|
|
|
var prevState = current.memoizedState;
|
|
var nextState = workInProgress.memoizedState; // Compare the new parent cache to the previous to see detect there was
|
|
// a refresh.
|
|
|
|
if (prevState.parent !== parentCache) {
|
|
// Refresh in parent. Update the parent.
|
|
var derivedState = {
|
|
parent: parentCache,
|
|
cache: parentCache
|
|
}; // Copied from getDerivedStateFromProps implementation. Once the update
|
|
// queue is empty, persist the derived state onto the base state.
|
|
|
|
workInProgress.memoizedState = derivedState;
|
|
|
|
if (workInProgress.lanes === NoLanes) {
|
|
var updateQueue = workInProgress.updateQueue;
|
|
workInProgress.memoizedState = updateQueue.baseState = derivedState;
|
|
}
|
|
|
|
pushCacheProvider(workInProgress, parentCache); // No need to propagate a context change because the refreshed parent
|
|
// already did.
|
|
} else {
|
|
// The parent didn't refresh. Now check if this cache did.
|
|
var nextCache = nextState.cache;
|
|
pushCacheProvider(workInProgress, nextCache);
|
|
|
|
if (nextCache !== prevState.cache) {
|
|
// This cache refreshed. Propagate a context change.
|
|
propagateContextChange(workInProgress, CacheContext, renderLanes);
|
|
}
|
|
}
|
|
}
|
|
|
|
var nextChildren = workInProgress.pendingProps.children;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
} // This should only be called if the name changes
|
|
|
|
function updateFragment(current, workInProgress, renderLanes) {
|
|
var nextChildren = workInProgress.pendingProps;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateMode(current, workInProgress, renderLanes) {
|
|
var nextChildren = workInProgress.pendingProps.children;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateProfiler(current, workInProgress, renderLanes) {
|
|
var nextProps = workInProgress.pendingProps;
|
|
var nextChildren = nextProps.children;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function markRef(current, workInProgress) {
|
|
var ref = workInProgress.ref;
|
|
|
|
if (
|
|
(current === null && ref !== null) ||
|
|
(current !== null && current.ref !== ref)
|
|
) {
|
|
// Schedule a Ref effect
|
|
workInProgress.flags |= Ref;
|
|
workInProgress.flags |= RefStatic;
|
|
}
|
|
}
|
|
|
|
function updateFunctionComponent(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
) {
|
|
{
|
|
if (workInProgress.type !== workInProgress.elementType) {
|
|
// Lazy component props can't be validated in createElement
|
|
// because they're only guaranteed to be resolved here.
|
|
var innerPropTypes = Component.propTypes;
|
|
|
|
if (innerPropTypes) {
|
|
checkPropTypes(
|
|
innerPropTypes,
|
|
nextProps, // Resolved props
|
|
"prop",
|
|
getComponentNameFromType(Component)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
var context;
|
|
|
|
var nextChildren;
|
|
var hasId;
|
|
prepareToReadContext(workInProgress, renderLanes);
|
|
|
|
{
|
|
ReactCurrentOwner$1.current = workInProgress;
|
|
setIsRendering(true);
|
|
nextChildren = renderWithHooks(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
context,
|
|
renderLanes
|
|
);
|
|
hasId = checkDidRenderIdHook();
|
|
setIsRendering(false);
|
|
}
|
|
|
|
if (current !== null && !didReceiveUpdate) {
|
|
bailoutHooks(current, workInProgress, renderLanes);
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
if (getIsHydrating() && hasId) {
|
|
pushMaterializedTreeId(workInProgress);
|
|
} // React DevTools reads this flag.
|
|
|
|
workInProgress.flags |= PerformedWork;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function replayFunctionComponent(
|
|
current,
|
|
workInProgress,
|
|
nextProps,
|
|
Component,
|
|
renderLanes
|
|
) {
|
|
// This function is used to replay a component that previously suspended,
|
|
// after its data resolves. It's a simplified version of
|
|
// updateFunctionComponent that reuses the hooks from the previous attempt.
|
|
var context;
|
|
|
|
prepareToReadContext(workInProgress, renderLanes);
|
|
|
|
var nextChildren = replaySuspendedComponentWithHooks(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
context
|
|
);
|
|
var hasId = checkDidRenderIdHook();
|
|
|
|
if (current !== null && !didReceiveUpdate) {
|
|
bailoutHooks(current, workInProgress, renderLanes);
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
if (getIsHydrating() && hasId) {
|
|
pushMaterializedTreeId(workInProgress);
|
|
} // React DevTools reads this flag.
|
|
|
|
workInProgress.flags |= PerformedWork;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateClassComponent(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
) {
|
|
{
|
|
// This is used by DevTools to force a boundary to error.
|
|
switch (shouldError(workInProgress)) {
|
|
case false: {
|
|
var _instance = workInProgress.stateNode;
|
|
var ctor = workInProgress.type; // TODO This way of resetting the error boundary state is a hack.
|
|
// Is there a better way to do this?
|
|
|
|
var tempInstance = new ctor(
|
|
workInProgress.memoizedProps,
|
|
_instance.context
|
|
);
|
|
var state = tempInstance.state;
|
|
|
|
_instance.updater.enqueueSetState(_instance, state, null);
|
|
|
|
break;
|
|
}
|
|
|
|
case true: {
|
|
workInProgress.flags |= DidCapture;
|
|
workInProgress.flags |= ShouldCapture; // eslint-disable-next-line react-internal/prod-error-codes
|
|
|
|
var error$1 = new Error("Simulated error coming from DevTools");
|
|
var lane = pickArbitraryLane(renderLanes);
|
|
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane); // Schedule the error boundary to re-render using updated state
|
|
|
|
var update = createClassErrorUpdate(
|
|
workInProgress,
|
|
createCapturedValueAtFiber(error$1, workInProgress),
|
|
lane
|
|
);
|
|
enqueueCapturedUpdate(workInProgress, update);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (workInProgress.type !== workInProgress.elementType) {
|
|
// Lazy component props can't be validated in createElement
|
|
// because they're only guaranteed to be resolved here.
|
|
var innerPropTypes = Component.propTypes;
|
|
|
|
if (innerPropTypes) {
|
|
checkPropTypes(
|
|
innerPropTypes,
|
|
nextProps, // Resolved props
|
|
"prop",
|
|
getComponentNameFromType(Component)
|
|
);
|
|
}
|
|
}
|
|
} // Push context providers early to prevent context stack mismatches.
|
|
// During mounting we don't know the child context yet as the instance doesn't exist.
|
|
// We will invalidate the child context in finishClassComponent() right after rendering.
|
|
|
|
var hasContext;
|
|
|
|
if (isContextProvider()) {
|
|
hasContext = true;
|
|
} else {
|
|
hasContext = false;
|
|
}
|
|
|
|
prepareToReadContext(workInProgress, renderLanes);
|
|
var instance = workInProgress.stateNode;
|
|
var shouldUpdate;
|
|
|
|
if (instance === null) {
|
|
resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress); // In the initial pass we might need to construct the instance.
|
|
|
|
constructClassInstance(workInProgress, Component, nextProps);
|
|
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
|
|
shouldUpdate = true;
|
|
} else if (current === null) {
|
|
// In a resume, we'll already have an instance we can reuse.
|
|
shouldUpdate = resumeMountClassInstance(
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
);
|
|
} else {
|
|
shouldUpdate = updateClassInstance(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
var nextUnitOfWork = finishClassComponent(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
shouldUpdate,
|
|
hasContext,
|
|
renderLanes
|
|
);
|
|
|
|
{
|
|
var inst = workInProgress.stateNode;
|
|
|
|
if (shouldUpdate && inst.props !== nextProps) {
|
|
if (!didWarnAboutReassigningProps) {
|
|
error(
|
|
"It looks like %s is reassigning its own `this.props` while rendering. " +
|
|
"This is not supported and can lead to confusing bugs.",
|
|
getComponentNameFromFiber(workInProgress) || "a component"
|
|
);
|
|
}
|
|
|
|
didWarnAboutReassigningProps = true;
|
|
}
|
|
}
|
|
|
|
return nextUnitOfWork;
|
|
}
|
|
|
|
function finishClassComponent(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
shouldUpdate,
|
|
hasContext,
|
|
renderLanes
|
|
) {
|
|
// Refs should update even if shouldComponentUpdate returns false
|
|
markRef(current, workInProgress);
|
|
var didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
|
|
|
|
if (!shouldUpdate && !didCaptureError) {
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
var instance = workInProgress.stateNode; // Rerender
|
|
|
|
ReactCurrentOwner$1.current = workInProgress;
|
|
var nextChildren;
|
|
|
|
if (
|
|
didCaptureError &&
|
|
typeof Component.getDerivedStateFromError !== "function"
|
|
) {
|
|
// If we captured an error, but getDerivedStateFromError is not defined,
|
|
// unmount all the children. componentDidCatch will schedule an update to
|
|
// re-render a fallback. This is temporary until we migrate everyone to
|
|
// the new API.
|
|
// TODO: Warn in a future release.
|
|
nextChildren = null;
|
|
} else {
|
|
{
|
|
setIsRendering(true);
|
|
nextChildren = instance.render();
|
|
|
|
setIsRendering(false);
|
|
}
|
|
} // React DevTools reads this flag.
|
|
|
|
workInProgress.flags |= PerformedWork;
|
|
|
|
if (current !== null && didCaptureError) {
|
|
// If we're recovering from an error, reconcile without reusing any of
|
|
// the existing children. Conceptually, the normal children and the children
|
|
// that are shown on error are two different sets, so we shouldn't reuse
|
|
// normal children even if their identities match.
|
|
forceUnmountCurrentAndReconcile(
|
|
current,
|
|
workInProgress,
|
|
nextChildren,
|
|
renderLanes
|
|
);
|
|
} else {
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
} // Memoize state using the values we just used to render.
|
|
// TODO: Restructure so we never read values from the instance.
|
|
|
|
workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it.
|
|
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function pushHostRootContext(workInProgress) {
|
|
var root = workInProgress.stateNode;
|
|
|
|
if (root.pendingContext) {
|
|
pushTopLevelContextObject(
|
|
workInProgress,
|
|
root.pendingContext,
|
|
root.pendingContext !== root.context
|
|
);
|
|
} else if (root.context) {
|
|
// Should always be set
|
|
pushTopLevelContextObject(workInProgress, root.context);
|
|
}
|
|
|
|
pushHostContainer(workInProgress, root.containerInfo);
|
|
}
|
|
|
|
function updateHostRoot(current, workInProgress, renderLanes) {
|
|
pushHostRootContext(workInProgress);
|
|
|
|
if (current === null) {
|
|
throw new Error("Should have a current fiber. This is a bug in React.");
|
|
}
|
|
|
|
var nextProps = workInProgress.pendingProps;
|
|
var prevState = workInProgress.memoizedState;
|
|
var prevChildren = prevState.element;
|
|
cloneUpdateQueue(current, workInProgress);
|
|
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
|
|
var nextState = workInProgress.memoizedState;
|
|
var root = workInProgress.stateNode;
|
|
|
|
{
|
|
var nextCache = nextState.cache;
|
|
pushCacheProvider(workInProgress, nextCache);
|
|
|
|
if (nextCache !== prevState.cache) {
|
|
// The root cache refreshed.
|
|
propagateContextChange(workInProgress, CacheContext, renderLanes);
|
|
}
|
|
} // Caution: React DevTools currently depends on this property
|
|
// being called "element".
|
|
|
|
var nextChildren = nextState.element;
|
|
|
|
if (prevState.isDehydrated) {
|
|
// This is a hydration root whose shell has not yet hydrated. We should
|
|
// attempt to hydrate.
|
|
// Flip isDehydrated to false to indicate that when this render
|
|
// finishes, the root will no longer be dehydrated.
|
|
var overrideState = {
|
|
element: nextChildren,
|
|
isDehydrated: false,
|
|
cache: nextState.cache
|
|
};
|
|
var updateQueue = workInProgress.updateQueue; // `baseState` can always be the last state because the root doesn't
|
|
// have reducer functions so it doesn't need rebasing.
|
|
|
|
updateQueue.baseState = overrideState;
|
|
workInProgress.memoizedState = overrideState;
|
|
|
|
if (workInProgress.flags & ForceClientRender) {
|
|
// Something errored during a previous attempt to hydrate the shell, so we
|
|
// forced a client render.
|
|
var recoverableError = createCapturedValueAtFiber(
|
|
new Error(
|
|
"There was an error while hydrating. Because the error happened outside " +
|
|
"of a Suspense boundary, the entire root will switch to " +
|
|
"client rendering."
|
|
),
|
|
workInProgress
|
|
);
|
|
return mountHostRootWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
nextChildren,
|
|
renderLanes,
|
|
recoverableError
|
|
);
|
|
} else if (nextChildren !== prevChildren) {
|
|
var _recoverableError = createCapturedValueAtFiber(
|
|
new Error(
|
|
"This root received an early update, before anything was able " +
|
|
"hydrate. Switched the entire root to client rendering."
|
|
),
|
|
workInProgress
|
|
);
|
|
|
|
return mountHostRootWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
nextChildren,
|
|
renderLanes,
|
|
_recoverableError
|
|
);
|
|
} else {
|
|
// The outermost shell has not hydrated yet. Start hydrating.
|
|
enterHydrationState(workInProgress);
|
|
|
|
{
|
|
var mutableSourceEagerHydrationData =
|
|
root.mutableSourceEagerHydrationData;
|
|
|
|
if (mutableSourceEagerHydrationData != null) {
|
|
for (var i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
|
|
var mutableSource = mutableSourceEagerHydrationData[i];
|
|
var version = mutableSourceEagerHydrationData[i + 1];
|
|
setWorkInProgressVersion(mutableSource, version);
|
|
}
|
|
}
|
|
}
|
|
|
|
var child = mountChildFibers(
|
|
workInProgress,
|
|
null,
|
|
nextChildren,
|
|
renderLanes
|
|
);
|
|
workInProgress.child = child;
|
|
var node = child;
|
|
|
|
while (node) {
|
|
// Mark each child as hydrating. This is a fast path to know whether this
|
|
// tree is part of a hydrating tree. This is used to determine if a child
|
|
// node has fully mounted yet, and for scheduling event replaying.
|
|
// Conceptually this is similar to Placement in that a new subtree is
|
|
// inserted into the React tree here. It just happens to not need DOM
|
|
// mutations because it already exists.
|
|
node.flags = (node.flags & ~Placement) | Hydrating;
|
|
node = node.sibling;
|
|
}
|
|
}
|
|
} else {
|
|
// Root is not dehydrated. Either this is a client-only root, or it
|
|
// already hydrated.
|
|
resetHydrationState();
|
|
|
|
if (nextChildren === prevChildren) {
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
}
|
|
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function mountHostRootWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
nextChildren,
|
|
renderLanes,
|
|
recoverableError
|
|
) {
|
|
// Revert to client rendering.
|
|
resetHydrationState();
|
|
queueHydrationError(recoverableError);
|
|
workInProgress.flags |= ForceClientRender;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateHostComponent(current, workInProgress, renderLanes) {
|
|
pushHostContext(workInProgress);
|
|
|
|
if (current === null) {
|
|
tryToClaimNextHydratableInstance(workInProgress);
|
|
}
|
|
|
|
var type = workInProgress.type;
|
|
var nextProps = workInProgress.pendingProps;
|
|
var prevProps = current !== null ? current.memoizedProps : null;
|
|
var nextChildren = nextProps.children;
|
|
var isDirectTextChild = shouldSetTextContent(type, nextProps);
|
|
|
|
if (isDirectTextChild) {
|
|
// We special case a direct text child of a host node. This is a common
|
|
// case. We won't handle it as a reified child. We will instead handle
|
|
// this in the host environment that also has access to this prop. That
|
|
// avoids allocating another HostText fiber and traversing it.
|
|
nextChildren = null;
|
|
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
|
|
// If we're switching from a direct text child to a normal child, or to
|
|
// empty, we need to schedule the text content to be reset.
|
|
workInProgress.flags |= ContentReset;
|
|
}
|
|
|
|
markRef(current, workInProgress);
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateHostResource(current, workInProgress, renderLanes) {
|
|
pushHostContext(workInProgress);
|
|
markRef(current, workInProgress);
|
|
var currentProps = current === null ? null : current.memoizedProps;
|
|
workInProgress.memoizedState = getResource(
|
|
workInProgress.type,
|
|
workInProgress.pendingProps,
|
|
currentProps
|
|
); // Resources never have reconciler managed children. It is possible for
|
|
// the host implementation of getResource to consider children in the
|
|
// resource construction but they will otherwise be discarded. In practice
|
|
// this precludes all but the simplest children and Host specific warnings
|
|
// should be implemented to warn when children are passsed when otherwise not
|
|
// expected
|
|
|
|
return null;
|
|
}
|
|
|
|
function updateHostSingleton(current, workInProgress, renderLanes) {
|
|
pushHostContext(workInProgress);
|
|
|
|
if (current === null) {
|
|
claimHydratableSingleton(workInProgress);
|
|
}
|
|
|
|
var nextChildren = workInProgress.pendingProps.children;
|
|
|
|
if (current === null && !getIsHydrating()) {
|
|
// Similar to Portals we append Singleton children in the commit phase. So we
|
|
// Track insertions even on mount.
|
|
// TODO: Consider unifying this with how the root works.
|
|
workInProgress.child = reconcileChildFibers(
|
|
workInProgress,
|
|
null,
|
|
nextChildren,
|
|
renderLanes
|
|
);
|
|
} else {
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
}
|
|
|
|
markRef(current, workInProgress);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateHostText(current, workInProgress) {
|
|
if (current === null) {
|
|
tryToClaimNextHydratableInstance(workInProgress);
|
|
} // Nothing to do here. This is terminal. We'll do the completion step
|
|
// immediately after.
|
|
|
|
return null;
|
|
}
|
|
|
|
function mountLazyComponent(
|
|
_current,
|
|
workInProgress,
|
|
elementType,
|
|
renderLanes
|
|
) {
|
|
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
|
|
var props = workInProgress.pendingProps;
|
|
var lazyComponent = elementType;
|
|
var payload = lazyComponent._payload;
|
|
var init = lazyComponent._init;
|
|
var Component = init(payload); // Store the unwrapped component in the type.
|
|
|
|
workInProgress.type = Component;
|
|
var resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
|
|
var resolvedProps = resolveDefaultProps(Component, props);
|
|
var child;
|
|
|
|
switch (resolvedTag) {
|
|
case FunctionComponent: {
|
|
{
|
|
validateFunctionComponentInDev(workInProgress, Component);
|
|
workInProgress.type = Component = resolveFunctionForHotReloading(
|
|
Component
|
|
);
|
|
}
|
|
|
|
child = updateFunctionComponent(
|
|
null,
|
|
workInProgress,
|
|
Component,
|
|
resolvedProps,
|
|
renderLanes
|
|
);
|
|
return child;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
{
|
|
workInProgress.type = Component = resolveClassForHotReloading(
|
|
Component
|
|
);
|
|
}
|
|
|
|
child = updateClassComponent(
|
|
null,
|
|
workInProgress,
|
|
Component,
|
|
resolvedProps,
|
|
renderLanes
|
|
);
|
|
return child;
|
|
}
|
|
|
|
case ForwardRef: {
|
|
{
|
|
workInProgress.type = Component = resolveForwardRefForHotReloading(
|
|
Component
|
|
);
|
|
}
|
|
|
|
child = updateForwardRef(
|
|
null,
|
|
workInProgress,
|
|
Component,
|
|
resolvedProps,
|
|
renderLanes
|
|
);
|
|
return child;
|
|
}
|
|
|
|
case MemoComponent: {
|
|
{
|
|
if (workInProgress.type !== workInProgress.elementType) {
|
|
var outerPropTypes = Component.propTypes;
|
|
|
|
if (outerPropTypes) {
|
|
checkPropTypes(
|
|
outerPropTypes,
|
|
resolvedProps, // Resolved for outer only
|
|
"prop",
|
|
getComponentNameFromType(Component)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
child = updateMemoComponent(
|
|
null,
|
|
workInProgress,
|
|
Component,
|
|
resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
|
|
renderLanes
|
|
);
|
|
return child;
|
|
}
|
|
}
|
|
|
|
var hint = "";
|
|
|
|
{
|
|
if (
|
|
Component !== null &&
|
|
typeof Component === "object" &&
|
|
Component.$$typeof === REACT_LAZY_TYPE
|
|
) {
|
|
hint = " Did you wrap a component in React.lazy() more than once?";
|
|
}
|
|
} // This message intentionally doesn't mention ForwardRef or MemoComponent
|
|
// because the fact that it's a separate type of work is an
|
|
// implementation detail.
|
|
|
|
throw new Error(
|
|
"Element type is invalid. Received a promise that resolves to: " +
|
|
Component +
|
|
". " +
|
|
("Lazy element type must resolve to a class or function." + hint)
|
|
);
|
|
}
|
|
|
|
function mountIncompleteClassComponent(
|
|
_current,
|
|
workInProgress,
|
|
Component,
|
|
nextProps,
|
|
renderLanes
|
|
) {
|
|
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress); // Promote the fiber to a class and try rendering again.
|
|
|
|
workInProgress.tag = ClassComponent; // The rest of this function is a fork of `updateClassComponent`
|
|
// Push context providers early to prevent context stack mismatches.
|
|
// During mounting we don't know the child context yet as the instance doesn't exist.
|
|
// We will invalidate the child context in finishClassComponent() right after rendering.
|
|
|
|
var hasContext;
|
|
|
|
if (isContextProvider()) {
|
|
hasContext = true;
|
|
} else {
|
|
hasContext = false;
|
|
}
|
|
|
|
prepareToReadContext(workInProgress, renderLanes);
|
|
constructClassInstance(workInProgress, Component, nextProps);
|
|
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
|
|
return finishClassComponent(
|
|
null,
|
|
workInProgress,
|
|
Component,
|
|
true,
|
|
hasContext,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
function mountIndeterminateComponent(
|
|
_current,
|
|
workInProgress,
|
|
Component,
|
|
renderLanes
|
|
) {
|
|
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
|
|
var props = workInProgress.pendingProps;
|
|
var context;
|
|
|
|
prepareToReadContext(workInProgress, renderLanes);
|
|
var value;
|
|
var hasId;
|
|
|
|
{
|
|
if (
|
|
Component.prototype &&
|
|
typeof Component.prototype.render === "function"
|
|
) {
|
|
var componentName = getComponentNameFromType(Component) || "Unknown";
|
|
|
|
if (!didWarnAboutBadClass[componentName]) {
|
|
error(
|
|
"The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
|
|
"This is likely to cause errors. Change %s to extend React.Component instead.",
|
|
componentName,
|
|
componentName
|
|
);
|
|
|
|
didWarnAboutBadClass[componentName] = true;
|
|
}
|
|
}
|
|
|
|
if (workInProgress.mode & StrictLegacyMode) {
|
|
ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
|
|
}
|
|
|
|
setIsRendering(true);
|
|
ReactCurrentOwner$1.current = workInProgress;
|
|
value = renderWithHooks(
|
|
null,
|
|
workInProgress,
|
|
Component,
|
|
props,
|
|
context,
|
|
renderLanes
|
|
);
|
|
hasId = checkDidRenderIdHook();
|
|
setIsRendering(false);
|
|
}
|
|
|
|
workInProgress.flags |= PerformedWork;
|
|
|
|
{
|
|
// Support for module components is deprecated and is removed behind a flag.
|
|
// Whether or not it would crash later, we want to show a good message in DEV first.
|
|
if (
|
|
typeof value === "object" &&
|
|
value !== null &&
|
|
typeof value.render === "function" &&
|
|
value.$$typeof === undefined
|
|
) {
|
|
var _componentName = getComponentNameFromType(Component) || "Unknown";
|
|
|
|
if (!didWarnAboutModulePatternComponent[_componentName]) {
|
|
error(
|
|
"The <%s /> component appears to be a function component that returns a class instance. " +
|
|
"Change %s to a class that extends React.Component instead. " +
|
|
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
|
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
|
|
"cannot be called with `new` by React.",
|
|
_componentName,
|
|
_componentName,
|
|
_componentName
|
|
);
|
|
|
|
didWarnAboutModulePatternComponent[_componentName] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// Proceed under the assumption that this is a function component
|
|
workInProgress.tag = FunctionComponent;
|
|
|
|
{
|
|
if (Component.contextTypes) {
|
|
error(
|
|
"%s uses the legacy contextTypes API which is no longer supported. " +
|
|
"Use React.createContext() with React.useContext() instead.",
|
|
getComponentNameFromType(Component) || "Unknown"
|
|
);
|
|
}
|
|
}
|
|
|
|
if (getIsHydrating() && hasId) {
|
|
pushMaterializedTreeId(workInProgress);
|
|
}
|
|
|
|
reconcileChildren(null, workInProgress, value, renderLanes);
|
|
|
|
{
|
|
validateFunctionComponentInDev(workInProgress, Component);
|
|
}
|
|
|
|
return workInProgress.child;
|
|
}
|
|
}
|
|
|
|
function validateFunctionComponentInDev(workInProgress, Component) {
|
|
{
|
|
if (Component) {
|
|
if (Component.childContextTypes) {
|
|
error(
|
|
"%s(...): childContextTypes cannot be defined on a function component.",
|
|
Component.displayName || Component.name || "Component"
|
|
);
|
|
}
|
|
}
|
|
|
|
if (workInProgress.ref !== null) {
|
|
var info = "";
|
|
var ownerName = getCurrentFiberOwnerNameInDevOrNull();
|
|
|
|
if (ownerName) {
|
|
info += "\n\nCheck the render method of `" + ownerName + "`.";
|
|
}
|
|
|
|
var warningKey = ownerName || "";
|
|
var debugSource = workInProgress._debugSource;
|
|
|
|
if (debugSource) {
|
|
warningKey = debugSource.fileName + ":" + debugSource.lineNumber;
|
|
}
|
|
|
|
if (!didWarnAboutFunctionRefs[warningKey]) {
|
|
didWarnAboutFunctionRefs[warningKey] = true;
|
|
|
|
error(
|
|
"Function components cannot be given refs. " +
|
|
"Attempts to access this ref will fail. " +
|
|
"Did you mean to use React.forwardRef()?%s",
|
|
info
|
|
);
|
|
}
|
|
}
|
|
|
|
if (Component.defaultProps !== undefined) {
|
|
var componentName = getComponentNameFromType(Component) || "Unknown";
|
|
|
|
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
|
|
error(
|
|
"%s: Support for defaultProps will be removed from function components " +
|
|
"in a future major release. Use JavaScript default parameters instead.",
|
|
componentName
|
|
);
|
|
|
|
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
|
|
}
|
|
}
|
|
|
|
if (typeof Component.getDerivedStateFromProps === "function") {
|
|
var _componentName3 = getComponentNameFromType(Component) || "Unknown";
|
|
|
|
if (!didWarnAboutGetDerivedStateOnFunctionComponent[_componentName3]) {
|
|
error(
|
|
"%s: Function components do not support getDerivedStateFromProps.",
|
|
_componentName3
|
|
);
|
|
|
|
didWarnAboutGetDerivedStateOnFunctionComponent[_componentName3] = true;
|
|
}
|
|
}
|
|
|
|
if (
|
|
typeof Component.contextType === "object" &&
|
|
Component.contextType !== null
|
|
) {
|
|
var _componentName4 = getComponentNameFromType(Component) || "Unknown";
|
|
|
|
if (!didWarnAboutContextTypeOnFunctionComponent[_componentName4]) {
|
|
error(
|
|
"%s: Function components do not support contextType.",
|
|
_componentName4
|
|
);
|
|
|
|
didWarnAboutContextTypeOnFunctionComponent[_componentName4] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var SUSPENDED_MARKER = {
|
|
dehydrated: null,
|
|
treeContext: null,
|
|
retryLane: NoLane
|
|
};
|
|
|
|
function mountSuspenseOffscreenState(renderLanes) {
|
|
return {
|
|
baseLanes: renderLanes,
|
|
cachePool: getSuspendedCache()
|
|
};
|
|
}
|
|
|
|
function updateSuspenseOffscreenState(prevOffscreenState, renderLanes) {
|
|
var cachePool = null;
|
|
|
|
{
|
|
var prevCachePool = prevOffscreenState.cachePool;
|
|
|
|
if (prevCachePool !== null) {
|
|
var parentCache = CacheContext._currentValue;
|
|
|
|
if (prevCachePool.parent !== parentCache) {
|
|
// Detected a refresh in the parent. This overrides any previously
|
|
// suspended cache.
|
|
cachePool = {
|
|
parent: parentCache,
|
|
pool: parentCache
|
|
};
|
|
} else {
|
|
// We can reuse the cache from last time. The only thing that would have
|
|
// overridden it is a parent refresh, which we checked for above.
|
|
cachePool = prevCachePool;
|
|
}
|
|
} else {
|
|
// If there's no previous cache pool, grab the current one.
|
|
cachePool = getSuspendedCache();
|
|
}
|
|
}
|
|
|
|
return {
|
|
baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes),
|
|
cachePool: cachePool
|
|
};
|
|
} // TODO: Probably should inline this back
|
|
|
|
function shouldRemainOnFallback(current, workInProgress, renderLanes) {
|
|
// If we're already showing a fallback, there are cases where we need to
|
|
// remain on that fallback regardless of whether the content has resolved.
|
|
// For example, SuspenseList coordinates when nested content appears.
|
|
if (current !== null) {
|
|
var suspenseState = current.memoizedState;
|
|
|
|
if (suspenseState === null) {
|
|
// Currently showing content. Don't hide it, even if ForceSuspenseFallback
|
|
// is true. More precise name might be "ForceRemainSuspenseFallback".
|
|
// Note: This is a factoring smell. Can't remain on a fallback if there's
|
|
// no fallback to remain on.
|
|
return false;
|
|
}
|
|
} // Not currently showing content. Consult the Suspense context.
|
|
|
|
var suspenseContext = suspenseStackCursor.current;
|
|
return hasSuspenseListContext(suspenseContext, ForceSuspenseFallback);
|
|
}
|
|
|
|
function getRemainingWorkInPrimaryTree(current, renderLanes) {
|
|
// TODO: Should not remove render lanes that were pinged during this render
|
|
return removeLanes(current.childLanes, renderLanes);
|
|
}
|
|
|
|
function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
|
var nextProps = workInProgress.pendingProps; // This is used by DevTools to force a boundary to suspend.
|
|
|
|
{
|
|
if (shouldSuspend(workInProgress)) {
|
|
workInProgress.flags |= DidCapture;
|
|
}
|
|
}
|
|
|
|
var showFallback = false;
|
|
var didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
|
|
|
|
if (didSuspend || shouldRemainOnFallback(current)) {
|
|
// Something in this boundary's subtree already suspended. Switch to
|
|
// rendering the fallback children.
|
|
showFallback = true;
|
|
workInProgress.flags &= ~DidCapture;
|
|
} // OK, the next part is confusing. We're about to reconcile the Suspense
|
|
// boundary's children. This involves some custom reconciliation logic. Two
|
|
// main reasons this is so complicated.
|
|
//
|
|
// First, Legacy Mode has different semantics for backwards compatibility. The
|
|
// primary tree will commit in an inconsistent state, so when we do the
|
|
// second pass to render the fallback, we do some exceedingly, uh, clever
|
|
// hacks to make that not totally break. Like transferring effects and
|
|
// deletions from hidden tree. In Concurrent Mode, it's much simpler,
|
|
// because we bailout on the primary tree completely and leave it in its old
|
|
// state, no effects. Same as what we do for Offscreen (except that
|
|
// Offscreen doesn't have the first render pass).
|
|
//
|
|
// Second is hydration. During hydration, the Suspense fiber has a slightly
|
|
// different layout, where the child points to a dehydrated fragment, which
|
|
// contains the DOM rendered by the server.
|
|
//
|
|
// Third, even if you set all that aside, Suspense is like error boundaries in
|
|
// that we first we try to render one tree, and if that fails, we render again
|
|
// and switch to a different tree. Like a try/catch block. So we have to track
|
|
// which branch we're currently rendering. Ideally we would model this using
|
|
// a stack.
|
|
|
|
if (current === null) {
|
|
// Initial mount
|
|
// Special path for hydration
|
|
// If we're currently hydrating, try to hydrate this boundary.
|
|
if (getIsHydrating()) {
|
|
// We must push the suspense handler context *before* attempting to
|
|
// hydrate, to avoid a mismatch in case it errors.
|
|
if (showFallback) {
|
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
|
} else {
|
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
|
}
|
|
|
|
tryToClaimNextHydratableInstance(workInProgress); // This could've been a dehydrated suspense component.
|
|
|
|
var suspenseState = workInProgress.memoizedState;
|
|
|
|
if (suspenseState !== null) {
|
|
var dehydrated = suspenseState.dehydrated;
|
|
|
|
if (dehydrated !== null) {
|
|
return mountDehydratedSuspenseComponent(workInProgress, dehydrated);
|
|
}
|
|
} // If hydration didn't succeed, fall through to the normal Suspense path.
|
|
// To avoid a stack mismatch we need to pop the Suspense handler that we
|
|
// pushed above. This will become less awkward when move the hydration
|
|
// logic to its own fiber.
|
|
|
|
popSuspenseHandler(workInProgress);
|
|
}
|
|
|
|
var nextPrimaryChildren = nextProps.children;
|
|
var nextFallbackChildren = nextProps.fallback;
|
|
|
|
if (showFallback) {
|
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
|
var fallbackFragment = mountSuspenseFallbackChildren(
|
|
workInProgress,
|
|
nextPrimaryChildren,
|
|
nextFallbackChildren,
|
|
renderLanes
|
|
);
|
|
var primaryChildFragment = workInProgress.child;
|
|
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
|
|
renderLanes
|
|
);
|
|
workInProgress.memoizedState = SUSPENDED_MARKER;
|
|
|
|
return fallbackFragment;
|
|
} else if (typeof nextProps.unstable_expectedLoadTime === "number") {
|
|
// This is a CPU-bound tree. Skip this tree and show a placeholder to
|
|
// unblock the surrounding content. Then immediately retry after the
|
|
// initial commit.
|
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
|
|
|
var _fallbackFragment = mountSuspenseFallbackChildren(
|
|
workInProgress,
|
|
nextPrimaryChildren,
|
|
nextFallbackChildren,
|
|
renderLanes
|
|
);
|
|
|
|
var _primaryChildFragment = workInProgress.child;
|
|
_primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
|
|
renderLanes
|
|
);
|
|
workInProgress.memoizedState = SUSPENDED_MARKER; // TODO: Transition Tracing is not yet implemented for CPU Suspense.
|
|
// Since nothing actually suspended, there will nothing to ping this to
|
|
// get it started back up to attempt the next item. While in terms of
|
|
// priority this work has the same priority as this current render, it's
|
|
// not part of the same transition once the transition has committed. If
|
|
// it's sync, we still want to yield so that it can be painted.
|
|
// Conceptually, this is really the same as pinging. We can use any
|
|
// RetryLane even if it's the one currently rendering since we're leaving
|
|
// it behind on this node.
|
|
|
|
workInProgress.lanes = SomeRetryLane;
|
|
return _fallbackFragment;
|
|
} else {
|
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
|
return mountSuspensePrimaryChildren(workInProgress, nextPrimaryChildren);
|
|
}
|
|
} else {
|
|
// This is an update.
|
|
// Special path for hydration
|
|
var prevState = current.memoizedState;
|
|
|
|
if (prevState !== null) {
|
|
var _dehydrated = prevState.dehydrated;
|
|
|
|
if (_dehydrated !== null) {
|
|
return updateDehydratedSuspenseComponent(
|
|
current,
|
|
workInProgress,
|
|
didSuspend,
|
|
nextProps,
|
|
_dehydrated,
|
|
prevState,
|
|
renderLanes
|
|
);
|
|
}
|
|
}
|
|
|
|
if (showFallback) {
|
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
|
var _nextFallbackChildren = nextProps.fallback;
|
|
var _nextPrimaryChildren = nextProps.children;
|
|
var fallbackChildFragment = updateSuspenseFallbackChildren(
|
|
current,
|
|
workInProgress,
|
|
_nextPrimaryChildren,
|
|
_nextFallbackChildren,
|
|
renderLanes
|
|
);
|
|
var _primaryChildFragment2 = workInProgress.child;
|
|
var prevOffscreenState = current.child.memoizedState;
|
|
_primaryChildFragment2.memoizedState =
|
|
prevOffscreenState === null
|
|
? mountSuspenseOffscreenState(renderLanes)
|
|
: updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
|
|
|
|
_primaryChildFragment2.childLanes = getRemainingWorkInPrimaryTree(
|
|
current,
|
|
renderLanes
|
|
);
|
|
workInProgress.memoizedState = SUSPENDED_MARKER;
|
|
return fallbackChildFragment;
|
|
} else {
|
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
|
var _nextPrimaryChildren2 = nextProps.children;
|
|
|
|
var _primaryChildFragment3 = updateSuspensePrimaryChildren(
|
|
current,
|
|
workInProgress,
|
|
_nextPrimaryChildren2,
|
|
renderLanes
|
|
);
|
|
|
|
workInProgress.memoizedState = null;
|
|
return _primaryChildFragment3;
|
|
}
|
|
}
|
|
}
|
|
|
|
function mountSuspensePrimaryChildren(
|
|
workInProgress,
|
|
primaryChildren,
|
|
renderLanes
|
|
) {
|
|
var mode = workInProgress.mode;
|
|
var primaryChildProps = {
|
|
mode: "visible",
|
|
children: primaryChildren
|
|
};
|
|
var primaryChildFragment = mountWorkInProgressOffscreenFiber(
|
|
primaryChildProps,
|
|
mode
|
|
);
|
|
primaryChildFragment.return = workInProgress;
|
|
workInProgress.child = primaryChildFragment;
|
|
return primaryChildFragment;
|
|
}
|
|
|
|
function mountSuspenseFallbackChildren(
|
|
workInProgress,
|
|
primaryChildren,
|
|
fallbackChildren,
|
|
renderLanes
|
|
) {
|
|
var mode = workInProgress.mode;
|
|
var progressedPrimaryFragment = workInProgress.child;
|
|
var primaryChildProps = {
|
|
mode: "hidden",
|
|
children: primaryChildren
|
|
};
|
|
var primaryChildFragment;
|
|
var fallbackChildFragment;
|
|
|
|
if (
|
|
(mode & ConcurrentMode) === NoMode &&
|
|
progressedPrimaryFragment !== null
|
|
) {
|
|
// In legacy mode, we commit the primary tree as if it successfully
|
|
// completed, even though it's in an inconsistent state.
|
|
primaryChildFragment = progressedPrimaryFragment;
|
|
primaryChildFragment.childLanes = NoLanes;
|
|
primaryChildFragment.pendingProps = primaryChildProps;
|
|
|
|
fallbackChildFragment = createFiberFromFragment(
|
|
fallbackChildren,
|
|
mode,
|
|
renderLanes,
|
|
null
|
|
);
|
|
} else {
|
|
primaryChildFragment = mountWorkInProgressOffscreenFiber(
|
|
primaryChildProps,
|
|
mode
|
|
);
|
|
fallbackChildFragment = createFiberFromFragment(
|
|
fallbackChildren,
|
|
mode,
|
|
renderLanes,
|
|
null
|
|
);
|
|
}
|
|
|
|
primaryChildFragment.return = workInProgress;
|
|
fallbackChildFragment.return = workInProgress;
|
|
primaryChildFragment.sibling = fallbackChildFragment;
|
|
workInProgress.child = primaryChildFragment;
|
|
return fallbackChildFragment;
|
|
}
|
|
|
|
function mountWorkInProgressOffscreenFiber(offscreenProps, mode, renderLanes) {
|
|
// The props argument to `createFiberFromOffscreen` is `any` typed, so we use
|
|
// this wrapper function to constrain it.
|
|
return createFiberFromOffscreen(offscreenProps, mode, NoLanes, null);
|
|
}
|
|
|
|
function updateWorkInProgressOffscreenFiber(current, offscreenProps) {
|
|
// The props argument to `createWorkInProgress` is `any` typed, so we use this
|
|
// wrapper function to constrain it.
|
|
return createWorkInProgress(current, offscreenProps);
|
|
}
|
|
|
|
function updateSuspensePrimaryChildren(
|
|
current,
|
|
workInProgress,
|
|
primaryChildren,
|
|
renderLanes
|
|
) {
|
|
var currentPrimaryChildFragment = current.child;
|
|
var currentFallbackChildFragment = currentPrimaryChildFragment.sibling;
|
|
var primaryChildFragment = updateWorkInProgressOffscreenFiber(
|
|
currentPrimaryChildFragment,
|
|
{
|
|
mode: "visible",
|
|
children: primaryChildren
|
|
}
|
|
);
|
|
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
|
primaryChildFragment.lanes = renderLanes;
|
|
}
|
|
|
|
primaryChildFragment.return = workInProgress;
|
|
primaryChildFragment.sibling = null;
|
|
|
|
if (currentFallbackChildFragment !== null) {
|
|
// Delete the fallback child fragment
|
|
var deletions = workInProgress.deletions;
|
|
|
|
if (deletions === null) {
|
|
workInProgress.deletions = [currentFallbackChildFragment];
|
|
workInProgress.flags |= ChildDeletion;
|
|
} else {
|
|
deletions.push(currentFallbackChildFragment);
|
|
}
|
|
}
|
|
|
|
workInProgress.child = primaryChildFragment;
|
|
return primaryChildFragment;
|
|
}
|
|
|
|
function updateSuspenseFallbackChildren(
|
|
current,
|
|
workInProgress,
|
|
primaryChildren,
|
|
fallbackChildren,
|
|
renderLanes
|
|
) {
|
|
var mode = workInProgress.mode;
|
|
var currentPrimaryChildFragment = current.child;
|
|
var currentFallbackChildFragment = currentPrimaryChildFragment.sibling;
|
|
var primaryChildProps = {
|
|
mode: "hidden",
|
|
children: primaryChildren
|
|
};
|
|
var primaryChildFragment;
|
|
|
|
if (
|
|
// In legacy mode, we commit the primary tree as if it successfully
|
|
// completed, even though it's in an inconsistent state.
|
|
(mode & ConcurrentMode) === NoMode && // Make sure we're on the second pass, i.e. the primary child fragment was
|
|
// already cloned. In legacy mode, the only case where this isn't true is
|
|
// when DevTools forces us to display a fallback; we skip the first render
|
|
// pass entirely and go straight to rendering the fallback. (In Concurrent
|
|
// Mode, SuspenseList can also trigger this scenario, but this is a legacy-
|
|
// only codepath.)
|
|
workInProgress.child !== currentPrimaryChildFragment
|
|
) {
|
|
var progressedPrimaryFragment = workInProgress.child;
|
|
primaryChildFragment = progressedPrimaryFragment;
|
|
primaryChildFragment.childLanes = NoLanes;
|
|
primaryChildFragment.pendingProps = primaryChildProps;
|
|
// However, since we're going to remain on the fallback, we no longer want
|
|
// to delete it.
|
|
|
|
workInProgress.deletions = null;
|
|
} else {
|
|
primaryChildFragment = updateWorkInProgressOffscreenFiber(
|
|
currentPrimaryChildFragment,
|
|
primaryChildProps
|
|
); // Since we're reusing a current tree, we need to reuse the flags, too.
|
|
// (We don't do this in legacy mode, because in legacy mode we don't re-use
|
|
// the current tree; see previous branch.)
|
|
|
|
primaryChildFragment.subtreeFlags =
|
|
currentPrimaryChildFragment.subtreeFlags & StaticMask;
|
|
}
|
|
|
|
var fallbackChildFragment;
|
|
|
|
if (currentFallbackChildFragment !== null) {
|
|
fallbackChildFragment = createWorkInProgress(
|
|
currentFallbackChildFragment,
|
|
fallbackChildren
|
|
);
|
|
} else {
|
|
fallbackChildFragment = createFiberFromFragment(
|
|
fallbackChildren,
|
|
mode,
|
|
renderLanes,
|
|
null
|
|
); // Needs a placement effect because the parent (the Suspense boundary) already
|
|
// mounted but this is a new fiber.
|
|
|
|
fallbackChildFragment.flags |= Placement;
|
|
}
|
|
|
|
fallbackChildFragment.return = workInProgress;
|
|
primaryChildFragment.return = workInProgress;
|
|
primaryChildFragment.sibling = fallbackChildFragment;
|
|
workInProgress.child = primaryChildFragment;
|
|
return fallbackChildFragment;
|
|
}
|
|
|
|
function retrySuspenseComponentWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
renderLanes,
|
|
recoverableError
|
|
) {
|
|
// Falling back to client rendering. Because this has performance
|
|
// implications, it's considered a recoverable error, even though the user
|
|
// likely won't observe anything wrong with the UI.
|
|
//
|
|
// The error is passed in as an argument to enforce that every caller provide
|
|
// a custom message, or explicitly opt out (currently the only path that opts
|
|
// out is legacy mode; every concurrent path provides an error).
|
|
if (recoverableError !== null) {
|
|
queueHydrationError(recoverableError);
|
|
} // This will add the old fiber to the deletion list
|
|
|
|
reconcileChildFibers(workInProgress, current.child, null, renderLanes); // We're now not suspended nor dehydrated.
|
|
|
|
var nextProps = workInProgress.pendingProps;
|
|
var primaryChildren = nextProps.children;
|
|
var primaryChildFragment = mountSuspensePrimaryChildren(
|
|
workInProgress,
|
|
primaryChildren
|
|
); // Needs a placement effect because the parent (the Suspense boundary) already
|
|
// mounted but this is a new fiber.
|
|
|
|
primaryChildFragment.flags |= Placement;
|
|
workInProgress.memoizedState = null;
|
|
return primaryChildFragment;
|
|
}
|
|
|
|
function mountSuspenseFallbackAfterRetryWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
primaryChildren,
|
|
fallbackChildren,
|
|
renderLanes
|
|
) {
|
|
var fiberMode = workInProgress.mode;
|
|
var primaryChildProps = {
|
|
mode: "visible",
|
|
children: primaryChildren
|
|
};
|
|
var primaryChildFragment = mountWorkInProgressOffscreenFiber(
|
|
primaryChildProps,
|
|
fiberMode
|
|
);
|
|
var fallbackChildFragment = createFiberFromFragment(
|
|
fallbackChildren,
|
|
fiberMode,
|
|
renderLanes,
|
|
null
|
|
); // Needs a placement effect because the parent (the Suspense
|
|
// boundary) already mounted but this is a new fiber.
|
|
|
|
fallbackChildFragment.flags |= Placement;
|
|
primaryChildFragment.return = workInProgress;
|
|
fallbackChildFragment.return = workInProgress;
|
|
primaryChildFragment.sibling = fallbackChildFragment;
|
|
workInProgress.child = primaryChildFragment;
|
|
|
|
if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
|
|
// We will have dropped the effect list which contains the
|
|
// deletion. We need to reconcile to delete the current child.
|
|
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
|
|
}
|
|
|
|
return fallbackChildFragment;
|
|
}
|
|
|
|
function mountDehydratedSuspenseComponent(
|
|
workInProgress,
|
|
suspenseInstance,
|
|
renderLanes
|
|
) {
|
|
// During the first pass, we'll bail out and not drill into the children.
|
|
// Instead, we'll leave the content in place and try to hydrate it later.
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
|
{
|
|
error(
|
|
"Cannot hydrate Suspense in legacy mode. Switch from " +
|
|
"ReactDOM.hydrate(element, container) to " +
|
|
"ReactDOMClient.hydrateRoot(container, <App />)" +
|
|
".render(element) or remove the Suspense components from " +
|
|
"the server rendered components."
|
|
);
|
|
}
|
|
|
|
workInProgress.lanes = laneToLanes(SyncLane);
|
|
} else if (isSuspenseInstanceFallback(suspenseInstance)) {
|
|
// This is a client-only boundary. Since we won't get any content from the server
|
|
// for this, we need to schedule that at a higher priority based on when it would
|
|
// have timed out. In theory we could render it in this pass but it would have the
|
|
// wrong priority associated with it and will prevent hydration of parent path.
|
|
// Instead, we'll leave work left on it to render it in a separate commit.
|
|
// TODO This time should be the time at which the server rendered response that is
|
|
// a parent to this boundary was displayed. However, since we currently don't have
|
|
// a protocol to transfer that time, we'll just estimate it by using the current
|
|
// time. This will mean that Suspense timeouts are slightly shifted to later than
|
|
// they should be.
|
|
// Schedule a normal pri update to render this content.
|
|
workInProgress.lanes = laneToLanes(DefaultHydrationLane);
|
|
} else {
|
|
// We'll continue hydrating the rest at offscreen priority since we'll already
|
|
// be showing the right content coming from the server, it is no rush.
|
|
workInProgress.lanes = laneToLanes(OffscreenLane);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function updateDehydratedSuspenseComponent(
|
|
current,
|
|
workInProgress,
|
|
didSuspend,
|
|
nextProps,
|
|
suspenseInstance,
|
|
suspenseState,
|
|
renderLanes
|
|
) {
|
|
if (!didSuspend) {
|
|
// This is the first render pass. Attempt to hydrate.
|
|
pushPrimaryTreeSuspenseHandler(workInProgress); // We should never be hydrating at this point because it is the first pass,
|
|
// but after we've already committed once.
|
|
|
|
warnIfHydrating();
|
|
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
|
return retrySuspenseComponentWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
renderLanes,
|
|
null
|
|
);
|
|
}
|
|
|
|
if (isSuspenseInstanceFallback(suspenseInstance)) {
|
|
// This boundary is in a permanent fallback state. In this case, we'll never
|
|
// get an update and we'll never be able to hydrate the final content. Let's just try the
|
|
// client side render instead.
|
|
var digest, message, stack;
|
|
|
|
{
|
|
var _getSuspenseInstanceF = getSuspenseInstanceFallbackErrorDetails(
|
|
suspenseInstance
|
|
);
|
|
|
|
digest = _getSuspenseInstanceF.digest;
|
|
message = _getSuspenseInstanceF.message;
|
|
stack = _getSuspenseInstanceF.stack;
|
|
}
|
|
|
|
var error;
|
|
|
|
if (message) {
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
error = new Error(message);
|
|
} else {
|
|
error = new Error(
|
|
"The server could not finish this Suspense boundary, likely " +
|
|
"due to an error during server rendering. Switched to " +
|
|
"client rendering."
|
|
);
|
|
}
|
|
|
|
error.digest = digest;
|
|
var capturedValue = createCapturedValue(error, digest, stack);
|
|
return retrySuspenseComponentWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
renderLanes,
|
|
capturedValue
|
|
);
|
|
}
|
|
// any context has changed, we need to treat is as if the input might have changed.
|
|
|
|
var hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
|
|
|
|
if (didReceiveUpdate || hasContextChanged) {
|
|
// This boundary has changed since the first render. This means that we are now unable to
|
|
// hydrate it. We might still be able to hydrate it using a higher priority lane.
|
|
var root = getWorkInProgressRoot();
|
|
|
|
if (root !== null) {
|
|
var attemptHydrationAtLane = getBumpedLaneForHydration(
|
|
root,
|
|
renderLanes
|
|
);
|
|
|
|
if (
|
|
attemptHydrationAtLane !== NoLane &&
|
|
attemptHydrationAtLane !== suspenseState.retryLane
|
|
) {
|
|
// Intentionally mutating since this render will get interrupted. This
|
|
// is one of the very rare times where we mutate the current tree
|
|
// during the render phase.
|
|
suspenseState.retryLane = attemptHydrationAtLane; // TODO: Ideally this would inherit the event time of the current render
|
|
|
|
var eventTime = NoTimestamp;
|
|
enqueueConcurrentRenderForLane(current, attemptHydrationAtLane);
|
|
scheduleUpdateOnFiber(
|
|
root,
|
|
current,
|
|
attemptHydrationAtLane,
|
|
eventTime
|
|
); // Throw a special object that signals to the work loop that it should
|
|
// interrupt the current render.
|
|
//
|
|
// Because we're inside a React-only execution stack, we don't
|
|
// strictly need to throw here — we could instead modify some internal
|
|
// work loop state. But using an exception means we don't need to
|
|
// check for this case on every iteration of the work loop. So doing
|
|
// it this way moves the check out of the fast path.
|
|
|
|
throw SelectiveHydrationException;
|
|
}
|
|
} // If we did not selectively hydrate, we'll continue rendering without
|
|
// hydrating. Mark this tree as suspended to prevent it from committing
|
|
// outside a transition.
|
|
//
|
|
// This path should only happen if the hydration lane already suspended.
|
|
// Currently, it also happens during sync updates because there is no
|
|
// hydration lane for sync updates.
|
|
// TODO: We should ideally have a sync hydration lane that we can apply to do
|
|
// a pass where we hydrate this subtree in place using the previous Context and then
|
|
// reapply the update afterwards.
|
|
|
|
renderDidSuspendDelayIfPossible();
|
|
return retrySuspenseComponentWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
renderLanes,
|
|
null
|
|
);
|
|
} else if (isSuspenseInstancePending(suspenseInstance)) {
|
|
// This component is still pending more data from the server, so we can't hydrate its
|
|
// content. We treat it as if this component suspended itself. It might seem as if
|
|
// we could just try to render it client-side instead. However, this will perform a
|
|
// lot of unnecessary work and is unlikely to complete since it often will suspend
|
|
// on missing data anyway. Additionally, the server might be able to render more
|
|
// than we can on the client yet. In that case we'd end up with more fallback states
|
|
// on the client than if we just leave it alone. If the server times out or errors
|
|
// these should update this boundary to the permanent Fallback state instead.
|
|
// Mark it as having captured (i.e. suspended).
|
|
workInProgress.flags |= DidCapture; // Leave the child in place. I.e. the dehydrated fragment.
|
|
|
|
workInProgress.child = current.child; // Register a callback to retry this boundary once the server has sent the result.
|
|
|
|
var retry = retryDehydratedSuspenseBoundary.bind(null, current);
|
|
registerSuspenseInstanceRetry(suspenseInstance, retry);
|
|
return null;
|
|
} else {
|
|
// This is the first attempt.
|
|
reenterHydrationStateFromDehydratedSuspenseInstance(
|
|
workInProgress,
|
|
suspenseInstance,
|
|
suspenseState.treeContext
|
|
);
|
|
var primaryChildren = nextProps.children;
|
|
var primaryChildFragment = mountSuspensePrimaryChildren(
|
|
workInProgress,
|
|
primaryChildren
|
|
); // Mark the children as hydrating. This is a fast path to know whether this
|
|
// tree is part of a hydrating tree. This is used to determine if a child
|
|
// node has fully mounted yet, and for scheduling event replaying.
|
|
// Conceptually this is similar to Placement in that a new subtree is
|
|
// inserted into the React tree here. It just happens to not need DOM
|
|
// mutations because it already exists.
|
|
|
|
primaryChildFragment.flags |= Hydrating;
|
|
return primaryChildFragment;
|
|
}
|
|
} else {
|
|
// This is the second render pass. We already attempted to hydrated, but
|
|
// something either suspended or errored.
|
|
if (workInProgress.flags & ForceClientRender) {
|
|
// Something errored during hydration. Try again without hydrating.
|
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
|
workInProgress.flags &= ~ForceClientRender;
|
|
|
|
var _capturedValue = createCapturedValue(
|
|
new Error(
|
|
"There was an error while hydrating this Suspense boundary. " +
|
|
"Switched to client rendering."
|
|
)
|
|
);
|
|
|
|
return retrySuspenseComponentWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
renderLanes,
|
|
_capturedValue
|
|
);
|
|
} else if (workInProgress.memoizedState !== null) {
|
|
// Something suspended and we should still be in dehydrated mode.
|
|
// Leave the existing child in place.
|
|
// Push to avoid a mismatch
|
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
|
workInProgress.child = current.child; // The dehydrated completion pass expects this flag to be there
|
|
// but the normal suspense pass doesn't.
|
|
|
|
workInProgress.flags |= DidCapture;
|
|
return null;
|
|
} else {
|
|
// Suspended but we should no longer be in dehydrated mode.
|
|
// Therefore we now have to render the fallback.
|
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
|
var nextPrimaryChildren = nextProps.children;
|
|
var nextFallbackChildren = nextProps.fallback;
|
|
var fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
|
|
current,
|
|
workInProgress,
|
|
nextPrimaryChildren,
|
|
nextFallbackChildren,
|
|
renderLanes
|
|
);
|
|
var _primaryChildFragment4 = workInProgress.child;
|
|
_primaryChildFragment4.memoizedState = mountSuspenseOffscreenState(
|
|
renderLanes
|
|
);
|
|
workInProgress.memoizedState = SUSPENDED_MARKER;
|
|
return fallbackChildFragment;
|
|
}
|
|
}
|
|
}
|
|
|
|
function scheduleSuspenseWorkOnFiber(fiber, renderLanes, propagationRoot) {
|
|
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
|
|
var alternate = fiber.alternate;
|
|
|
|
if (alternate !== null) {
|
|
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
|
|
}
|
|
|
|
scheduleContextWorkOnParentPath(fiber.return, renderLanes, propagationRoot);
|
|
}
|
|
|
|
function propagateSuspenseContextChange(
|
|
workInProgress,
|
|
firstChild,
|
|
renderLanes
|
|
) {
|
|
// Mark any Suspense boundaries with fallbacks as having work to do.
|
|
// If they were previously forced into fallbacks, they may now be able
|
|
// to unblock.
|
|
var node = firstChild;
|
|
|
|
while (node !== null) {
|
|
if (node.tag === SuspenseComponent) {
|
|
var state = node.memoizedState;
|
|
|
|
if (state !== null) {
|
|
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
|
|
}
|
|
} else if (node.tag === SuspenseListComponent) {
|
|
// If the tail is hidden there might not be an Suspense boundaries
|
|
// to schedule work on. In this case we have to schedule it on the
|
|
// list itself.
|
|
// We don't have to traverse to the children of the list since
|
|
// the list will propagate the change when it rerenders.
|
|
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
|
|
} else if (node.child !== null) {
|
|
node.child.return = node;
|
|
node = node.child;
|
|
continue;
|
|
}
|
|
|
|
if (node === workInProgress) {
|
|
return;
|
|
} // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
while (node.sibling === null) {
|
|
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
if (node.return === null || node.return === workInProgress) {
|
|
return;
|
|
}
|
|
|
|
node = node.return;
|
|
} // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
node.sibling.return = node.return;
|
|
node = node.sibling;
|
|
}
|
|
}
|
|
|
|
function findLastContentRow(firstChild) {
|
|
// This is going to find the last row among these children that is already
|
|
// showing content on the screen, as opposed to being in fallback state or
|
|
// new. If a row has multiple Suspense boundaries, any of them being in the
|
|
// fallback state, counts as the whole row being in a fallback state.
|
|
// Note that the "rows" will be workInProgress, but any nested children
|
|
// will still be current since we haven't rendered them yet. The mounted
|
|
// order may not be the same as the new order. We use the new order.
|
|
var row = firstChild;
|
|
var lastContentRow = null;
|
|
|
|
while (row !== null) {
|
|
var currentRow = row.alternate; // New rows can't be content rows.
|
|
|
|
if (currentRow !== null && findFirstSuspended(currentRow) === null) {
|
|
lastContentRow = row;
|
|
}
|
|
|
|
row = row.sibling;
|
|
}
|
|
|
|
return lastContentRow;
|
|
}
|
|
|
|
function validateRevealOrder(revealOrder) {
|
|
{
|
|
if (
|
|
revealOrder !== undefined &&
|
|
revealOrder !== "forwards" &&
|
|
revealOrder !== "backwards" &&
|
|
revealOrder !== "together" &&
|
|
!didWarnAboutRevealOrder[revealOrder]
|
|
) {
|
|
didWarnAboutRevealOrder[revealOrder] = true;
|
|
|
|
if (typeof revealOrder === "string") {
|
|
switch (revealOrder.toLowerCase()) {
|
|
case "together":
|
|
case "forwards":
|
|
case "backwards": {
|
|
error(
|
|
'"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
|
|
'Use lowercase "%s" instead.',
|
|
revealOrder,
|
|
revealOrder.toLowerCase()
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
case "forward":
|
|
case "backward": {
|
|
error(
|
|
'"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
|
|
'React uses the -s suffix in the spelling. Use "%ss" instead.',
|
|
revealOrder,
|
|
revealOrder.toLowerCase()
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
error(
|
|
'"%s" is not a supported revealOrder on <SuspenseList />. ' +
|
|
'Did you mean "together", "forwards" or "backwards"?',
|
|
revealOrder
|
|
);
|
|
|
|
break;
|
|
}
|
|
} else {
|
|
error(
|
|
"%s is not a supported value for revealOrder on <SuspenseList />. " +
|
|
'Did you mean "together", "forwards" or "backwards"?',
|
|
revealOrder
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateTailOptions(tailMode, revealOrder) {
|
|
{
|
|
if (tailMode !== undefined && !didWarnAboutTailOptions[tailMode]) {
|
|
if (tailMode !== "collapsed" && tailMode !== "hidden") {
|
|
didWarnAboutTailOptions[tailMode] = true;
|
|
|
|
error(
|
|
'"%s" is not a supported value for tail on <SuspenseList />. ' +
|
|
'Did you mean "collapsed" or "hidden"?',
|
|
tailMode
|
|
);
|
|
} else if (revealOrder !== "forwards" && revealOrder !== "backwards") {
|
|
didWarnAboutTailOptions[tailMode] = true;
|
|
|
|
error(
|
|
'<SuspenseList tail="%s" /> is only valid if revealOrder is ' +
|
|
'"forwards" or "backwards". ' +
|
|
'Did you mean to specify revealOrder="forwards"?',
|
|
tailMode
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateSuspenseListNestedChild(childSlot, index) {
|
|
{
|
|
var isAnArray = isArray(childSlot);
|
|
var isIterable =
|
|
!isAnArray && typeof getIteratorFn(childSlot) === "function";
|
|
|
|
if (isAnArray || isIterable) {
|
|
var type = isAnArray ? "array" : "iterable";
|
|
|
|
error(
|
|
"A nested %s was passed to row #%s in <SuspenseList />. Wrap it in " +
|
|
"an additional SuspenseList to configure its revealOrder: " +
|
|
"<SuspenseList revealOrder=...> ... " +
|
|
"<SuspenseList revealOrder=...>{%s}</SuspenseList> ... " +
|
|
"</SuspenseList>",
|
|
type,
|
|
index,
|
|
type
|
|
);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function validateSuspenseListChildren(children, revealOrder) {
|
|
{
|
|
if (
|
|
(revealOrder === "forwards" || revealOrder === "backwards") &&
|
|
children !== undefined &&
|
|
children !== null &&
|
|
children !== false
|
|
) {
|
|
if (isArray(children)) {
|
|
for (var i = 0; i < children.length; i++) {
|
|
if (!validateSuspenseListNestedChild(children[i], i)) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
var iteratorFn = getIteratorFn(children);
|
|
|
|
if (typeof iteratorFn === "function") {
|
|
var childrenIterator = iteratorFn.call(children);
|
|
|
|
if (childrenIterator) {
|
|
var step = childrenIterator.next();
|
|
var _i = 0;
|
|
|
|
for (; !step.done; step = childrenIterator.next()) {
|
|
if (!validateSuspenseListNestedChild(step.value, _i)) {
|
|
return;
|
|
}
|
|
|
|
_i++;
|
|
}
|
|
}
|
|
} else {
|
|
error(
|
|
'A single row was passed to a <SuspenseList revealOrder="%s" />. ' +
|
|
"This is not useful since it needs multiple rows. " +
|
|
"Did you mean to pass multiple children or an array?",
|
|
revealOrder
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function initSuspenseListRenderState(
|
|
workInProgress,
|
|
isBackwards,
|
|
tail,
|
|
lastContentRow,
|
|
tailMode
|
|
) {
|
|
var renderState = workInProgress.memoizedState;
|
|
|
|
if (renderState === null) {
|
|
workInProgress.memoizedState = {
|
|
isBackwards: isBackwards,
|
|
rendering: null,
|
|
renderingStartTime: 0,
|
|
last: lastContentRow,
|
|
tail: tail,
|
|
tailMode: tailMode
|
|
};
|
|
} else {
|
|
// We can reuse the existing object from previous renders.
|
|
renderState.isBackwards = isBackwards;
|
|
renderState.rendering = null;
|
|
renderState.renderingStartTime = 0;
|
|
renderState.last = lastContentRow;
|
|
renderState.tail = tail;
|
|
renderState.tailMode = tailMode;
|
|
}
|
|
} // This can end up rendering this component multiple passes.
|
|
// The first pass splits the children fibers into two sets. A head and tail.
|
|
// We first render the head. If anything is in fallback state, we do another
|
|
// pass through beginWork to rerender all children (including the tail) with
|
|
// the force suspend context. If the first render didn't have anything in
|
|
// in fallback state. Then we render each row in the tail one-by-one.
|
|
// That happens in the completeWork phase without going back to beginWork.
|
|
|
|
function updateSuspenseListComponent(current, workInProgress, renderLanes) {
|
|
var nextProps = workInProgress.pendingProps;
|
|
var revealOrder = nextProps.revealOrder;
|
|
var tailMode = nextProps.tail;
|
|
var newChildren = nextProps.children;
|
|
validateRevealOrder(revealOrder);
|
|
validateTailOptions(tailMode, revealOrder);
|
|
validateSuspenseListChildren(newChildren, revealOrder);
|
|
reconcileChildren(current, workInProgress, newChildren, renderLanes);
|
|
var suspenseContext = suspenseStackCursor.current;
|
|
var shouldForceFallback = hasSuspenseListContext(
|
|
suspenseContext,
|
|
ForceSuspenseFallback
|
|
);
|
|
|
|
if (shouldForceFallback) {
|
|
suspenseContext = setShallowSuspenseListContext(
|
|
suspenseContext,
|
|
ForceSuspenseFallback
|
|
);
|
|
workInProgress.flags |= DidCapture;
|
|
} else {
|
|
var didSuspendBefore =
|
|
current !== null && (current.flags & DidCapture) !== NoFlags;
|
|
|
|
if (didSuspendBefore) {
|
|
// If we previously forced a fallback, we need to schedule work
|
|
// on any nested boundaries to let them know to try to render
|
|
// again. This is the same as context updating.
|
|
propagateSuspenseContextChange(
|
|
workInProgress,
|
|
workInProgress.child,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
|
|
}
|
|
|
|
pushSuspenseListContext(workInProgress, suspenseContext);
|
|
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
|
// In legacy mode, SuspenseList doesn't work so we just
|
|
// use make it a noop by treating it as the default revealOrder.
|
|
workInProgress.memoizedState = null;
|
|
} else {
|
|
switch (revealOrder) {
|
|
case "forwards": {
|
|
var lastContentRow = findLastContentRow(workInProgress.child);
|
|
var tail;
|
|
|
|
if (lastContentRow === null) {
|
|
// The whole list is part of the tail.
|
|
// TODO: We could fast path by just rendering the tail now.
|
|
tail = workInProgress.child;
|
|
workInProgress.child = null;
|
|
} else {
|
|
// Disconnect the tail rows after the content row.
|
|
// We're going to render them separately later.
|
|
tail = lastContentRow.sibling;
|
|
lastContentRow.sibling = null;
|
|
}
|
|
|
|
initSuspenseListRenderState(
|
|
workInProgress,
|
|
false, // isBackwards
|
|
tail,
|
|
lastContentRow,
|
|
tailMode
|
|
);
|
|
break;
|
|
}
|
|
|
|
case "backwards": {
|
|
// We're going to find the first row that has existing content.
|
|
// At the same time we're going to reverse the list of everything
|
|
// we pass in the meantime. That's going to be our tail in reverse
|
|
// order.
|
|
var _tail = null;
|
|
var row = workInProgress.child;
|
|
workInProgress.child = null;
|
|
|
|
while (row !== null) {
|
|
var currentRow = row.alternate; // New rows can't be content rows.
|
|
|
|
if (currentRow !== null && findFirstSuspended(currentRow) === null) {
|
|
// This is the beginning of the main content.
|
|
workInProgress.child = row;
|
|
break;
|
|
}
|
|
|
|
var nextRow = row.sibling;
|
|
row.sibling = _tail;
|
|
_tail = row;
|
|
row = nextRow;
|
|
} // TODO: If workInProgress.child is null, we can continue on the tail immediately.
|
|
|
|
initSuspenseListRenderState(
|
|
workInProgress,
|
|
true, // isBackwards
|
|
_tail,
|
|
null, // last
|
|
tailMode
|
|
);
|
|
break;
|
|
}
|
|
|
|
case "together": {
|
|
initSuspenseListRenderState(
|
|
workInProgress,
|
|
false, // isBackwards
|
|
null, // tail
|
|
null, // last
|
|
undefined
|
|
);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
// The default reveal order is the same as not having
|
|
// a boundary.
|
|
workInProgress.memoizedState = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updatePortalComponent(current, workInProgress, renderLanes) {
|
|
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
|
|
var nextChildren = workInProgress.pendingProps;
|
|
|
|
if (current === null) {
|
|
// Portals are special because we don't append the children during mount
|
|
// but at commit. Therefore we need to track insertions which the normal
|
|
// flow doesn't do during mount. This doesn't happen at the root because
|
|
// the root always starts with a "current" with a null child.
|
|
// TODO: Consider unifying this with how the root works.
|
|
workInProgress.child = reconcileChildFibers(
|
|
workInProgress,
|
|
null,
|
|
nextChildren,
|
|
renderLanes
|
|
);
|
|
} else {
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
}
|
|
|
|
return workInProgress.child;
|
|
}
|
|
|
|
var hasWarnedAboutUsingNoValuePropOnContextProvider = false;
|
|
|
|
function updateContextProvider(current, workInProgress, renderLanes) {
|
|
var providerType = workInProgress.type;
|
|
var context = providerType._context;
|
|
var newProps = workInProgress.pendingProps;
|
|
var oldProps = workInProgress.memoizedProps;
|
|
var newValue = newProps.value;
|
|
|
|
{
|
|
if (!("value" in newProps)) {
|
|
if (!hasWarnedAboutUsingNoValuePropOnContextProvider) {
|
|
hasWarnedAboutUsingNoValuePropOnContextProvider = true;
|
|
|
|
error(
|
|
"The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?"
|
|
);
|
|
}
|
|
}
|
|
|
|
var providerPropTypes = workInProgress.type.propTypes;
|
|
|
|
if (providerPropTypes) {
|
|
checkPropTypes(providerPropTypes, newProps, "prop", "Context.Provider");
|
|
}
|
|
}
|
|
|
|
pushProvider(workInProgress, context, newValue);
|
|
|
|
{
|
|
if (oldProps !== null) {
|
|
var oldValue = oldProps.value;
|
|
|
|
if (objectIs(oldValue, newValue)) {
|
|
// No change. Bailout early if children are the same.
|
|
if (oldProps.children === newProps.children && !hasContextChanged()) {
|
|
return bailoutOnAlreadyFinishedWork(
|
|
current,
|
|
workInProgress,
|
|
renderLanes
|
|
);
|
|
}
|
|
} else {
|
|
// The context value changed. Search for matching consumers and schedule
|
|
// them to update.
|
|
propagateContextChange(workInProgress, context, renderLanes);
|
|
}
|
|
}
|
|
}
|
|
|
|
var newChildren = newProps.children;
|
|
reconcileChildren(current, workInProgress, newChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
var hasWarnedAboutUsingContextAsConsumer = false;
|
|
|
|
function updateContextConsumer(current, workInProgress, renderLanes) {
|
|
var context = workInProgress.type; // The logic below for Context differs depending on PROD or DEV mode. In
|
|
// DEV mode, we create a separate object for Context.Consumer that acts
|
|
// like a proxy to Context. This proxy object adds unnecessary code in PROD
|
|
// so we use the old behaviour (Context.Consumer references Context) to
|
|
// reduce size and overhead. The separate object references context via
|
|
// a property called "_context", which also gives us the ability to check
|
|
// in DEV mode if this property exists or not and warn if it does not.
|
|
|
|
{
|
|
if (context._context === undefined) {
|
|
// This may be because it's a Context (rather than a Consumer).
|
|
// Or it may be because it's older React where they're the same thing.
|
|
// We only want to warn if we're sure it's a new React.
|
|
if (context !== context.Consumer) {
|
|
if (!hasWarnedAboutUsingContextAsConsumer) {
|
|
hasWarnedAboutUsingContextAsConsumer = true;
|
|
|
|
error(
|
|
"Rendering <Context> directly is not supported and will be removed in " +
|
|
"a future major release. Did you mean to render <Context.Consumer> instead?"
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
context = context._context;
|
|
}
|
|
}
|
|
|
|
var newProps = workInProgress.pendingProps;
|
|
var render = newProps.children;
|
|
|
|
{
|
|
if (typeof render !== "function") {
|
|
error(
|
|
"A context consumer was rendered with multiple children, or a child " +
|
|
"that isn't a function. A context consumer expects a single child " +
|
|
"that is a function. If you did pass a function, make sure there " +
|
|
"is no trailing or leading whitespace around it."
|
|
);
|
|
}
|
|
}
|
|
|
|
prepareToReadContext(workInProgress, renderLanes);
|
|
var newValue = readContext(context);
|
|
|
|
var newChildren;
|
|
|
|
{
|
|
ReactCurrentOwner$1.current = workInProgress;
|
|
setIsRendering(true);
|
|
newChildren = render(newValue);
|
|
setIsRendering(false);
|
|
}
|
|
|
|
workInProgress.flags |= PerformedWork;
|
|
reconcileChildren(current, workInProgress, newChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function updateScopeComponent(current, workInProgress, renderLanes) {
|
|
var nextProps = workInProgress.pendingProps;
|
|
var nextChildren = nextProps.children;
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function markWorkInProgressReceivedUpdate() {
|
|
didReceiveUpdate = true;
|
|
}
|
|
|
|
function resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress) {
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
|
if (current !== null) {
|
|
// A lazy component only mounts if it suspended inside a non-
|
|
// concurrent tree, in an inconsistent state. We want to treat it like
|
|
// a new mount, even though an empty version of it already committed.
|
|
// Disconnect the alternate pointers.
|
|
current.alternate = null;
|
|
workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect
|
|
|
|
workInProgress.flags |= Placement;
|
|
}
|
|
}
|
|
}
|
|
|
|
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
|
|
if (current !== null) {
|
|
// Reuse previous dependencies
|
|
workInProgress.dependencies = current.dependencies;
|
|
}
|
|
|
|
markSkippedUpdateLanes(workInProgress.lanes); // Check if the children have any pending work.
|
|
|
|
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
|
|
// The children don't have any work either. We can skip them.
|
|
// TODO: Once we add back resuming, we should check if the children are
|
|
// a work-in-progress set. If so, we need to transfer their effects.
|
|
{
|
|
return null;
|
|
}
|
|
} // This fiber doesn't have work, but its subtree does. Clone the child
|
|
// fibers and continue.
|
|
|
|
cloneChildFibers(current, workInProgress);
|
|
return workInProgress.child;
|
|
}
|
|
|
|
function remountFiber(current, oldWorkInProgress, newWorkInProgress) {
|
|
{
|
|
var returnFiber = oldWorkInProgress.return;
|
|
|
|
if (returnFiber === null) {
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
throw new Error("Cannot swap the root fiber.");
|
|
} // Disconnect from the old current.
|
|
// It will get deleted.
|
|
|
|
current.alternate = null;
|
|
oldWorkInProgress.alternate = null; // Connect to the new tree.
|
|
|
|
newWorkInProgress.index = oldWorkInProgress.index;
|
|
newWorkInProgress.sibling = oldWorkInProgress.sibling;
|
|
newWorkInProgress.return = oldWorkInProgress.return;
|
|
newWorkInProgress.ref = oldWorkInProgress.ref; // Replace the child/sibling pointers above it.
|
|
|
|
if (oldWorkInProgress === returnFiber.child) {
|
|
returnFiber.child = newWorkInProgress;
|
|
} else {
|
|
var prevSibling = returnFiber.child;
|
|
|
|
if (prevSibling === null) {
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
throw new Error("Expected parent to have a child.");
|
|
} // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
while (prevSibling.sibling !== oldWorkInProgress) {
|
|
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
prevSibling = prevSibling.sibling;
|
|
|
|
if (prevSibling === null) {
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
throw new Error("Expected to find the previous sibling.");
|
|
}
|
|
} // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
prevSibling.sibling = newWorkInProgress;
|
|
} // Delete the old fiber and place the new one.
|
|
// Since the old fiber is disconnected, we have to schedule it manually.
|
|
|
|
var deletions = returnFiber.deletions;
|
|
|
|
if (deletions === null) {
|
|
returnFiber.deletions = [current];
|
|
returnFiber.flags |= ChildDeletion;
|
|
} else {
|
|
deletions.push(current);
|
|
}
|
|
|
|
newWorkInProgress.flags |= Placement; // Restart work from the new fiber.
|
|
|
|
return newWorkInProgress;
|
|
}
|
|
}
|
|
|
|
function checkScheduledUpdateOrContext(current, renderLanes) {
|
|
// Before performing an early bailout, we must check if there are pending
|
|
// updates or context.
|
|
var updateLanes = current.lanes;
|
|
|
|
if (includesSomeLane(updateLanes, renderLanes)) {
|
|
return true;
|
|
} // No pending update, but because context is propagated lazily, we need
|
|
|
|
return false;
|
|
}
|
|
|
|
function attemptEarlyBailoutIfNoScheduledUpdate(
|
|
current,
|
|
workInProgress,
|
|
renderLanes
|
|
) {
|
|
// This fiber does not have any pending work. Bailout without entering
|
|
// the begin phase. There's still some bookkeeping we that needs to be done
|
|
// in this optimized path, mostly pushing stuff onto the stack.
|
|
switch (workInProgress.tag) {
|
|
case HostRoot:
|
|
pushHostRootContext(workInProgress);
|
|
var root = workInProgress.stateNode;
|
|
|
|
{
|
|
var cache = current.memoizedState.cache;
|
|
pushCacheProvider(workInProgress, cache);
|
|
}
|
|
|
|
resetHydrationState();
|
|
break;
|
|
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
pushHostContext(workInProgress);
|
|
break;
|
|
|
|
case ClassComponent: {
|
|
var Component = workInProgress.type;
|
|
|
|
break;
|
|
}
|
|
|
|
case HostPortal:
|
|
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
|
|
break;
|
|
|
|
case ContextProvider: {
|
|
var newValue = workInProgress.memoizedProps.value;
|
|
var context = workInProgress.type._context;
|
|
pushProvider(workInProgress, context, newValue);
|
|
break;
|
|
}
|
|
|
|
case Profiler:
|
|
break;
|
|
|
|
case SuspenseComponent: {
|
|
var state = workInProgress.memoizedState;
|
|
|
|
if (state !== null) {
|
|
if (state.dehydrated !== null) {
|
|
// We're not going to render the children, so this is just to maintain
|
|
// push/pop symmetry
|
|
pushPrimaryTreeSuspenseHandler(workInProgress); // We know that this component will suspend again because if it has
|
|
// been unsuspended it has committed as a resolved Suspense component.
|
|
// If it needs to be retried, it should have work scheduled on it.
|
|
|
|
workInProgress.flags |= DidCapture; // We should never render the children of a dehydrated boundary until we
|
|
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
|
|
|
|
return null;
|
|
} // If this boundary is currently timed out, we need to decide
|
|
// whether to retry the primary children, or to skip over it and
|
|
// go straight to the fallback. Check the priority of the primary
|
|
// child fragment.
|
|
|
|
var primaryChildFragment = workInProgress.child;
|
|
var primaryChildLanes = primaryChildFragment.childLanes;
|
|
|
|
if (includesSomeLane(renderLanes, primaryChildLanes)) {
|
|
// The primary children have pending work. Use the normal path
|
|
// to attempt to render the primary children again.
|
|
return updateSuspenseComponent(current, workInProgress, renderLanes);
|
|
} else {
|
|
// The primary child fragment does not have pending work marked
|
|
// on it
|
|
pushPrimaryTreeSuspenseHandler(workInProgress); // The primary children do not have pending work with sufficient
|
|
// priority. Bailout.
|
|
|
|
var child = bailoutOnAlreadyFinishedWork(
|
|
current,
|
|
workInProgress,
|
|
renderLanes
|
|
);
|
|
|
|
if (child !== null) {
|
|
// The fallback children have pending work. Skip over the
|
|
// primary children and work on the fallback.
|
|
return child.sibling;
|
|
} else {
|
|
// Note: We can return `null` here because we already checked
|
|
// whether there were nested context consumers, via the call to
|
|
// `bailoutOnAlreadyFinishedWork` above.
|
|
return null;
|
|
}
|
|
}
|
|
} else {
|
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SuspenseListComponent: {
|
|
var didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
|
|
|
|
var _hasChildWork = includesSomeLane(
|
|
renderLanes,
|
|
workInProgress.childLanes
|
|
);
|
|
|
|
if (didSuspendBefore) {
|
|
if (_hasChildWork) {
|
|
// If something was in fallback state last time, and we have all the
|
|
// same children then we're still in progressive loading state.
|
|
// Something might get unblocked by state updates or retries in the
|
|
// tree which will affect the tail. So we need to use the normal
|
|
// path to compute the correct tail.
|
|
return updateSuspenseListComponent(
|
|
current,
|
|
workInProgress,
|
|
renderLanes
|
|
);
|
|
} // If none of the children had any work, that means that none of
|
|
// them got retried so they'll still be blocked in the same way
|
|
// as before. We can fast bail out.
|
|
|
|
workInProgress.flags |= DidCapture;
|
|
} // If nothing suspended before and we're rendering the same children,
|
|
// then the tail doesn't matter. Anything new that suspends will work
|
|
// in the "together" mode, so we can continue from the state we had.
|
|
|
|
var renderState = workInProgress.memoizedState;
|
|
|
|
if (renderState !== null) {
|
|
// Reset to the "together" mode in case we've started a different
|
|
// update in the past but didn't complete it.
|
|
renderState.rendering = null;
|
|
renderState.tail = null;
|
|
renderState.lastEffect = null;
|
|
}
|
|
|
|
pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
|
|
|
|
if (_hasChildWork) {
|
|
break;
|
|
} else {
|
|
// If none of the children had any work, that means that none of
|
|
// them got retried so they'll still be blocked in the same way
|
|
// as before. We can fast bail out.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
case OffscreenComponent:
|
|
case LegacyHiddenComponent: {
|
|
// Need to check if the tree still needs to be deferred. This is
|
|
// almost identical to the logic used in the normal update path,
|
|
// so we'll just enter that. The only difference is we'll bail out
|
|
// at the next level instead of this one, because the child props
|
|
// have not changed. Which is fine.
|
|
// TODO: Probably should refactor `beginWork` to split the bailout
|
|
// path from the normal path. I'm tempted to do a labeled break here
|
|
// but I won't :)
|
|
workInProgress.lanes = NoLanes;
|
|
return updateOffscreenComponent(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
case CacheComponent: {
|
|
{
|
|
var _cache = current.memoizedState.cache;
|
|
pushCacheProvider(workInProgress, _cache);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
function beginWork(current, workInProgress, renderLanes) {
|
|
{
|
|
if (workInProgress._debugNeedsRemount && current !== null) {
|
|
// This will restart the begin phase with a new fiber.
|
|
return remountFiber(
|
|
current,
|
|
workInProgress,
|
|
createFiberFromTypeAndProps(
|
|
workInProgress.type,
|
|
workInProgress.key,
|
|
workInProgress.pendingProps,
|
|
workInProgress._debugOwner || null,
|
|
workInProgress.mode,
|
|
workInProgress.lanes
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (current !== null) {
|
|
var oldProps = current.memoizedProps;
|
|
var newProps = workInProgress.pendingProps;
|
|
|
|
if (
|
|
oldProps !== newProps ||
|
|
hasContextChanged() || // Force a re-render if the implementation changed due to hot reload:
|
|
workInProgress.type !== current.type
|
|
) {
|
|
// If props or context changed, mark the fiber as having performed work.
|
|
// This may be unset if the props are determined to be equal later (memo).
|
|
didReceiveUpdate = true;
|
|
} else {
|
|
// Neither props nor legacy context changes. Check if there's a pending
|
|
// update or context change.
|
|
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
|
|
current,
|
|
renderLanes
|
|
);
|
|
|
|
if (
|
|
!hasScheduledUpdateOrContext && // If this is the second pass of an error or suspense boundary, there
|
|
// may not be work scheduled on `current`, so we check for this flag.
|
|
(workInProgress.flags & DidCapture) === NoFlags
|
|
) {
|
|
// No pending updates or context. Bail out now.
|
|
didReceiveUpdate = false;
|
|
return attemptEarlyBailoutIfNoScheduledUpdate(
|
|
current,
|
|
workInProgress,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
|
|
// This is a special case that only exists for legacy mode.
|
|
// See https://github.com/facebook/react/pull/19216.
|
|
didReceiveUpdate = true;
|
|
} else {
|
|
// An update was scheduled on this fiber, but there are no new props
|
|
// nor legacy context. Set this to false. If an update queue or context
|
|
// consumer produces a changed value, it will set this to true. Otherwise,
|
|
// the component will assume the children have not changed and bail out.
|
|
didReceiveUpdate = false;
|
|
}
|
|
}
|
|
} else {
|
|
didReceiveUpdate = false;
|
|
|
|
if (getIsHydrating() && isForkedChild(workInProgress)) {
|
|
// Check if this child belongs to a list of muliple children in
|
|
// its parent.
|
|
//
|
|
// In a true multi-threaded implementation, we would render children on
|
|
// parallel threads. This would represent the beginning of a new render
|
|
// thread for this subtree.
|
|
//
|
|
// We only use this for id generation during hydration, which is why the
|
|
// logic is located in this special branch.
|
|
var slotIndex = workInProgress.index;
|
|
var numberOfForks = getForksAtLevel();
|
|
pushTreeId(workInProgress, numberOfForks, slotIndex);
|
|
}
|
|
} // Before entering the begin phase, clear pending update priority.
|
|
// TODO: This assumes that we're about to evaluate the component and process
|
|
// the update queue. However, there's an exception: SimpleMemoComponent
|
|
// sometimes bails out later in the begin phase. This indicates that we should
|
|
// move this assignment out of the common path and into each branch.
|
|
|
|
workInProgress.lanes = NoLanes;
|
|
|
|
switch (workInProgress.tag) {
|
|
case IndeterminateComponent: {
|
|
return mountIndeterminateComponent(
|
|
current,
|
|
workInProgress,
|
|
workInProgress.type,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
case LazyComponent: {
|
|
var elementType = workInProgress.elementType;
|
|
return mountLazyComponent(
|
|
current,
|
|
workInProgress,
|
|
elementType,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
case FunctionComponent: {
|
|
var Component = workInProgress.type;
|
|
var unresolvedProps = workInProgress.pendingProps;
|
|
var resolvedProps =
|
|
workInProgress.elementType === Component
|
|
? unresolvedProps
|
|
: resolveDefaultProps(Component, unresolvedProps);
|
|
return updateFunctionComponent(
|
|
current,
|
|
workInProgress,
|
|
Component,
|
|
resolvedProps,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
case ClassComponent: {
|
|
var _Component = workInProgress.type;
|
|
var _unresolvedProps = workInProgress.pendingProps;
|
|
|
|
var _resolvedProps =
|
|
workInProgress.elementType === _Component
|
|
? _unresolvedProps
|
|
: resolveDefaultProps(_Component, _unresolvedProps);
|
|
|
|
return updateClassComponent(
|
|
current,
|
|
workInProgress,
|
|
_Component,
|
|
_resolvedProps,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
case HostRoot:
|
|
return updateHostRoot(current, workInProgress, renderLanes);
|
|
|
|
case HostResource: {
|
|
return updateHostResource(current, workInProgress);
|
|
}
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case HostSingleton: {
|
|
return updateHostSingleton(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case HostComponent:
|
|
return updateHostComponent(current, workInProgress, renderLanes);
|
|
|
|
case HostText:
|
|
return updateHostText(current, workInProgress);
|
|
|
|
case SuspenseComponent:
|
|
return updateSuspenseComponent(current, workInProgress, renderLanes);
|
|
|
|
case HostPortal:
|
|
return updatePortalComponent(current, workInProgress, renderLanes);
|
|
|
|
case ForwardRef: {
|
|
var type = workInProgress.type;
|
|
var _unresolvedProps2 = workInProgress.pendingProps;
|
|
|
|
var _resolvedProps2 =
|
|
workInProgress.elementType === type
|
|
? _unresolvedProps2
|
|
: resolveDefaultProps(type, _unresolvedProps2);
|
|
|
|
return updateForwardRef(
|
|
current,
|
|
workInProgress,
|
|
type,
|
|
_resolvedProps2,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
case Fragment:
|
|
return updateFragment(current, workInProgress, renderLanes);
|
|
|
|
case Mode:
|
|
return updateMode(current, workInProgress, renderLanes);
|
|
|
|
case Profiler:
|
|
return updateProfiler(current, workInProgress, renderLanes);
|
|
|
|
case ContextProvider:
|
|
return updateContextProvider(current, workInProgress, renderLanes);
|
|
|
|
case ContextConsumer:
|
|
return updateContextConsumer(current, workInProgress, renderLanes);
|
|
|
|
case MemoComponent: {
|
|
var _type2 = workInProgress.type;
|
|
var _unresolvedProps3 = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props.
|
|
|
|
var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3);
|
|
|
|
{
|
|
if (workInProgress.type !== workInProgress.elementType) {
|
|
var outerPropTypes = _type2.propTypes;
|
|
|
|
if (outerPropTypes) {
|
|
checkPropTypes(
|
|
outerPropTypes,
|
|
_resolvedProps3, // Resolved for outer only
|
|
"prop",
|
|
getComponentNameFromType(_type2)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
_resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3);
|
|
return updateMemoComponent(
|
|
current,
|
|
workInProgress,
|
|
_type2,
|
|
_resolvedProps3,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
case SimpleMemoComponent: {
|
|
return updateSimpleMemoComponent(
|
|
current,
|
|
workInProgress,
|
|
workInProgress.type,
|
|
workInProgress.pendingProps,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
case IncompleteClassComponent: {
|
|
var _Component2 = workInProgress.type;
|
|
var _unresolvedProps4 = workInProgress.pendingProps;
|
|
|
|
var _resolvedProps4 =
|
|
workInProgress.elementType === _Component2
|
|
? _unresolvedProps4
|
|
: resolveDefaultProps(_Component2, _unresolvedProps4);
|
|
|
|
return mountIncompleteClassComponent(
|
|
current,
|
|
workInProgress,
|
|
_Component2,
|
|
_resolvedProps4,
|
|
renderLanes
|
|
);
|
|
}
|
|
|
|
case SuspenseListComponent: {
|
|
return updateSuspenseListComponent(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
case ScopeComponent: {
|
|
{
|
|
return updateScopeComponent(current, workInProgress, renderLanes);
|
|
}
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
return updateOffscreenComponent(current, workInProgress, renderLanes);
|
|
}
|
|
|
|
case LegacyHiddenComponent: {
|
|
break;
|
|
}
|
|
|
|
case CacheComponent: {
|
|
{
|
|
return updateCacheComponent(current, workInProgress, renderLanes);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
"Unknown unit of work tag (" +
|
|
workInProgress.tag +
|
|
"). This error is likely caused by a bug in " +
|
|
"React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
var valueCursor = createCursor(null);
|
|
var rendererCursorDEV;
|
|
|
|
{
|
|
rendererCursorDEV = createCursor(null);
|
|
}
|
|
|
|
var rendererSigil$1;
|
|
|
|
{
|
|
// Use this to detect multiple renderers using the same context
|
|
rendererSigil$1 = {};
|
|
}
|
|
|
|
var currentlyRenderingFiber$1 = null;
|
|
var lastContextDependency = null;
|
|
var lastFullyObservedContext = null;
|
|
var isDisallowedContextReadInDEV = false;
|
|
function resetContextDependencies() {
|
|
// This is called right before React yields execution, to ensure `readContext`
|
|
// cannot be called outside the render phase.
|
|
currentlyRenderingFiber$1 = null;
|
|
lastContextDependency = null;
|
|
lastFullyObservedContext = null;
|
|
|
|
{
|
|
isDisallowedContextReadInDEV = false;
|
|
}
|
|
}
|
|
function enterDisallowedContextReadInDEV() {
|
|
{
|
|
isDisallowedContextReadInDEV = true;
|
|
}
|
|
}
|
|
function exitDisallowedContextReadInDEV() {
|
|
{
|
|
isDisallowedContextReadInDEV = false;
|
|
}
|
|
}
|
|
function pushProvider(providerFiber, context, nextValue) {
|
|
{
|
|
push(valueCursor, context._currentValue, providerFiber);
|
|
context._currentValue = nextValue;
|
|
|
|
{
|
|
push(rendererCursorDEV, context._currentRenderer, providerFiber);
|
|
|
|
if (
|
|
context._currentRenderer !== undefined &&
|
|
context._currentRenderer !== null &&
|
|
context._currentRenderer !== rendererSigil$1
|
|
) {
|
|
error(
|
|
"Detected multiple renderers concurrently rendering the " +
|
|
"same context provider. This is currently unsupported."
|
|
);
|
|
}
|
|
|
|
context._currentRenderer = rendererSigil$1;
|
|
}
|
|
}
|
|
}
|
|
function popProvider(context, providerFiber) {
|
|
var currentValue = valueCursor.current;
|
|
|
|
{
|
|
if (currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
|
|
context._currentValue = context._defaultValue;
|
|
} else {
|
|
context._currentValue = currentValue;
|
|
}
|
|
|
|
{
|
|
var currentRenderer = rendererCursorDEV.current;
|
|
pop(rendererCursorDEV, providerFiber);
|
|
context._currentRenderer = currentRenderer;
|
|
}
|
|
}
|
|
|
|
pop(valueCursor, providerFiber);
|
|
}
|
|
function scheduleContextWorkOnParentPath(parent, renderLanes, propagationRoot) {
|
|
// Update the child lanes of all the ancestors, including the alternates.
|
|
var node = parent;
|
|
|
|
while (node !== null) {
|
|
var alternate = node.alternate;
|
|
|
|
if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
|
|
node.childLanes = mergeLanes(node.childLanes, renderLanes);
|
|
|
|
if (alternate !== null) {
|
|
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
|
|
}
|
|
} else if (
|
|
alternate !== null &&
|
|
!isSubsetOfLanes(alternate.childLanes, renderLanes)
|
|
) {
|
|
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
|
|
}
|
|
|
|
if (node === propagationRoot) {
|
|
break;
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
|
|
{
|
|
if (node !== propagationRoot) {
|
|
error(
|
|
"Expected to find the propagation root when scheduling context work. " +
|
|
"This error is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
function propagateContextChange(workInProgress, context, renderLanes) {
|
|
{
|
|
propagateContextChange_eager(workInProgress, context, renderLanes);
|
|
}
|
|
}
|
|
|
|
function propagateContextChange_eager(workInProgress, context, renderLanes) {
|
|
var fiber = workInProgress.child;
|
|
|
|
if (fiber !== null) {
|
|
// Set the return pointer of the child to the work-in-progress fiber.
|
|
fiber.return = workInProgress;
|
|
}
|
|
|
|
while (fiber !== null) {
|
|
var nextFiber = void 0; // Visit this fiber.
|
|
|
|
var list = fiber.dependencies;
|
|
|
|
if (list !== null) {
|
|
nextFiber = fiber.child;
|
|
var dependency = list.firstContext;
|
|
|
|
while (dependency !== null) {
|
|
// Check if the context matches.
|
|
if (dependency.context === context) {
|
|
// Match! Schedule an update on this fiber.
|
|
if (fiber.tag === ClassComponent) {
|
|
// Schedule a force update on the work-in-progress.
|
|
var lane = pickArbitraryLane(renderLanes);
|
|
var update = createUpdate(NoTimestamp, lane);
|
|
update.tag = ForceUpdate; // TODO: Because we don't have a work-in-progress, this will add the
|
|
// update to the current fiber, too, which means it will persist even if
|
|
// this render is thrown away. Since it's a race condition, not sure it's
|
|
// worth fixing.
|
|
// Inlined `enqueueUpdate` to remove interleaved update check
|
|
|
|
var updateQueue = fiber.updateQueue;
|
|
|
|
if (updateQueue === null);
|
|
else {
|
|
var sharedQueue = updateQueue.shared;
|
|
var pending = sharedQueue.pending;
|
|
|
|
if (pending === null) {
|
|
// This is the first update. Create a circular list.
|
|
update.next = update;
|
|
} else {
|
|
update.next = pending.next;
|
|
pending.next = update;
|
|
}
|
|
|
|
sharedQueue.pending = update;
|
|
}
|
|
}
|
|
|
|
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
|
|
var alternate = fiber.alternate;
|
|
|
|
if (alternate !== null) {
|
|
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
|
|
}
|
|
|
|
scheduleContextWorkOnParentPath(
|
|
fiber.return,
|
|
renderLanes,
|
|
workInProgress
|
|
); // Mark the updated lanes on the list, too.
|
|
|
|
list.lanes = mergeLanes(list.lanes, renderLanes); // Since we already found a match, we can stop traversing the
|
|
// dependency list.
|
|
|
|
break;
|
|
}
|
|
|
|
dependency = dependency.next;
|
|
}
|
|
} else if (fiber.tag === ContextProvider) {
|
|
// Don't scan deeper if this is a matching provider
|
|
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
|
|
} else if (fiber.tag === DehydratedFragment) {
|
|
// If a dehydrated suspense boundary is in this subtree, we don't know
|
|
// if it will have any context consumers in it. The best we can do is
|
|
// mark it as having updates.
|
|
var parentSuspense = fiber.return;
|
|
|
|
if (parentSuspense === null) {
|
|
throw new Error(
|
|
"We just came from a parent so we must have had a parent. This is a bug in React."
|
|
);
|
|
}
|
|
|
|
parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
|
|
var _alternate = parentSuspense.alternate;
|
|
|
|
if (_alternate !== null) {
|
|
_alternate.lanes = mergeLanes(_alternate.lanes, renderLanes);
|
|
} // This is intentionally passing this fiber as the parent
|
|
// because we want to schedule this fiber as having work
|
|
// on its children. We'll use the childLanes on
|
|
// this fiber to indicate that a context has changed.
|
|
|
|
scheduleContextWorkOnParentPath(
|
|
parentSuspense,
|
|
renderLanes,
|
|
workInProgress
|
|
);
|
|
nextFiber = fiber.sibling;
|
|
} else {
|
|
// Traverse down.
|
|
nextFiber = fiber.child;
|
|
}
|
|
|
|
if (nextFiber !== null) {
|
|
// Set the return pointer of the child to the work-in-progress fiber.
|
|
nextFiber.return = fiber;
|
|
} else {
|
|
// No child. Traverse to next sibling.
|
|
nextFiber = fiber;
|
|
|
|
while (nextFiber !== null) {
|
|
if (nextFiber === workInProgress) {
|
|
// We're back to the root of this subtree. Exit.
|
|
nextFiber = null;
|
|
break;
|
|
}
|
|
|
|
var sibling = nextFiber.sibling;
|
|
|
|
if (sibling !== null) {
|
|
// Set the return pointer of the sibling to the work-in-progress fiber.
|
|
sibling.return = nextFiber.return;
|
|
nextFiber = sibling;
|
|
break;
|
|
} // No more siblings. Traverse up.
|
|
|
|
nextFiber = nextFiber.return;
|
|
}
|
|
}
|
|
|
|
fiber = nextFiber;
|
|
}
|
|
}
|
|
function prepareToReadContext(workInProgress, renderLanes) {
|
|
currentlyRenderingFiber$1 = workInProgress;
|
|
lastContextDependency = null;
|
|
lastFullyObservedContext = null;
|
|
var dependencies = workInProgress.dependencies;
|
|
|
|
if (dependencies !== null) {
|
|
{
|
|
var firstContext = dependencies.firstContext;
|
|
|
|
if (firstContext !== null) {
|
|
if (includesSomeLane(dependencies.lanes, renderLanes)) {
|
|
// Context list has a pending update. Mark that this fiber performed work.
|
|
markWorkInProgressReceivedUpdate();
|
|
} // Reset the work-in-progress list
|
|
|
|
dependencies.firstContext = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function readContext(context) {
|
|
{
|
|
// This warning would fire if you read context inside a Hook like useMemo.
|
|
// Unlike the class check below, it's not enforced in production for perf.
|
|
if (isDisallowedContextReadInDEV) {
|
|
error(
|
|
"Context can only be read while React is rendering. " +
|
|
"In classes, you can read it in the render method or getDerivedStateFromProps. " +
|
|
"In function components, you can read it directly in the function body, but not " +
|
|
"inside Hooks like useReducer() or useMemo()."
|
|
);
|
|
}
|
|
}
|
|
|
|
var value = context._currentValue;
|
|
|
|
if (lastFullyObservedContext === context);
|
|
else {
|
|
var contextItem = {
|
|
context: context,
|
|
memoizedValue: value,
|
|
next: null
|
|
};
|
|
|
|
if (lastContextDependency === null) {
|
|
if (currentlyRenderingFiber$1 === null) {
|
|
throw new Error(
|
|
"Context can only be read while React is rendering. " +
|
|
"In classes, you can read it in the render method or getDerivedStateFromProps. " +
|
|
"In function components, you can read it directly in the function body, but not " +
|
|
"inside Hooks like useReducer() or useMemo()."
|
|
);
|
|
} // This is the first dependency for this component. Create a new list.
|
|
|
|
lastContextDependency = contextItem;
|
|
currentlyRenderingFiber$1.dependencies = {
|
|
lanes: NoLanes,
|
|
firstContext: contextItem
|
|
};
|
|
} else {
|
|
// Append a new context item.
|
|
lastContextDependency = lastContextDependency.next = contextItem;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// replace it with a lightweight shim that only has the features we use.
|
|
|
|
var AbortControllerLocal =
|
|
typeof AbortController !== "undefined"
|
|
? AbortController // $FlowFixMe[missing-this-annot]
|
|
: function AbortControllerShim() {
|
|
var listeners = [];
|
|
var signal = (this.signal = {
|
|
aborted: false,
|
|
addEventListener: function(type, listener) {
|
|
listeners.push(listener);
|
|
}
|
|
});
|
|
|
|
this.abort = function() {
|
|
signal.aborted = true;
|
|
listeners.forEach(function(listener) {
|
|
return listener();
|
|
});
|
|
};
|
|
}; // Intentionally not named imports because Rollup would
|
|
// use dynamic dispatch for CommonJS interop named imports.
|
|
|
|
var scheduleCallback$1 = Scheduler.unstable_scheduleCallback,
|
|
NormalPriority$1 = Scheduler.unstable_NormalPriority;
|
|
var CacheContext = {
|
|
$$typeof: REACT_CONTEXT_TYPE,
|
|
// We don't use Consumer/Provider for Cache components. So we'll cheat.
|
|
Consumer: null,
|
|
Provider: null,
|
|
// We'll initialize these at the root.
|
|
_currentValue: null,
|
|
_currentValue2: null,
|
|
_threadCount: 0,
|
|
_defaultValue: null,
|
|
_globalName: null
|
|
};
|
|
|
|
{
|
|
CacheContext._currentRenderer = null;
|
|
CacheContext._currentRenderer2 = null;
|
|
} // Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
|
|
// for retaining the cache once it is in use (retainCache), and releasing the cache
|
|
// once it is no longer needed (releaseCache).
|
|
|
|
function createCache() {
|
|
var cache = {
|
|
controller: new AbortControllerLocal(),
|
|
data: new Map(),
|
|
refCount: 0
|
|
};
|
|
return cache;
|
|
}
|
|
function retainCache(cache) {
|
|
{
|
|
if (cache.controller.signal.aborted) {
|
|
warn(
|
|
"A cache instance was retained after it was already freed. " +
|
|
"This likely indicates a bug in React."
|
|
);
|
|
}
|
|
}
|
|
|
|
cache.refCount++;
|
|
} // Cleanup a cache instance, potentially freeing it if there are no more references
|
|
|
|
function releaseCache(cache) {
|
|
cache.refCount--;
|
|
|
|
{
|
|
if (cache.refCount < 0) {
|
|
warn(
|
|
"A cache instance was released after it was already freed. " +
|
|
"This likely indicates a bug in React."
|
|
);
|
|
}
|
|
}
|
|
|
|
if (cache.refCount === 0) {
|
|
scheduleCallback$1(NormalPriority$1, function() {
|
|
cache.controller.abort();
|
|
});
|
|
}
|
|
}
|
|
function pushCacheProvider(workInProgress, cache) {
|
|
pushProvider(workInProgress, CacheContext, cache);
|
|
}
|
|
function popCacheProvider(workInProgress, cache) {
|
|
popProvider(CacheContext, workInProgress);
|
|
}
|
|
|
|
var ReactCurrentBatchConfig$2 = ReactSharedInternals.ReactCurrentBatchConfig;
|
|
var NoTransition = null;
|
|
function requestCurrentTransition() {
|
|
return ReactCurrentBatchConfig$2.transition;
|
|
} // When retrying a Suspense/Offscreen boundary, we restore the cache that was
|
|
// used during the previous render by placing it here, on the stack.
|
|
|
|
var resumedCache = createCursor(null); // During the render/synchronous commit phase, we don't actually process the
|
|
|
|
function peekCacheFromPool() {
|
|
// If we're rendering inside a Suspense boundary that is currently hidden,
|
|
// we should use the same cache that we used during the previous render, if
|
|
// one exists.
|
|
|
|
var cacheResumedFromPreviousRender = resumedCache.current;
|
|
|
|
if (cacheResumedFromPreviousRender !== null) {
|
|
return cacheResumedFromPreviousRender;
|
|
} // Otherwise, check the root's cache pool.
|
|
|
|
var root = getWorkInProgressRoot();
|
|
var cacheFromRootCachePool = root.pooledCache;
|
|
return cacheFromRootCachePool;
|
|
}
|
|
|
|
function requestCacheFromPool(renderLanes) {
|
|
// Similar to previous function, except if there's not already a cache in the
|
|
// pool, we allocate a new one.
|
|
var cacheFromPool = peekCacheFromPool();
|
|
|
|
if (cacheFromPool !== null) {
|
|
return cacheFromPool;
|
|
} // Create a fresh cache and add it to the root cache pool. A cache can have
|
|
// multiple owners:
|
|
// - A cache pool that lives on the FiberRoot. This is where all fresh caches
|
|
// are originally created (TODO: except during refreshes, until we implement
|
|
// this correctly). The root takes ownership immediately when the cache is
|
|
// created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
|
|
// and the return value of this function is a &Arc<Cache> (borrowed).
|
|
// - One of several fiber types: host root, cache boundary, suspense
|
|
// component. These retain and release in the commit phase.
|
|
|
|
var root = getWorkInProgressRoot();
|
|
var freshCache = createCache();
|
|
root.pooledCache = freshCache;
|
|
retainCache(freshCache);
|
|
|
|
if (freshCache !== null) {
|
|
root.pooledCacheLanes |= renderLanes;
|
|
}
|
|
|
|
return freshCache;
|
|
}
|
|
function pushTransition(
|
|
offscreenWorkInProgress,
|
|
prevCachePool,
|
|
newTransitions
|
|
) {
|
|
{
|
|
if (prevCachePool === null) {
|
|
push(resumedCache, resumedCache.current, offscreenWorkInProgress);
|
|
} else {
|
|
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
|
|
}
|
|
}
|
|
}
|
|
function popTransition(workInProgress, current) {
|
|
if (current !== null) {
|
|
{
|
|
pop(resumedCache, workInProgress);
|
|
}
|
|
}
|
|
}
|
|
function getSuspendedCache() {
|
|
// cache that would have been used to render fresh data during this render,
|
|
// if there was any, so that we can resume rendering with the same cache when
|
|
// we receive more data.
|
|
|
|
var cacheFromPool = peekCacheFromPool();
|
|
|
|
if (cacheFromPool === null) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
// We must also save the parent, so that when we resume we can detect
|
|
// a refresh.
|
|
parent: CacheContext._currentValue,
|
|
pool: cacheFromPool
|
|
};
|
|
}
|
|
function getOffscreenDeferredCache() {
|
|
var cacheFromPool = peekCacheFromPool();
|
|
|
|
if (cacheFromPool === null) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
// We must also store the parent, so that when we resume we can detect
|
|
// a refresh.
|
|
parent: CacheContext._currentValue,
|
|
pool: cacheFromPool
|
|
};
|
|
}
|
|
|
|
function getSuspenseFallbackChild(fiber) {
|
|
return fiber.child.sibling.child;
|
|
}
|
|
|
|
var emptyObject = {};
|
|
|
|
function collectScopedNodes(node, fn, scopedNodes) {
|
|
{
|
|
if (node.tag === HostComponent) {
|
|
var type = node.type,
|
|
memoizedProps = node.memoizedProps,
|
|
stateNode = node.stateNode;
|
|
var instance = getPublicInstance(stateNode);
|
|
|
|
if (
|
|
instance !== null &&
|
|
fn(type, memoizedProps || emptyObject, instance) === true
|
|
) {
|
|
scopedNodes.push(instance);
|
|
}
|
|
}
|
|
|
|
var child = node.child;
|
|
|
|
if (isFiberSuspenseAndTimedOut(node)) {
|
|
child = getSuspenseFallbackChild(node);
|
|
}
|
|
|
|
if (child !== null) {
|
|
collectScopedNodesFromChildren(child, fn, scopedNodes);
|
|
}
|
|
}
|
|
}
|
|
|
|
function collectFirstScopedNode(node, fn) {
|
|
{
|
|
if (node.tag === HostComponent) {
|
|
var type = node.type,
|
|
memoizedProps = node.memoizedProps,
|
|
stateNode = node.stateNode;
|
|
var instance = getPublicInstance(stateNode);
|
|
|
|
if (instance !== null && fn(type, memoizedProps, instance) === true) {
|
|
return instance;
|
|
}
|
|
}
|
|
|
|
var child = node.child;
|
|
|
|
if (isFiberSuspenseAndTimedOut(node)) {
|
|
child = getSuspenseFallbackChild(node);
|
|
}
|
|
|
|
if (child !== null) {
|
|
return collectFirstScopedNodeFromChildren(child, fn);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function collectScopedNodesFromChildren(startingChild, fn, scopedNodes) {
|
|
var child = startingChild;
|
|
|
|
while (child !== null) {
|
|
collectScopedNodes(child, fn, scopedNodes);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
function collectFirstScopedNodeFromChildren(startingChild, fn) {
|
|
var child = startingChild;
|
|
|
|
while (child !== null) {
|
|
var scopedNode = collectFirstScopedNode(child, fn);
|
|
|
|
if (scopedNode !== null) {
|
|
return scopedNode;
|
|
}
|
|
|
|
child = child.sibling;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function collectNearestContextValues(node, context, childContextValues) {
|
|
if (node.tag === ContextProvider && node.type._context === context) {
|
|
var contextValue = node.memoizedProps.value;
|
|
childContextValues.push(contextValue);
|
|
} else {
|
|
var child = node.child;
|
|
|
|
if (isFiberSuspenseAndTimedOut(node)) {
|
|
child = getSuspenseFallbackChild(node);
|
|
}
|
|
|
|
if (child !== null) {
|
|
collectNearestChildContextValues(child, context, childContextValues);
|
|
}
|
|
}
|
|
}
|
|
|
|
function collectNearestChildContextValues(
|
|
startingChild,
|
|
context,
|
|
childContextValues
|
|
) {
|
|
var child = startingChild;
|
|
|
|
while (child !== null) {
|
|
collectNearestContextValues(child, context, childContextValues);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
function DO_NOT_USE_queryAllNodes(fn) {
|
|
var currentFiber = getInstanceFromScope(this);
|
|
|
|
if (currentFiber === null) {
|
|
return null;
|
|
}
|
|
|
|
var child = currentFiber.child;
|
|
var scopedNodes = [];
|
|
|
|
if (child !== null) {
|
|
collectScopedNodesFromChildren(child, fn, scopedNodes);
|
|
}
|
|
|
|
return scopedNodes.length === 0 ? null : scopedNodes;
|
|
}
|
|
|
|
function DO_NOT_USE_queryFirstNode(fn) {
|
|
var currentFiber = getInstanceFromScope(this);
|
|
|
|
if (currentFiber === null) {
|
|
return null;
|
|
}
|
|
|
|
var child = currentFiber.child;
|
|
|
|
if (child !== null) {
|
|
return collectFirstScopedNodeFromChildren(child, fn);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function containsNode$1(node) {
|
|
var fiber = getInstanceFromNode(node);
|
|
|
|
while (fiber !== null) {
|
|
if (fiber.tag === ScopeComponent && fiber.stateNode === this) {
|
|
return true;
|
|
}
|
|
|
|
fiber = fiber.return;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getChildContextValues(context) {
|
|
var currentFiber = getInstanceFromScope(this);
|
|
|
|
if (currentFiber === null) {
|
|
return [];
|
|
}
|
|
|
|
var child = currentFiber.child;
|
|
var childContextValues = [];
|
|
|
|
if (child !== null) {
|
|
collectNearestChildContextValues(child, context, childContextValues);
|
|
}
|
|
|
|
return childContextValues;
|
|
}
|
|
|
|
function createScopeInstance() {
|
|
return {
|
|
DO_NOT_USE_queryAllNodes: DO_NOT_USE_queryAllNodes,
|
|
DO_NOT_USE_queryFirstNode: DO_NOT_USE_queryFirstNode,
|
|
containsNode: containsNode$1,
|
|
getChildContextValues: getChildContextValues
|
|
};
|
|
}
|
|
|
|
function markUpdate(workInProgress) {
|
|
// Tag the fiber with an update effect. This turns a Placement into
|
|
// a PlacementAndUpdate.
|
|
workInProgress.flags |= Update;
|
|
}
|
|
|
|
function markRef$1(workInProgress) {
|
|
workInProgress.flags |= Ref | RefStatic;
|
|
}
|
|
|
|
var appendAllChildren;
|
|
var updateHostContainer;
|
|
var updateHostComponent$1;
|
|
var updateHostText$1;
|
|
|
|
{
|
|
// Mutation mode
|
|
appendAllChildren = function(
|
|
parent,
|
|
workInProgress,
|
|
needsVisibilityToggle,
|
|
isHidden
|
|
) {
|
|
// We only have the top Fiber that was created but we need recurse down its
|
|
// children to find all the terminal nodes.
|
|
var node = workInProgress.child;
|
|
|
|
while (node !== null) {
|
|
if (node.tag === HostComponent || node.tag === HostText) {
|
|
appendInitialChild(parent, node.stateNode);
|
|
} else if (node.tag === HostPortal || node.tag === HostSingleton);
|
|
else if (node.child !== null) {
|
|
node.child.return = node;
|
|
node = node.child;
|
|
continue;
|
|
}
|
|
|
|
if (node === workInProgress) {
|
|
return;
|
|
} // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
while (node.sibling === null) {
|
|
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
if (node.return === null || node.return === workInProgress) {
|
|
return;
|
|
}
|
|
|
|
node = node.return;
|
|
} // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
node.sibling.return = node.return;
|
|
node = node.sibling;
|
|
}
|
|
};
|
|
|
|
updateHostContainer = function(current, workInProgress) {
|
|
// Noop
|
|
};
|
|
|
|
updateHostComponent$1 = function(current, workInProgress, type, newProps) {
|
|
// If we have an alternate, that means this is an update and we need to
|
|
// schedule a side-effect to do the updates.
|
|
var oldProps = current.memoizedProps;
|
|
|
|
if (oldProps === newProps) {
|
|
// In mutation mode, this is sufficient for a bailout because
|
|
// we won't touch this node even if children changed.
|
|
return;
|
|
} // If we get updated because one of our children updated, we don't
|
|
// have newProps so we'll have to reuse them.
|
|
// TODO: Split the update API as separate for the props vs. children.
|
|
// Even better would be if children weren't special cased at all tho.
|
|
|
|
var instance = workInProgress.stateNode;
|
|
var currentHostContext = getHostContext(); // TODO: Experiencing an error where oldProps is null. Suggests a host
|
|
// component is hitting the resume path. Figure out why. Possibly
|
|
// related to `hidden`.
|
|
|
|
var updatePayload = prepareUpdate(
|
|
instance,
|
|
type,
|
|
oldProps,
|
|
newProps,
|
|
currentHostContext
|
|
); // TODO: Type this specific to this type of component.
|
|
|
|
workInProgress.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there
|
|
// is a new ref we mark this as an update. All the work is done in commitWork.
|
|
|
|
if (updatePayload) {
|
|
markUpdate(workInProgress);
|
|
}
|
|
};
|
|
|
|
updateHostText$1 = function(current, workInProgress, oldText, newText) {
|
|
// If the text differs, mark it as an update. All the work in done in commitWork.
|
|
if (oldText !== newText) {
|
|
markUpdate(workInProgress);
|
|
}
|
|
};
|
|
}
|
|
|
|
function cutOffTailIfNeeded(renderState, hasRenderedATailFallback) {
|
|
if (getIsHydrating()) {
|
|
// If we're hydrating, we should consume as many items as we can
|
|
// so we don't leave any behind.
|
|
return;
|
|
}
|
|
|
|
switch (renderState.tailMode) {
|
|
case "hidden": {
|
|
// Any insertions at the end of the tail list after this point
|
|
// should be invisible. If there are already mounted boundaries
|
|
// anything before them are not considered for collapsing.
|
|
// Therefore we need to go through the whole tail to find if
|
|
// there are any.
|
|
var tailNode = renderState.tail;
|
|
var lastTailNode = null;
|
|
|
|
while (tailNode !== null) {
|
|
if (tailNode.alternate !== null) {
|
|
lastTailNode = tailNode;
|
|
}
|
|
|
|
tailNode = tailNode.sibling;
|
|
} // Next we're simply going to delete all insertions after the
|
|
// last rendered item.
|
|
|
|
if (lastTailNode === null) {
|
|
// All remaining items in the tail are insertions.
|
|
renderState.tail = null;
|
|
} else {
|
|
// Detach the insertion after the last node that was already
|
|
// inserted.
|
|
lastTailNode.sibling = null;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case "collapsed": {
|
|
// Any insertions at the end of the tail list after this point
|
|
// should be invisible. If there are already mounted boundaries
|
|
// anything before them are not considered for collapsing.
|
|
// Therefore we need to go through the whole tail to find if
|
|
// there are any.
|
|
var _tailNode = renderState.tail;
|
|
var _lastTailNode = null;
|
|
|
|
while (_tailNode !== null) {
|
|
if (_tailNode.alternate !== null) {
|
|
_lastTailNode = _tailNode;
|
|
}
|
|
|
|
_tailNode = _tailNode.sibling;
|
|
} // Next we're simply going to delete all insertions after the
|
|
// last rendered item.
|
|
|
|
if (_lastTailNode === null) {
|
|
// All remaining items in the tail are insertions.
|
|
if (!hasRenderedATailFallback && renderState.tail !== null) {
|
|
// We suspended during the head. We want to show at least one
|
|
// row at the tail. So we'll keep on and cut off the rest.
|
|
renderState.tail.sibling = null;
|
|
} else {
|
|
renderState.tail = null;
|
|
}
|
|
} else {
|
|
// Detach the insertion after the last node that was already
|
|
// inserted.
|
|
_lastTailNode.sibling = null;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function bubbleProperties(completedWork) {
|
|
var didBailout =
|
|
completedWork.alternate !== null &&
|
|
completedWork.alternate.child === completedWork.child;
|
|
var newChildLanes = NoLanes;
|
|
var subtreeFlags = NoFlags;
|
|
|
|
if (!didBailout) {
|
|
// Bubble up the earliest expiration time.
|
|
{
|
|
var _child = completedWork.child;
|
|
|
|
while (_child !== null) {
|
|
newChildLanes = mergeLanes(
|
|
newChildLanes,
|
|
mergeLanes(_child.lanes, _child.childLanes)
|
|
);
|
|
subtreeFlags |= _child.subtreeFlags;
|
|
subtreeFlags |= _child.flags; // Update the return pointer so the tree is consistent. This is a code
|
|
// smell because it assumes the commit phase is never concurrent with
|
|
// the render phase. Will address during refactor to alternate model.
|
|
|
|
_child.return = completedWork;
|
|
_child = _child.sibling;
|
|
}
|
|
}
|
|
|
|
completedWork.subtreeFlags |= subtreeFlags;
|
|
} else {
|
|
// Bubble up the earliest expiration time.
|
|
{
|
|
var _child3 = completedWork.child;
|
|
|
|
while (_child3 !== null) {
|
|
newChildLanes = mergeLanes(
|
|
newChildLanes,
|
|
mergeLanes(_child3.lanes, _child3.childLanes)
|
|
); // "Static" flags share the lifetime of the fiber/hook they belong to,
|
|
// so we should bubble those up even during a bailout. All the other
|
|
// flags have a lifetime only of a single render + commit, so we should
|
|
// ignore them.
|
|
|
|
subtreeFlags |= _child3.subtreeFlags & StaticMask;
|
|
subtreeFlags |= _child3.flags & StaticMask; // Update the return pointer so the tree is consistent. This is a code
|
|
// smell because it assumes the commit phase is never concurrent with
|
|
// the render phase. Will address during refactor to alternate model.
|
|
|
|
_child3.return = completedWork;
|
|
_child3 = _child3.sibling;
|
|
}
|
|
}
|
|
|
|
completedWork.subtreeFlags |= subtreeFlags;
|
|
}
|
|
|
|
completedWork.childLanes = newChildLanes;
|
|
return didBailout;
|
|
}
|
|
|
|
function completeDehydratedSuspenseBoundary(
|
|
current,
|
|
workInProgress,
|
|
nextState
|
|
) {
|
|
if (
|
|
hasUnhydratedTailNodes() &&
|
|
(workInProgress.mode & ConcurrentMode) !== NoMode &&
|
|
(workInProgress.flags & DidCapture) === NoFlags
|
|
) {
|
|
warnIfUnhydratedTailNodes(workInProgress);
|
|
resetHydrationState();
|
|
workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture;
|
|
return false;
|
|
}
|
|
|
|
var wasHydrated = popHydrationState(workInProgress);
|
|
|
|
if (nextState !== null && nextState.dehydrated !== null) {
|
|
// We might be inside a hydration state the first time we're picking up this
|
|
// Suspense boundary, and also after we've reentered it for further hydration.
|
|
if (current === null) {
|
|
if (!wasHydrated) {
|
|
throw new Error(
|
|
"A dehydrated suspense component was completed without a hydrated node. " +
|
|
"This is probably a bug in React."
|
|
);
|
|
}
|
|
|
|
prepareToHydrateHostSuspenseInstance(workInProgress);
|
|
bubbleProperties(workInProgress);
|
|
|
|
return false;
|
|
} else {
|
|
// We might have reentered this boundary to hydrate it. If so, we need to reset the hydration
|
|
// state since we're now exiting out of it. popHydrationState doesn't do that for us.
|
|
resetHydrationState();
|
|
|
|
if ((workInProgress.flags & DidCapture) === NoFlags) {
|
|
// This boundary did not suspend so it's now hydrated and unsuspended.
|
|
workInProgress.memoizedState = null;
|
|
} // If nothing suspended, we need to schedule an effect to mark this boundary
|
|
// as having hydrated so events know that they're free to be invoked.
|
|
// It's also a signal to replay events and the suspense callback.
|
|
// If something suspended, schedule an effect to attach retry listeners.
|
|
// So we might as well always mark this.
|
|
|
|
workInProgress.flags |= Update;
|
|
bubbleProperties(workInProgress);
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
// Successfully completed this tree. If this was a forced client render,
|
|
// there may have been recoverable errors during first hydration
|
|
// attempt. If so, add them to a queue so we can log them in the
|
|
// commit phase.
|
|
upgradeHydrationErrorsToRecoverable(); // Fall through to normal Suspense path
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function completeWork(current, workInProgress, renderLanes) {
|
|
var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing
|
|
// to the current tree provider fiber is just as fast and less error-prone.
|
|
// Ideally we would have a special version of the work loop only
|
|
// for hydration.
|
|
|
|
popTreeContext(workInProgress);
|
|
|
|
switch (workInProgress.tag) {
|
|
case IndeterminateComponent:
|
|
case LazyComponent:
|
|
case SimpleMemoComponent:
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case Fragment:
|
|
case Mode:
|
|
case Profiler:
|
|
case ContextConsumer:
|
|
case MemoComponent:
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
|
|
case ClassComponent: {
|
|
var Component = workInProgress.type;
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
case HostRoot: {
|
|
var fiberRoot = workInProgress.stateNode;
|
|
|
|
{
|
|
var previousCache = null;
|
|
|
|
if (current !== null) {
|
|
previousCache = current.memoizedState.cache;
|
|
}
|
|
|
|
var cache = workInProgress.memoizedState.cache;
|
|
|
|
if (cache !== previousCache) {
|
|
// Run passive effects to retain/release the cache.
|
|
workInProgress.flags |= Passive;
|
|
}
|
|
|
|
popCacheProvider(workInProgress);
|
|
}
|
|
popHostContainer(workInProgress);
|
|
resetWorkInProgressVersions();
|
|
|
|
if (fiberRoot.pendingContext) {
|
|
fiberRoot.context = fiberRoot.pendingContext;
|
|
fiberRoot.pendingContext = null;
|
|
}
|
|
|
|
if (current === null || current.child === null) {
|
|
// If we hydrated, pop so that we can delete any remaining children
|
|
// that weren't hydrated.
|
|
var wasHydrated = popHydrationState(workInProgress);
|
|
|
|
if (wasHydrated) {
|
|
// If we hydrated, then we'll need to schedule an update for
|
|
// the commit side-effects on the root.
|
|
markUpdate(workInProgress);
|
|
} else {
|
|
if (current !== null) {
|
|
var prevState = current.memoizedState;
|
|
|
|
if (
|
|
// Check if this is a client root
|
|
!prevState.isDehydrated || // Check if we reverted to client rendering (e.g. due to an error)
|
|
(workInProgress.flags & ForceClientRender) !== NoFlags
|
|
) {
|
|
// Schedule an effect to clear this container at the start of the
|
|
// next commit. This handles the case of React rendering into a
|
|
// container with previous children. It's also safe to do for
|
|
// updates too, because current.child would only be null if the
|
|
// previous render was null (so the container would already
|
|
// be empty).
|
|
workInProgress.flags |= Snapshot; // If this was a forced client render, there may have been
|
|
// recoverable errors during first hydration attempt. If so, add
|
|
// them to a queue so we can log them in the commit phase.
|
|
|
|
upgradeHydrationErrorsToRecoverable();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
updateHostContainer(current, workInProgress);
|
|
bubbleProperties(workInProgress);
|
|
|
|
return null;
|
|
}
|
|
|
|
case HostResource: {
|
|
{
|
|
popHostContext(workInProgress);
|
|
var currentRef = current ? current.ref : null;
|
|
|
|
if (currentRef !== workInProgress.ref) {
|
|
markRef$1(workInProgress);
|
|
}
|
|
|
|
if (
|
|
current === null ||
|
|
current.memoizedState !== workInProgress.memoizedState
|
|
) {
|
|
// The workInProgress resource is different than the current one or the current
|
|
// one does not exist
|
|
markUpdate(workInProgress);
|
|
}
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case HostSingleton: {
|
|
{
|
|
popHostContext(workInProgress);
|
|
var rootContainerInstance = getRootHostContainer();
|
|
var type = workInProgress.type;
|
|
|
|
if (current !== null && workInProgress.stateNode != null) {
|
|
updateHostComponent$1(current, workInProgress, type, newProps);
|
|
|
|
if (current.ref !== workInProgress.ref) {
|
|
markRef$1(workInProgress);
|
|
}
|
|
} else {
|
|
if (!newProps) {
|
|
if (workInProgress.stateNode === null) {
|
|
throw new Error(
|
|
"We must have new props for new mounts. This error is likely " +
|
|
"caused by a bug in React. Please file an issue."
|
|
);
|
|
} // This can happen when we abort work.
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
var currentHostContext = getHostContext();
|
|
|
|
var _wasHydrated = popHydrationState(workInProgress);
|
|
|
|
if (_wasHydrated) {
|
|
// We ignore the boolean indicating there is an updateQueue because
|
|
// it is used only to set text children and HostSingletons do not
|
|
// use them.
|
|
prepareToHydrateHostInstance(workInProgress, currentHostContext);
|
|
} else {
|
|
workInProgress.stateNode = resolveSingletonInstance(
|
|
type,
|
|
newProps,
|
|
rootContainerInstance,
|
|
currentHostContext,
|
|
true
|
|
);
|
|
markUpdate(workInProgress);
|
|
}
|
|
|
|
if (workInProgress.ref !== null) {
|
|
// If there is a ref on a host node we need to schedule a callback
|
|
markRef$1(workInProgress);
|
|
}
|
|
}
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case HostComponent: {
|
|
popHostContext(workInProgress);
|
|
var _type = workInProgress.type;
|
|
|
|
if (current !== null && workInProgress.stateNode != null) {
|
|
updateHostComponent$1(current, workInProgress, _type, newProps);
|
|
|
|
if (current.ref !== workInProgress.ref) {
|
|
markRef$1(workInProgress);
|
|
}
|
|
} else {
|
|
if (!newProps) {
|
|
if (workInProgress.stateNode === null) {
|
|
throw new Error(
|
|
"We must have new props for new mounts. This error is likely " +
|
|
"caused by a bug in React. Please file an issue."
|
|
);
|
|
} // This can happen when we abort work.
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
var _currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
|
|
// "stack" as the parent. Then append children as we go in beginWork
|
|
// or completeWork depending on whether we want to add them top->down or
|
|
// bottom->up. Top->down is faster in IE11.
|
|
|
|
var _wasHydrated2 = popHydrationState(workInProgress);
|
|
|
|
if (_wasHydrated2) {
|
|
// TODO: Move this and createInstance step into the beginPhase
|
|
// to consolidate.
|
|
if (
|
|
prepareToHydrateHostInstance(workInProgress, _currentHostContext)
|
|
) {
|
|
// If changes to the hydrated node need to be applied at the
|
|
// commit-phase we mark this as such.
|
|
markUpdate(workInProgress);
|
|
}
|
|
} else {
|
|
var _rootContainerInstance = getRootHostContainer();
|
|
|
|
var instance = createInstance(
|
|
_type,
|
|
newProps,
|
|
_rootContainerInstance,
|
|
_currentHostContext,
|
|
workInProgress
|
|
);
|
|
appendAllChildren(instance, workInProgress, false, false);
|
|
workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
|
|
// (eg DOM renderer supports auto-focus for certain elements).
|
|
// Make sure such renderers get scheduled for later work.
|
|
|
|
if (finalizeInitialChildren(instance, _type, newProps)) {
|
|
markUpdate(workInProgress);
|
|
}
|
|
}
|
|
|
|
if (workInProgress.ref !== null) {
|
|
// If there is a ref on a host node we need to schedule a callback
|
|
markRef$1(workInProgress);
|
|
}
|
|
}
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
case HostText: {
|
|
var newText = newProps;
|
|
|
|
if (current && workInProgress.stateNode != null) {
|
|
var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need
|
|
// to schedule a side-effect to do the updates.
|
|
|
|
updateHostText$1(current, workInProgress, oldText, newText);
|
|
} else {
|
|
if (typeof newText !== "string") {
|
|
if (workInProgress.stateNode === null) {
|
|
throw new Error(
|
|
"We must have new props for new mounts. This error is likely " +
|
|
"caused by a bug in React. Please file an issue."
|
|
);
|
|
} // This can happen when we abort work.
|
|
}
|
|
|
|
var _rootContainerInstance2 = getRootHostContainer();
|
|
|
|
var _currentHostContext2 = getHostContext();
|
|
|
|
var _wasHydrated3 = popHydrationState(workInProgress);
|
|
|
|
if (_wasHydrated3) {
|
|
if (prepareToHydrateHostTextInstance(workInProgress)) {
|
|
markUpdate(workInProgress);
|
|
}
|
|
} else {
|
|
workInProgress.stateNode = createTextInstance(
|
|
newText,
|
|
_rootContainerInstance2,
|
|
_currentHostContext2,
|
|
workInProgress
|
|
);
|
|
}
|
|
}
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
popSuspenseHandler(workInProgress);
|
|
var nextState = workInProgress.memoizedState; // Special path for dehydrated boundaries. We may eventually move this
|
|
// to its own fiber type so that we can add other kinds of hydration
|
|
// boundaries that aren't associated with a Suspense tree. In anticipation
|
|
// of such a refactor, all the hydration logic is contained in
|
|
// this branch.
|
|
|
|
if (
|
|
current === null ||
|
|
(current.memoizedState !== null &&
|
|
current.memoizedState.dehydrated !== null)
|
|
) {
|
|
var fallthroughToNormalSuspensePath = completeDehydratedSuspenseBoundary(
|
|
current,
|
|
workInProgress,
|
|
nextState
|
|
);
|
|
|
|
if (!fallthroughToNormalSuspensePath) {
|
|
if (workInProgress.flags & ShouldCapture) {
|
|
// Special case. There were remaining unhydrated nodes. We treat
|
|
// this as a mismatch. Revert to client rendering.
|
|
return workInProgress;
|
|
} else {
|
|
// Did not finish hydrating, either because this is the initial
|
|
// render or because something suspended.
|
|
return null;
|
|
}
|
|
} // Continue with the normal Suspense path.
|
|
}
|
|
|
|
if ((workInProgress.flags & DidCapture) !== NoFlags) {
|
|
// Something suspended. Re-render with the fallback children.
|
|
workInProgress.lanes = renderLanes; // Do not reset the effect list.
|
|
|
|
return workInProgress;
|
|
}
|
|
|
|
var nextDidTimeout = nextState !== null;
|
|
var prevDidTimeout = current !== null && current.memoizedState !== null;
|
|
|
|
if (nextDidTimeout) {
|
|
var offscreenFiber = workInProgress.child;
|
|
var _previousCache = null;
|
|
|
|
if (
|
|
offscreenFiber.alternate !== null &&
|
|
offscreenFiber.alternate.memoizedState !== null &&
|
|
offscreenFiber.alternate.memoizedState.cachePool !== null
|
|
) {
|
|
_previousCache =
|
|
offscreenFiber.alternate.memoizedState.cachePool.pool;
|
|
}
|
|
|
|
var _cache = null;
|
|
|
|
if (
|
|
offscreenFiber.memoizedState !== null &&
|
|
offscreenFiber.memoizedState.cachePool !== null
|
|
) {
|
|
_cache = offscreenFiber.memoizedState.cachePool.pool;
|
|
}
|
|
|
|
if (_cache !== _previousCache) {
|
|
// Run passive effects to retain/release the cache.
|
|
offscreenFiber.flags |= Passive;
|
|
}
|
|
} // If the suspended state of the boundary changes, we need to schedule
|
|
// a passive effect, which is when we process the transitions
|
|
|
|
if (nextDidTimeout !== prevDidTimeout) {
|
|
// an effect to toggle the subtree's visibility. When we switch from
|
|
// fallback -> primary, the inner Offscreen fiber schedules this effect
|
|
// as part of its normal complete phase. But when we switch from
|
|
// primary -> fallback, the inner Offscreen fiber does not have a complete
|
|
// phase. So we need to schedule its effect here.
|
|
//
|
|
// We also use this flag to connect/disconnect the effects, but the same
|
|
// logic applies: when re-connecting, the Offscreen fiber's complete
|
|
// phase will handle scheduling the effect. It's only when the fallback
|
|
// is active that we have to do anything special.
|
|
|
|
if (nextDidTimeout) {
|
|
var _offscreenFiber2 = workInProgress.child;
|
|
_offscreenFiber2.flags |= Visibility;
|
|
}
|
|
}
|
|
|
|
var wakeables = workInProgress.updateQueue;
|
|
|
|
if (wakeables !== null) {
|
|
// Schedule an effect to attach a retry listener to the promise.
|
|
// TODO: Move to passive phase
|
|
workInProgress.flags |= Update;
|
|
}
|
|
|
|
if (
|
|
workInProgress.updateQueue !== null &&
|
|
workInProgress.memoizedProps.suspenseCallback != null
|
|
) {
|
|
// Always notify the callback
|
|
// TODO: Move to passive phase
|
|
workInProgress.flags |= Update;
|
|
}
|
|
|
|
bubbleProperties(workInProgress);
|
|
|
|
return null;
|
|
}
|
|
|
|
case HostPortal:
|
|
popHostContainer(workInProgress);
|
|
updateHostContainer(current, workInProgress);
|
|
|
|
if (current === null) {
|
|
preparePortalMount(workInProgress.stateNode.containerInfo);
|
|
}
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
|
|
case ContextProvider:
|
|
// Pop provider fiber
|
|
var context = workInProgress.type._context;
|
|
popProvider(context, workInProgress);
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
|
|
case IncompleteClassComponent: {
|
|
// Same as class component case. I put it down here so that the tags are
|
|
// sequential to ensure this switch is compiled to a jump table.
|
|
var _Component = workInProgress.type;
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
case SuspenseListComponent: {
|
|
popSuspenseListContext(workInProgress);
|
|
var renderState = workInProgress.memoizedState;
|
|
|
|
if (renderState === null) {
|
|
// We're running in the default, "independent" mode.
|
|
// We don't do anything in this mode.
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
var didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;
|
|
var renderedTail = renderState.rendering;
|
|
|
|
if (renderedTail === null) {
|
|
// We just rendered the head.
|
|
if (!didSuspendAlready) {
|
|
// This is the first pass. We need to figure out if anything is still
|
|
// suspended in the rendered set.
|
|
// If new content unsuspended, but there's still some content that
|
|
// didn't. Then we need to do a second pass that forces everything
|
|
// to keep showing their fallbacks.
|
|
// We might be suspended if something in this render pass suspended, or
|
|
// something in the previous committed pass suspended. Otherwise,
|
|
// there's no chance so we can skip the expensive call to
|
|
// findFirstSuspended.
|
|
var cannotBeSuspended =
|
|
renderHasNotSuspendedYet() &&
|
|
(current === null || (current.flags & DidCapture) === NoFlags);
|
|
|
|
if (!cannotBeSuspended) {
|
|
var row = workInProgress.child;
|
|
|
|
while (row !== null) {
|
|
var suspended = findFirstSuspended(row);
|
|
|
|
if (suspended !== null) {
|
|
didSuspendAlready = true;
|
|
workInProgress.flags |= DidCapture;
|
|
cutOffTailIfNeeded(renderState, false); // If this is a newly suspended tree, it might not get committed as
|
|
// part of the second pass. In that case nothing will subscribe to
|
|
// its thenables. Instead, we'll transfer its thenables to the
|
|
// SuspenseList so that it can retry if they resolve.
|
|
// There might be multiple of these in the list but since we're
|
|
// going to wait for all of them anyway, it doesn't really matter
|
|
// which ones gets to ping. In theory we could get clever and keep
|
|
// track of how many dependencies remain but it gets tricky because
|
|
// in the meantime, we can add/remove/change items and dependencies.
|
|
// We might bail out of the loop before finding any but that
|
|
// doesn't matter since that means that the other boundaries that
|
|
// we did find already has their listeners attached.
|
|
|
|
var newThenables = suspended.updateQueue;
|
|
|
|
if (newThenables !== null) {
|
|
workInProgress.updateQueue = newThenables;
|
|
workInProgress.flags |= Update;
|
|
} // Rerender the whole list, but this time, we'll force fallbacks
|
|
// to stay in place.
|
|
// Reset the effect flags before doing the second pass since that's now invalid.
|
|
// Reset the child fibers to their original state.
|
|
|
|
workInProgress.subtreeFlags = NoFlags;
|
|
resetChildFibers(workInProgress, renderLanes); // Set up the Suspense List Context to force suspense and
|
|
// immediately rerender the children.
|
|
|
|
pushSuspenseListContext(
|
|
workInProgress,
|
|
setShallowSuspenseListContext(
|
|
suspenseStackCursor.current,
|
|
ForceSuspenseFallback
|
|
)
|
|
); // Don't bubble properties in this case.
|
|
|
|
return workInProgress.child;
|
|
}
|
|
|
|
row = row.sibling;
|
|
}
|
|
}
|
|
|
|
if (renderState.tail !== null && now() > getRenderTargetTime()) {
|
|
// We have already passed our CPU deadline but we still have rows
|
|
// left in the tail. We'll just give up further attempts to render
|
|
// the main content and only render fallbacks.
|
|
workInProgress.flags |= DidCapture;
|
|
didSuspendAlready = true;
|
|
cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this
|
|
// to get it started back up to attempt the next item. While in terms
|
|
// of priority this work has the same priority as this current render,
|
|
// it's not part of the same transition once the transition has
|
|
// committed. If it's sync, we still want to yield so that it can be
|
|
// painted. Conceptually, this is really the same as pinging.
|
|
// We can use any RetryLane even if it's the one currently rendering
|
|
// since we're leaving it behind on this node.
|
|
|
|
workInProgress.lanes = SomeRetryLane;
|
|
}
|
|
} else {
|
|
cutOffTailIfNeeded(renderState, false);
|
|
} // Next we're going to render the tail.
|
|
} else {
|
|
// Append the rendered row to the child list.
|
|
if (!didSuspendAlready) {
|
|
var _suspended = findFirstSuspended(renderedTail);
|
|
|
|
if (_suspended !== null) {
|
|
workInProgress.flags |= DidCapture;
|
|
didSuspendAlready = true; // Ensure we transfer the update queue to the parent so that it doesn't
|
|
// get lost if this row ends up dropped during a second pass.
|
|
|
|
var _newThenables = _suspended.updateQueue;
|
|
|
|
if (_newThenables !== null) {
|
|
workInProgress.updateQueue = _newThenables;
|
|
workInProgress.flags |= Update;
|
|
}
|
|
|
|
cutOffTailIfNeeded(renderState, true); // This might have been modified.
|
|
|
|
if (
|
|
renderState.tail === null &&
|
|
renderState.tailMode === "hidden" &&
|
|
!renderedTail.alternate &&
|
|
!getIsHydrating() // We don't cut it if we're hydrating.
|
|
) {
|
|
// We're done.
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
} else if (
|
|
// The time it took to render last row is greater than the remaining
|
|
// time we have to render. So rendering one more row would likely
|
|
// exceed it.
|
|
now() * 2 - renderState.renderingStartTime >
|
|
getRenderTargetTime() &&
|
|
renderLanes !== OffscreenLane
|
|
) {
|
|
// We have now passed our CPU deadline and we'll just give up further
|
|
// attempts to render the main content and only render fallbacks.
|
|
// The assumption is that this is usually faster.
|
|
workInProgress.flags |= DidCapture;
|
|
didSuspendAlready = true;
|
|
cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this
|
|
// to get it started back up to attempt the next item. While in terms
|
|
// of priority this work has the same priority as this current render,
|
|
// it's not part of the same transition once the transition has
|
|
// committed. If it's sync, we still want to yield so that it can be
|
|
// painted. Conceptually, this is really the same as pinging.
|
|
// We can use any RetryLane even if it's the one currently rendering
|
|
// since we're leaving it behind on this node.
|
|
|
|
workInProgress.lanes = SomeRetryLane;
|
|
}
|
|
}
|
|
|
|
if (renderState.isBackwards) {
|
|
// The effect list of the backwards tail will have been added
|
|
// to the end. This breaks the guarantee that life-cycles fire in
|
|
// sibling order but that isn't a strong guarantee promised by React.
|
|
// Especially since these might also just pop in during future commits.
|
|
// Append to the beginning of the list.
|
|
renderedTail.sibling = workInProgress.child;
|
|
workInProgress.child = renderedTail;
|
|
} else {
|
|
var previousSibling = renderState.last;
|
|
|
|
if (previousSibling !== null) {
|
|
previousSibling.sibling = renderedTail;
|
|
} else {
|
|
workInProgress.child = renderedTail;
|
|
}
|
|
|
|
renderState.last = renderedTail;
|
|
}
|
|
}
|
|
|
|
if (renderState.tail !== null) {
|
|
// We still have tail rows to render.
|
|
// Pop a row.
|
|
var next = renderState.tail;
|
|
renderState.rendering = next;
|
|
renderState.tail = next.sibling;
|
|
renderState.renderingStartTime = now();
|
|
next.sibling = null; // Restore the context.
|
|
// TODO: We can probably just avoid popping it instead and only
|
|
// setting it the first time we go from not suspended to suspended.
|
|
|
|
var suspenseContext = suspenseStackCursor.current;
|
|
|
|
if (didSuspendAlready) {
|
|
suspenseContext = setShallowSuspenseListContext(
|
|
suspenseContext,
|
|
ForceSuspenseFallback
|
|
);
|
|
} else {
|
|
suspenseContext = setDefaultShallowSuspenseListContext(
|
|
suspenseContext
|
|
);
|
|
}
|
|
|
|
pushSuspenseListContext(workInProgress, suspenseContext); // Do a pass over the next row.
|
|
// Don't bubble properties in this case.
|
|
|
|
return next;
|
|
}
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
case ScopeComponent: {
|
|
{
|
|
if (current === null) {
|
|
var scopeInstance = createScopeInstance();
|
|
workInProgress.stateNode = scopeInstance;
|
|
prepareScopeUpdate(scopeInstance, workInProgress);
|
|
|
|
if (workInProgress.ref !== null) {
|
|
markRef$1(workInProgress);
|
|
markUpdate(workInProgress);
|
|
}
|
|
} else {
|
|
if (workInProgress.ref !== null) {
|
|
markUpdate(workInProgress);
|
|
}
|
|
|
|
if (current.ref !== workInProgress.ref) {
|
|
markRef$1(workInProgress);
|
|
}
|
|
}
|
|
|
|
bubbleProperties(workInProgress);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
case OffscreenComponent:
|
|
case LegacyHiddenComponent: {
|
|
popSuspenseHandler(workInProgress);
|
|
popHiddenContext(workInProgress);
|
|
var _nextState = workInProgress.memoizedState;
|
|
var nextIsHidden = _nextState !== null; // Schedule a Visibility effect if the visibility has changed
|
|
|
|
{
|
|
if (current !== null) {
|
|
var _prevState = current.memoizedState;
|
|
var prevIsHidden = _prevState !== null;
|
|
|
|
if (prevIsHidden !== nextIsHidden) {
|
|
workInProgress.flags |= Visibility;
|
|
}
|
|
} else {
|
|
// On initial mount, we only need a Visibility effect if the tree
|
|
// is hidden.
|
|
if (nextIsHidden) {
|
|
workInProgress.flags |= Visibility;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!nextIsHidden || (workInProgress.mode & ConcurrentMode) === NoMode) {
|
|
bubbleProperties(workInProgress);
|
|
} else {
|
|
// Don't bubble properties for hidden children unless we're rendering
|
|
// at offscreen priority.
|
|
if (
|
|
includesSomeLane(renderLanes, OffscreenLane) && // Also don't bubble if the tree suspended
|
|
(workInProgress.flags & DidCapture) === NoLanes
|
|
) {
|
|
bubbleProperties(workInProgress); // Check if there was an insertion or update in the hidden subtree.
|
|
// If so, we need to hide those nodes in the commit phase, so
|
|
// schedule a visibility effect.
|
|
|
|
if (workInProgress.subtreeFlags & (Placement | Update)) {
|
|
workInProgress.flags |= Visibility;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (workInProgress.updateQueue !== null) {
|
|
// Schedule an effect to attach Suspense retry listeners
|
|
// TODO: Move to passive phase
|
|
workInProgress.flags |= Update;
|
|
}
|
|
|
|
{
|
|
var _previousCache2 = null;
|
|
|
|
if (
|
|
current !== null &&
|
|
current.memoizedState !== null &&
|
|
current.memoizedState.cachePool !== null
|
|
) {
|
|
_previousCache2 = current.memoizedState.cachePool.pool;
|
|
}
|
|
|
|
var _cache2 = null;
|
|
|
|
if (
|
|
workInProgress.memoizedState !== null &&
|
|
workInProgress.memoizedState.cachePool !== null
|
|
) {
|
|
_cache2 = workInProgress.memoizedState.cachePool.pool;
|
|
}
|
|
|
|
if (_cache2 !== _previousCache2) {
|
|
// Run passive effects to retain/release the cache.
|
|
workInProgress.flags |= Passive;
|
|
}
|
|
}
|
|
|
|
popTransition(workInProgress, current);
|
|
return null;
|
|
}
|
|
|
|
case CacheComponent: {
|
|
{
|
|
var _previousCache3 = null;
|
|
|
|
if (current !== null) {
|
|
_previousCache3 = current.memoizedState.cache;
|
|
}
|
|
|
|
var _cache3 = workInProgress.memoizedState.cache;
|
|
|
|
if (_cache3 !== _previousCache3) {
|
|
// Run passive effects to retain/release the cache.
|
|
workInProgress.flags |= Passive;
|
|
}
|
|
|
|
popCacheProvider(workInProgress);
|
|
bubbleProperties(workInProgress);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
case TracingMarkerComponent: {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
"Unknown unit of work tag (" +
|
|
workInProgress.tag +
|
|
"). This error is likely caused by a bug in " +
|
|
"React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
function unwindWork(current, workInProgress, renderLanes) {
|
|
// Note: This intentionally doesn't check if we're hydrating because comparing
|
|
// to the current tree provider fiber is just as fast and less error-prone.
|
|
// Ideally we would have a special version of the work loop only
|
|
// for hydration.
|
|
popTreeContext(workInProgress);
|
|
|
|
switch (workInProgress.tag) {
|
|
case ClassComponent: {
|
|
var Component = workInProgress.type;
|
|
|
|
var flags = workInProgress.flags;
|
|
|
|
if (flags & ShouldCapture) {
|
|
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
|
|
|
return workInProgress;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
case HostRoot: {
|
|
var root = workInProgress.stateNode;
|
|
|
|
{
|
|
var cache = workInProgress.memoizedState.cache;
|
|
popCacheProvider(workInProgress);
|
|
}
|
|
popHostContainer(workInProgress);
|
|
resetWorkInProgressVersions();
|
|
var _flags = workInProgress.flags;
|
|
|
|
if (
|
|
(_flags & ShouldCapture) !== NoFlags &&
|
|
(_flags & DidCapture) === NoFlags
|
|
) {
|
|
// There was an error during render that wasn't captured by a suspense
|
|
// boundary. Do a second pass on the root to unmount the children.
|
|
workInProgress.flags = (_flags & ~ShouldCapture) | DidCapture;
|
|
return workInProgress;
|
|
} // We unwound to the root without completing it. Exit.
|
|
|
|
return null;
|
|
}
|
|
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
// TODO: popHydrationState
|
|
popHostContext(workInProgress);
|
|
return null;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
popSuspenseHandler(workInProgress);
|
|
var suspenseState = workInProgress.memoizedState;
|
|
|
|
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
|
if (workInProgress.alternate === null) {
|
|
throw new Error(
|
|
"Threw in newly mounted dehydrated component. This is likely a bug in " +
|
|
"React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
resetHydrationState();
|
|
}
|
|
|
|
var _flags2 = workInProgress.flags;
|
|
|
|
if (_flags2 & ShouldCapture) {
|
|
workInProgress.flags = (_flags2 & ~ShouldCapture) | DidCapture; // Captured a suspense effect. Re-render the boundary.
|
|
|
|
return workInProgress;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
case SuspenseListComponent: {
|
|
popSuspenseListContext(workInProgress); // SuspenseList doesn't actually catch anything. It should've been
|
|
// caught by a nested boundary. If not, it should bubble through.
|
|
|
|
return null;
|
|
}
|
|
|
|
case HostPortal:
|
|
popHostContainer(workInProgress);
|
|
return null;
|
|
|
|
case ContextProvider:
|
|
var context = workInProgress.type._context;
|
|
popProvider(context, workInProgress);
|
|
return null;
|
|
|
|
case OffscreenComponent:
|
|
case LegacyHiddenComponent: {
|
|
popSuspenseHandler(workInProgress);
|
|
popHiddenContext(workInProgress);
|
|
popTransition(workInProgress, current);
|
|
var _flags3 = workInProgress.flags;
|
|
|
|
if (_flags3 & ShouldCapture) {
|
|
workInProgress.flags = (_flags3 & ~ShouldCapture) | DidCapture; // Captured a suspense effect. Re-render the boundary.
|
|
|
|
return workInProgress;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
case CacheComponent:
|
|
{
|
|
var _cache = workInProgress.memoizedState.cache;
|
|
popCacheProvider(workInProgress);
|
|
}
|
|
|
|
return null;
|
|
|
|
case TracingMarkerComponent:
|
|
return null;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function unwindInterruptedWork(current, interruptedWork, renderLanes) {
|
|
// Note: This intentionally doesn't check if we're hydrating because comparing
|
|
// to the current tree provider fiber is just as fast and less error-prone.
|
|
// Ideally we would have a special version of the work loop only
|
|
// for hydration.
|
|
popTreeContext(interruptedWork);
|
|
|
|
switch (interruptedWork.tag) {
|
|
case ClassComponent: {
|
|
var childContextTypes = interruptedWork.type.childContextTypes;
|
|
|
|
break;
|
|
}
|
|
|
|
case HostRoot: {
|
|
var root = interruptedWork.stateNode;
|
|
|
|
{
|
|
var cache = interruptedWork.memoizedState.cache;
|
|
popCacheProvider(interruptedWork);
|
|
}
|
|
popHostContainer(interruptedWork);
|
|
resetWorkInProgressVersions();
|
|
break;
|
|
}
|
|
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
popHostContext(interruptedWork);
|
|
break;
|
|
}
|
|
|
|
case HostPortal:
|
|
popHostContainer(interruptedWork);
|
|
break;
|
|
|
|
case SuspenseComponent:
|
|
popSuspenseHandler(interruptedWork);
|
|
break;
|
|
|
|
case SuspenseListComponent:
|
|
popSuspenseListContext(interruptedWork);
|
|
break;
|
|
|
|
case ContextProvider:
|
|
var context = interruptedWork.type._context;
|
|
popProvider(context, interruptedWork);
|
|
break;
|
|
|
|
case OffscreenComponent:
|
|
case LegacyHiddenComponent:
|
|
popSuspenseHandler(interruptedWork);
|
|
popHiddenContext(interruptedWork);
|
|
popTransition(interruptedWork, current);
|
|
break;
|
|
|
|
case CacheComponent:
|
|
{
|
|
var _cache2 = interruptedWork.memoizedState.cache;
|
|
popCacheProvider(interruptedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
var didWarnAboutUndefinedSnapshotBeforeUpdate = null;
|
|
|
|
{
|
|
didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
|
|
} // Used during the commit phase to track the state of the Offscreen component stack.
|
|
// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
|
|
|
|
var offscreenSubtreeIsHidden = false;
|
|
var offscreenSubtreeWasHidden = false;
|
|
var PossiblyWeakSet = typeof WeakSet === "function" ? WeakSet : Set;
|
|
var nextEffect = null; // Used for Profiling builds to track updaters.
|
|
|
|
function shouldProfile(current) {
|
|
return enableProfilerTimer;
|
|
}
|
|
|
|
function reportUncaughtErrorInDEV(error) {
|
|
// Wrapping each small part of the commit phase into a guarded
|
|
// callback is a bit too slow (https://github.com/facebook/react/pull/21666).
|
|
// But we rely on it to surface errors to DEV tools like overlays
|
|
// (https://github.com/facebook/react/issues/21712).
|
|
// As a compromise, rethrow only caught errors in a guard.
|
|
{
|
|
invokeGuardedCallback(null, function() {
|
|
throw error;
|
|
});
|
|
clearCaughtError();
|
|
}
|
|
}
|
|
|
|
var callComponentWillUnmountWithTimer = function(current, instance) {
|
|
instance.props = current.memoizedProps;
|
|
instance.state = current.memoizedState;
|
|
|
|
{
|
|
instance.componentWillUnmount();
|
|
}
|
|
}; // Capture errors so they don't interrupt unmounting.
|
|
|
|
function safelyCallComponentWillUnmount(
|
|
current,
|
|
nearestMountedAncestor,
|
|
instance
|
|
) {
|
|
try {
|
|
callComponentWillUnmountWithTimer(current, instance);
|
|
} catch (error) {
|
|
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
|
}
|
|
} // Capture errors so they don't interrupt mounting.
|
|
|
|
function safelyAttachRef(current, nearestMountedAncestor) {
|
|
try {
|
|
commitAttachRef(current);
|
|
} catch (error) {
|
|
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
|
}
|
|
}
|
|
|
|
function safelyDetachRef(current, nearestMountedAncestor) {
|
|
var ref = current.ref;
|
|
var refCleanup = current.refCleanup;
|
|
|
|
if (ref !== null) {
|
|
if (typeof refCleanup === "function") {
|
|
try {
|
|
if (shouldProfile(current)) {
|
|
try {
|
|
startLayoutEffectTimer();
|
|
refCleanup();
|
|
} finally {
|
|
recordLayoutEffectDuration(current);
|
|
}
|
|
} else {
|
|
refCleanup();
|
|
}
|
|
} catch (error) {
|
|
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
|
} finally {
|
|
// `refCleanup` has been called. Nullify all references to it to prevent double invocation.
|
|
current.refCleanup = null;
|
|
var finishedWork = current.alternate;
|
|
|
|
if (finishedWork != null) {
|
|
finishedWork.refCleanup = null;
|
|
}
|
|
}
|
|
} else if (typeof ref === "function") {
|
|
var retVal;
|
|
|
|
try {
|
|
if (shouldProfile(current)) {
|
|
try {
|
|
startLayoutEffectTimer();
|
|
retVal = ref(null);
|
|
} finally {
|
|
recordLayoutEffectDuration(current);
|
|
}
|
|
} else {
|
|
retVal = ref(null);
|
|
}
|
|
} catch (error) {
|
|
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
|
}
|
|
|
|
{
|
|
if (typeof retVal === "function") {
|
|
error(
|
|
"Unexpected return value from a callback ref in %s. " +
|
|
"A callback ref should not return a function.",
|
|
getComponentNameFromFiber(current)
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// $FlowFixMe unable to narrow type to RefObject
|
|
ref.current = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function safelyCallDestroy(current, nearestMountedAncestor, destroy) {
|
|
try {
|
|
destroy();
|
|
} catch (error) {
|
|
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
|
}
|
|
}
|
|
|
|
var focusedInstanceHandle = null;
|
|
var shouldFireAfterActiveInstanceBlur = false;
|
|
function commitBeforeMutationEffects(root, firstChild) {
|
|
focusedInstanceHandle = prepareForCommit(root.containerInfo);
|
|
nextEffect = firstChild;
|
|
commitBeforeMutationEffects_begin(); // We no longer need to track the active instance fiber
|
|
|
|
var shouldFire = shouldFireAfterActiveInstanceBlur;
|
|
shouldFireAfterActiveInstanceBlur = false;
|
|
focusedInstanceHandle = null;
|
|
return shouldFire;
|
|
}
|
|
|
|
function commitBeforeMutationEffects_begin() {
|
|
while (nextEffect !== null) {
|
|
var fiber = nextEffect; // This phase is only used for beforeActiveInstanceBlur.
|
|
// Let's skip the whole loop if it's off.
|
|
|
|
{
|
|
// TODO: Should wrap this in flags check, too, as optimization
|
|
var deletions = fiber.deletions;
|
|
|
|
if (deletions !== null) {
|
|
for (var i = 0; i < deletions.length; i++) {
|
|
var deletion = deletions[i];
|
|
commitBeforeMutationEffectsDeletion(deletion);
|
|
}
|
|
}
|
|
}
|
|
|
|
var child = fiber.child;
|
|
|
|
if (
|
|
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
|
|
child !== null
|
|
) {
|
|
child.return = fiber;
|
|
nextEffect = child;
|
|
} else {
|
|
commitBeforeMutationEffects_complete();
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitBeforeMutationEffects_complete() {
|
|
while (nextEffect !== null) {
|
|
var fiber = nextEffect;
|
|
setCurrentFiber(fiber);
|
|
|
|
try {
|
|
commitBeforeMutationEffectsOnFiber(fiber);
|
|
} catch (error) {
|
|
captureCommitPhaseError(fiber, fiber.return, error);
|
|
}
|
|
|
|
resetCurrentFiber();
|
|
var sibling = fiber.sibling;
|
|
|
|
if (sibling !== null) {
|
|
sibling.return = fiber.return;
|
|
nextEffect = sibling;
|
|
return;
|
|
}
|
|
|
|
nextEffect = fiber.return;
|
|
}
|
|
}
|
|
|
|
function commitBeforeMutationEffectsOnFiber(finishedWork) {
|
|
var current = finishedWork.alternate;
|
|
var flags = finishedWork.flags;
|
|
|
|
{
|
|
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
|
|
// Check to see if the focused element was inside of a hidden (Suspense) subtree.
|
|
// TODO: Move this out of the hot path using a dedicated effect tag.
|
|
if (
|
|
finishedWork.tag === SuspenseComponent &&
|
|
isSuspenseBoundaryBeingHidden(current, finishedWork) && // $FlowFixMe[incompatible-call] found when upgrading Flow
|
|
doesFiberContain(finishedWork, focusedInstanceHandle)
|
|
) {
|
|
shouldFireAfterActiveInstanceBlur = true;
|
|
beforeActiveInstanceBlur(finishedWork);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((flags & Snapshot) !== NoFlags) {
|
|
setCurrentFiber(finishedWork);
|
|
}
|
|
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent: {
|
|
break;
|
|
}
|
|
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
break;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
if ((flags & Snapshot) !== NoFlags) {
|
|
if (current !== null) {
|
|
var prevProps = current.memoizedProps;
|
|
var prevState = current.memoizedState;
|
|
var instance = finishedWork.stateNode; // We could update instance props and state here,
|
|
// but instead we rely on them being set during last render.
|
|
// TODO: revisit this when we implement resuming.
|
|
|
|
{
|
|
if (
|
|
finishedWork.type === finishedWork.elementType &&
|
|
!didWarnAboutReassigningProps
|
|
) {
|
|
if (instance.props !== finishedWork.memoizedProps) {
|
|
error(
|
|
"Expected %s props to match memoized props before " +
|
|
"getSnapshotBeforeUpdate. " +
|
|
"This might either be because of a bug in React, or because " +
|
|
"a component reassigns its own `this.props`. " +
|
|
"Please file an issue.",
|
|
getComponentNameFromFiber(finishedWork) || "instance"
|
|
);
|
|
}
|
|
|
|
if (instance.state !== finishedWork.memoizedState) {
|
|
error(
|
|
"Expected %s state to match memoized state before " +
|
|
"getSnapshotBeforeUpdate. " +
|
|
"This might either be because of a bug in React, or because " +
|
|
"a component reassigns its own `this.state`. " +
|
|
"Please file an issue.",
|
|
getComponentNameFromFiber(finishedWork) || "instance"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
var snapshot = instance.getSnapshotBeforeUpdate(
|
|
finishedWork.elementType === finishedWork.type
|
|
? prevProps
|
|
: resolveDefaultProps(finishedWork.type, prevProps),
|
|
prevState
|
|
);
|
|
|
|
{
|
|
var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;
|
|
|
|
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
|
|
didWarnSet.add(finishedWork.type);
|
|
|
|
error(
|
|
"%s.getSnapshotBeforeUpdate(): A snapshot value (or null) " +
|
|
"must be returned. You have returned undefined.",
|
|
getComponentNameFromFiber(finishedWork)
|
|
);
|
|
}
|
|
}
|
|
|
|
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HostRoot: {
|
|
if ((flags & Snapshot) !== NoFlags) {
|
|
{
|
|
var root = finishedWork.stateNode;
|
|
clearContainer(root.containerInfo);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HostComponent:
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostText:
|
|
case HostPortal:
|
|
case IncompleteClassComponent:
|
|
// Nothing to do for these component types
|
|
break;
|
|
|
|
default: {
|
|
if ((flags & Snapshot) !== NoFlags) {
|
|
throw new Error(
|
|
"This unit of work tag should not have side-effects. This error is " +
|
|
"likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((flags & Snapshot) !== NoFlags) {
|
|
resetCurrentFiber();
|
|
}
|
|
}
|
|
|
|
function commitBeforeMutationEffectsDeletion(deletion) {
|
|
{
|
|
// TODO (effects) It would be nice to avoid calling doesFiberContain()
|
|
// Maybe we can repurpose one of the subtreeFlags positions for this instead?
|
|
// Use it to store which part of the tree the focused instance is in?
|
|
// This assumes we can safely determine that instance during the "render" phase.
|
|
if (doesFiberContain(deletion, focusedInstanceHandle)) {
|
|
shouldFireAfterActiveInstanceBlur = true;
|
|
beforeActiveInstanceBlur(deletion);
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitHookEffectListUnmount(
|
|
flags,
|
|
finishedWork,
|
|
nearestMountedAncestor
|
|
) {
|
|
var updateQueue = finishedWork.updateQueue;
|
|
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
|
|
|
|
if (lastEffect !== null) {
|
|
var firstEffect = lastEffect.next;
|
|
var effect = firstEffect;
|
|
|
|
do {
|
|
if ((effect.tag & flags) === flags) {
|
|
// Unmount
|
|
var destroy = effect.destroy;
|
|
effect.destroy = undefined;
|
|
|
|
if (destroy !== undefined) {
|
|
{
|
|
if ((flags & Insertion) !== NoFlags$1) {
|
|
setIsRunningInsertionEffect(true);
|
|
}
|
|
}
|
|
|
|
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
|
|
|
|
{
|
|
if ((flags & Insertion) !== NoFlags$1) {
|
|
setIsRunningInsertionEffect(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
effect = effect.next;
|
|
} while (effect !== firstEffect);
|
|
}
|
|
}
|
|
|
|
function commitHookEffectListMount(flags, finishedWork) {
|
|
var updateQueue = finishedWork.updateQueue;
|
|
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
|
|
|
|
if (lastEffect !== null) {
|
|
var firstEffect = lastEffect.next;
|
|
var effect = firstEffect;
|
|
|
|
do {
|
|
if ((effect.tag & flags) === flags) {
|
|
var create = effect.create;
|
|
|
|
{
|
|
if ((flags & Insertion) !== NoFlags$1) {
|
|
setIsRunningInsertionEffect(true);
|
|
}
|
|
}
|
|
|
|
effect.destroy = create();
|
|
|
|
{
|
|
if ((flags & Insertion) !== NoFlags$1) {
|
|
setIsRunningInsertionEffect(false);
|
|
}
|
|
}
|
|
|
|
{
|
|
var destroy = effect.destroy;
|
|
|
|
if (destroy !== undefined && typeof destroy !== "function") {
|
|
var hookName = void 0;
|
|
|
|
if ((effect.tag & Layout) !== NoFlags) {
|
|
hookName = "useLayoutEffect";
|
|
} else if ((effect.tag & Insertion) !== NoFlags) {
|
|
hookName = "useInsertionEffect";
|
|
} else {
|
|
hookName = "useEffect";
|
|
}
|
|
|
|
var addendum = void 0;
|
|
|
|
if (destroy === null) {
|
|
addendum =
|
|
" You returned null. If your effect does not require clean " +
|
|
"up, return undefined (or nothing).";
|
|
} else if (typeof destroy.then === "function") {
|
|
addendum =
|
|
"\n\nIt looks like you wrote " +
|
|
hookName +
|
|
"(async () => ...) or returned a Promise. " +
|
|
"Instead, write the async function inside your effect " +
|
|
"and call it immediately:\n\n" +
|
|
hookName +
|
|
"(() => {\n" +
|
|
" async function fetchData() {\n" +
|
|
" // You can await here\n" +
|
|
" const response = await MyAPI.getData(someId);\n" +
|
|
" // ...\n" +
|
|
" }\n" +
|
|
" fetchData();\n" +
|
|
"}, [someId]); // Or [] if effect doesn't need props or state\n\n" +
|
|
"Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching";
|
|
} else {
|
|
addendum = " You returned: " + destroy;
|
|
}
|
|
|
|
error(
|
|
"%s must not return anything besides a function, " +
|
|
"which is used for clean-up.%s",
|
|
hookName,
|
|
addendum
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
effect = effect.next;
|
|
} while (effect !== firstEffect);
|
|
}
|
|
}
|
|
|
|
function commitHookLayoutEffects(finishedWork, hookFlags) {
|
|
// At this point layout effects have already been destroyed (during mutation phase).
|
|
// This is done to prevent sibling component effects from interfering with each other,
|
|
// e.g. a destroy function in one component should never override a ref set
|
|
// by a create function in another component during the same commit.
|
|
{
|
|
try {
|
|
commitHookEffectListMount(hookFlags, finishedWork);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitClassLayoutLifecycles(finishedWork, current) {
|
|
var instance = finishedWork.stateNode;
|
|
|
|
if (current === null) {
|
|
// We could update instance props and state here,
|
|
// but instead we rely on them being set during last render.
|
|
// TODO: revisit this when we implement resuming.
|
|
{
|
|
if (
|
|
finishedWork.type === finishedWork.elementType &&
|
|
!didWarnAboutReassigningProps
|
|
) {
|
|
if (instance.props !== finishedWork.memoizedProps) {
|
|
error(
|
|
"Expected %s props to match memoized props before " +
|
|
"componentDidMount. " +
|
|
"This might either be because of a bug in React, or because " +
|
|
"a component reassigns its own `this.props`. " +
|
|
"Please file an issue.",
|
|
getComponentNameFromFiber(finishedWork) || "instance"
|
|
);
|
|
}
|
|
|
|
if (instance.state !== finishedWork.memoizedState) {
|
|
error(
|
|
"Expected %s state to match memoized state before " +
|
|
"componentDidMount. " +
|
|
"This might either be because of a bug in React, or because " +
|
|
"a component reassigns its own `this.state`. " +
|
|
"Please file an issue.",
|
|
getComponentNameFromFiber(finishedWork) || "instance"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
try {
|
|
instance.componentDidMount();
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
} else {
|
|
var prevProps =
|
|
finishedWork.elementType === finishedWork.type
|
|
? current.memoizedProps
|
|
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
|
|
var prevState = current.memoizedState; // We could update instance props and state here,
|
|
// but instead we rely on them being set during last render.
|
|
// TODO: revisit this when we implement resuming.
|
|
|
|
{
|
|
if (
|
|
finishedWork.type === finishedWork.elementType &&
|
|
!didWarnAboutReassigningProps
|
|
) {
|
|
if (instance.props !== finishedWork.memoizedProps) {
|
|
error(
|
|
"Expected %s props to match memoized props before " +
|
|
"componentDidUpdate. " +
|
|
"This might either be because of a bug in React, or because " +
|
|
"a component reassigns its own `this.props`. " +
|
|
"Please file an issue.",
|
|
getComponentNameFromFiber(finishedWork) || "instance"
|
|
);
|
|
}
|
|
|
|
if (instance.state !== finishedWork.memoizedState) {
|
|
error(
|
|
"Expected %s state to match memoized state before " +
|
|
"componentDidUpdate. " +
|
|
"This might either be because of a bug in React, or because " +
|
|
"a component reassigns its own `this.state`. " +
|
|
"Please file an issue.",
|
|
getComponentNameFromFiber(finishedWork) || "instance"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
try {
|
|
instance.componentDidUpdate(
|
|
prevProps,
|
|
prevState,
|
|
instance.__reactInternalSnapshotBeforeUpdate
|
|
);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitClassCallbacks(finishedWork) {
|
|
// TODO: I think this is now always non-null by the time it reaches the
|
|
// commit phase. Consider removing the type check.
|
|
var updateQueue = finishedWork.updateQueue;
|
|
|
|
if (updateQueue !== null) {
|
|
var instance = finishedWork.stateNode;
|
|
|
|
{
|
|
if (
|
|
finishedWork.type === finishedWork.elementType &&
|
|
!didWarnAboutReassigningProps
|
|
) {
|
|
if (instance.props !== finishedWork.memoizedProps) {
|
|
error(
|
|
"Expected %s props to match memoized props before " +
|
|
"processing the update queue. " +
|
|
"This might either be because of a bug in React, or because " +
|
|
"a component reassigns its own `this.props`. " +
|
|
"Please file an issue.",
|
|
getComponentNameFromFiber(finishedWork) || "instance"
|
|
);
|
|
}
|
|
|
|
if (instance.state !== finishedWork.memoizedState) {
|
|
error(
|
|
"Expected %s state to match memoized state before " +
|
|
"processing the update queue. " +
|
|
"This might either be because of a bug in React, or because " +
|
|
"a component reassigns its own `this.state`. " +
|
|
"Please file an issue.",
|
|
getComponentNameFromFiber(finishedWork) || "instance"
|
|
);
|
|
}
|
|
}
|
|
} // We could update instance props and state here,
|
|
// but instead we rely on them being set during last render.
|
|
// TODO: revisit this when we implement resuming.
|
|
|
|
try {
|
|
commitCallbacks(updateQueue, instance);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitHostComponentMount(finishedWork) {
|
|
var type = finishedWork.type;
|
|
var props = finishedWork.memoizedProps;
|
|
var instance = finishedWork.stateNode;
|
|
|
|
try {
|
|
commitMount(instance, type, props, finishedWork);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
|
|
function commitLayoutEffectOnFiber(
|
|
finishedRoot,
|
|
current,
|
|
finishedWork,
|
|
committedLanes
|
|
) {
|
|
// When updating this function, also update reappearLayoutEffects, which does
|
|
// most of the same things when an offscreen tree goes from hidden -> visible.
|
|
var flags = finishedWork.flags;
|
|
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
|
|
|
|
if (flags & Update) {
|
|
commitHookLayoutEffects(finishedWork, Layout | HasEffect);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
|
|
|
|
if (flags & Update) {
|
|
commitClassLayoutLifecycles(finishedWork, current);
|
|
}
|
|
|
|
if (flags & Callback) {
|
|
commitClassCallbacks(finishedWork);
|
|
}
|
|
|
|
if (flags & Ref) {
|
|
safelyAttachRef(finishedWork, finishedWork.return);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HostRoot: {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
|
|
|
|
if (flags & Callback) {
|
|
// TODO: I think this is now always non-null by the time it reaches the
|
|
// commit phase. Consider removing the type check.
|
|
var updateQueue = finishedWork.updateQueue;
|
|
|
|
if (updateQueue !== null) {
|
|
var instance = null;
|
|
|
|
if (finishedWork.child !== null) {
|
|
switch (finishedWork.child.tag) {
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
instance = getPublicInstance(finishedWork.child.stateNode);
|
|
break;
|
|
|
|
case ClassComponent:
|
|
instance = finishedWork.child.stateNode;
|
|
break;
|
|
}
|
|
}
|
|
|
|
try {
|
|
commitCallbacks(updateQueue, instance);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HostResource: {
|
|
{
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
|
|
|
|
if (flags & Ref) {
|
|
safelyAttachRef(finishedWork, finishedWork.return);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork); // Renderers may schedule work to be done after host components are mounted
|
|
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
|
|
// These effects should only be committed when components are first mounted,
|
|
// aka when there is no current/alternate.
|
|
|
|
if (current === null && flags & Update) {
|
|
commitHostComponentMount(finishedWork);
|
|
}
|
|
|
|
if (flags & Ref) {
|
|
safelyAttachRef(finishedWork, finishedWork.return);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Profiler: {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork); // TODO: Should this fire inside an offscreen tree? Or should it wait to
|
|
|
|
break;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
|
|
|
|
if (flags & Update) {
|
|
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
var isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode;
|
|
|
|
if (isModernRoot) {
|
|
var isHidden = finishedWork.memoizedState !== null;
|
|
var newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden;
|
|
|
|
if (newOffscreenSubtreeIsHidden);
|
|
else {
|
|
// The Offscreen tree is visible.
|
|
var wasHidden = current !== null && current.memoizedState !== null;
|
|
var newOffscreenSubtreeWasHidden =
|
|
wasHidden || offscreenSubtreeWasHidden;
|
|
var prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
|
|
var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
|
|
offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
|
|
offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
|
|
|
|
if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
|
|
// This is the root of a reappearing boundary. As we continue
|
|
// traversing the layout effects, we must also re-mount layout
|
|
// effects that were unmounted when the Offscreen subtree was
|
|
// hidden. So this is a superset of the normal commitLayoutEffects.
|
|
var includeWorkInProgressEffects =
|
|
(finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
|
|
recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
includeWorkInProgressEffects
|
|
);
|
|
} else {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
|
|
}
|
|
|
|
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
|
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
|
|
}
|
|
} else {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
|
|
}
|
|
|
|
if (flags & Ref) {
|
|
var props = finishedWork.memoizedProps;
|
|
|
|
if (props.mode === "manual") {
|
|
safelyAttachRef(finishedWork, finishedWork.return);
|
|
} else {
|
|
safelyDetachRef(finishedWork, finishedWork.return);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function hideOrUnhideAllChildren(finishedWork, isHidden) {
|
|
// Only hide or unhide the top-most host nodes.
|
|
var hostSubtreeRoot = null;
|
|
|
|
{
|
|
// We only have the top Fiber that was inserted but we need to recurse down its
|
|
// children to find all the terminal nodes.
|
|
var node = finishedWork;
|
|
|
|
while (true) {
|
|
if (
|
|
node.tag === HostComponent ||
|
|
node.tag === HostResource ||
|
|
node.tag === HostSingleton
|
|
) {
|
|
if (hostSubtreeRoot === null) {
|
|
hostSubtreeRoot = node;
|
|
|
|
try {
|
|
var instance = node.stateNode;
|
|
|
|
if (isHidden) {
|
|
hideInstance(instance);
|
|
} else {
|
|
unhideInstance(node.stateNode, node.memoizedProps);
|
|
}
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
} else if (node.tag === HostText) {
|
|
if (hostSubtreeRoot === null) {
|
|
try {
|
|
var _instance = node.stateNode;
|
|
|
|
if (isHidden) {
|
|
hideTextInstance(_instance);
|
|
} else {
|
|
unhideTextInstance(_instance, node.memoizedProps);
|
|
}
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
} else if (
|
|
(node.tag === OffscreenComponent ||
|
|
node.tag === LegacyHiddenComponent) &&
|
|
node.memoizedState !== null &&
|
|
node !== finishedWork
|
|
);
|
|
else if (node.child !== null) {
|
|
node.child.return = node;
|
|
node = node.child;
|
|
continue;
|
|
}
|
|
|
|
if (node === finishedWork) {
|
|
return;
|
|
}
|
|
|
|
while (node.sibling === null) {
|
|
if (node.return === null || node.return === finishedWork) {
|
|
return;
|
|
}
|
|
|
|
if (hostSubtreeRoot === node) {
|
|
hostSubtreeRoot = null;
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
|
|
if (hostSubtreeRoot === node) {
|
|
hostSubtreeRoot = null;
|
|
}
|
|
|
|
node.sibling.return = node.return;
|
|
node = node.sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitAttachRef(finishedWork) {
|
|
var ref = finishedWork.ref;
|
|
|
|
if (ref !== null) {
|
|
var instance = finishedWork.stateNode;
|
|
var instanceToUse;
|
|
|
|
switch (finishedWork.tag) {
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
instanceToUse = getPublicInstance(instance);
|
|
break;
|
|
|
|
default:
|
|
instanceToUse = instance;
|
|
} // Moved outside to ensure DCE works with this flag
|
|
|
|
if (finishedWork.tag === ScopeComponent) {
|
|
instanceToUse = instance;
|
|
}
|
|
|
|
if (typeof ref === "function") {
|
|
{
|
|
finishedWork.refCleanup = ref(instanceToUse);
|
|
}
|
|
} else {
|
|
{
|
|
if (!ref.hasOwnProperty("current")) {
|
|
error(
|
|
"Unexpected ref object provided for %s. " +
|
|
"Use either a ref-setter function or React.createRef().",
|
|
getComponentNameFromFiber(finishedWork)
|
|
);
|
|
}
|
|
} // $FlowFixMe unable to narrow type to the non-function case
|
|
|
|
ref.current = instanceToUse;
|
|
}
|
|
}
|
|
}
|
|
|
|
function detachFiberMutation(fiber) {
|
|
// Cut off the return pointer to disconnect it from the tree.
|
|
// This enables us to detect and warn against state updates on an unmounted component.
|
|
// It also prevents events from bubbling from within disconnected components.
|
|
//
|
|
// Ideally, we should also clear the child pointer of the parent alternate to let this
|
|
// get GC:ed but we don't know which for sure which parent is the current
|
|
// one so we'll settle for GC:ing the subtree of this child.
|
|
// This child itself will be GC:ed when the parent updates the next time.
|
|
//
|
|
// Note that we can't clear child or sibling pointers yet.
|
|
// They're needed for passive effects and for findDOMNode.
|
|
// We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
|
|
//
|
|
// Don't reset the alternate yet, either. We need that so we can detach the
|
|
// alternate's fields in the passive phase. Clearing the return pointer is
|
|
// sufficient for findDOMNode semantics.
|
|
var alternate = fiber.alternate;
|
|
|
|
if (alternate !== null) {
|
|
alternate.return = null;
|
|
}
|
|
|
|
fiber.return = null;
|
|
}
|
|
|
|
function detachFiberAfterEffects(fiber) {
|
|
var alternate = fiber.alternate;
|
|
|
|
if (alternate !== null) {
|
|
fiber.alternate = null;
|
|
detachFiberAfterEffects(alternate);
|
|
} // Note: Defensively using negation instead of < in case
|
|
// `deletedTreeCleanUpLevel` is undefined.
|
|
|
|
{
|
|
// Clear cyclical Fiber fields. This level alone is designed to roughly
|
|
// approximate the planned Fiber refactor. In that world, `setState` will be
|
|
// bound to a special "instance" object instead of a Fiber. The Instance
|
|
// object will not have any of these fields. It will only be connected to
|
|
// the fiber tree via a single link at the root. So if this level alone is
|
|
// sufficient to fix memory issues, that bodes well for our plans.
|
|
fiber.child = null;
|
|
fiber.deletions = null;
|
|
fiber.sibling = null; // The `stateNode` is cyclical because on host nodes it points to the host
|
|
// tree, which has its own pointers to children, parents, and siblings.
|
|
// The other host nodes also point back to fibers, so we should detach that
|
|
// one, too.
|
|
|
|
if (fiber.tag === HostComponent) {
|
|
var hostInstance = fiber.stateNode;
|
|
|
|
if (hostInstance !== null) {
|
|
detachDeletedInstance(hostInstance);
|
|
}
|
|
}
|
|
|
|
fiber.stateNode = null; // I'm intentionally not clearing the `return` field in this level. We
|
|
// already disconnect the `return` pointer at the root of the deleted
|
|
// subtree (in `detachFiberMutation`). Besides, `return` by itself is not
|
|
// cyclical — it's only cyclical when combined with `child`, `sibling`, and
|
|
// `alternate`. But we'll clear it in the next level anyway, just in case.
|
|
|
|
{
|
|
fiber._debugOwner = null;
|
|
}
|
|
|
|
{
|
|
// Theoretically, nothing in here should be necessary, because we already
|
|
// disconnected the fiber from the tree. So even if something leaks this
|
|
// particular fiber, it won't leak anything else
|
|
//
|
|
// The purpose of this branch is to be super aggressive so we can measure
|
|
// if there's any difference in memory impact. If there is, that could
|
|
// indicate a React leak we don't know about.
|
|
fiber.return = null;
|
|
fiber.dependencies = null;
|
|
fiber.memoizedProps = null;
|
|
fiber.memoizedState = null;
|
|
fiber.pendingProps = null;
|
|
fiber.stateNode = null; // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
|
|
|
|
fiber.updateQueue = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getHostParentFiber(fiber) {
|
|
var parent = fiber.return;
|
|
|
|
while (parent !== null) {
|
|
if (isHostParent(parent)) {
|
|
return parent;
|
|
}
|
|
|
|
parent = parent.return;
|
|
}
|
|
|
|
throw new Error(
|
|
"Expected to find a host parent. This error is likely caused by a bug " +
|
|
"in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
function isHostParent(fiber) {
|
|
return (
|
|
fiber.tag === HostComponent ||
|
|
fiber.tag === HostRoot ||
|
|
fiber.tag === HostResource ||
|
|
fiber.tag === HostSingleton ||
|
|
fiber.tag === HostPortal
|
|
);
|
|
}
|
|
|
|
function getHostSibling(fiber) {
|
|
// We're going to search forward into the tree until we find a sibling host
|
|
// node. Unfortunately, if multiple insertions are done in a row we have to
|
|
// search past them. This leads to exponential search for the next sibling.
|
|
// TODO: Find a more efficient way to do this.
|
|
var node = fiber;
|
|
|
|
siblings: while (true) {
|
|
// If we didn't find anything, let's try the next sibling.
|
|
while (node.sibling === null) {
|
|
if (node.return === null || isHostParent(node.return)) {
|
|
// If we pop out of the root or hit the parent the fiber we are the
|
|
// last sibling.
|
|
return null;
|
|
} // $FlowFixMe[incompatible-type] found when upgrading Flow
|
|
|
|
node = node.return;
|
|
}
|
|
|
|
node.sibling.return = node.return;
|
|
node = node.sibling;
|
|
|
|
while (
|
|
node.tag !== HostComponent &&
|
|
node.tag !== HostText &&
|
|
node.tag !== HostSingleton &&
|
|
node.tag !== DehydratedFragment
|
|
) {
|
|
// If it is not host node and, we might have a host node inside it.
|
|
// Try to search down until we find one.
|
|
if (node.flags & Placement) {
|
|
// If we don't have a child, try the siblings instead.
|
|
continue siblings;
|
|
} // If we don't have a child, try the siblings instead.
|
|
// We also skip portals because they are not part of this host tree.
|
|
|
|
if (node.child === null || node.tag === HostPortal) {
|
|
continue siblings;
|
|
} else {
|
|
node.child.return = node;
|
|
node = node.child;
|
|
}
|
|
} // Check if this host node is stable or about to be placed.
|
|
|
|
if (!(node.flags & Placement)) {
|
|
// Found it!
|
|
return node.stateNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitPlacement(finishedWork) {
|
|
{
|
|
if (finishedWork.tag === HostSingleton) {
|
|
// Singletons are already in the Host and don't need to be placed
|
|
// Since they operate somewhat like Portals though their children will
|
|
// have Placement and will get placed inside them
|
|
return;
|
|
}
|
|
} // Recursively insert all host nodes into the parent.
|
|
|
|
var parentFiber = getHostParentFiber(finishedWork);
|
|
|
|
switch (parentFiber.tag) {
|
|
case HostSingleton: {
|
|
{
|
|
var parent = parentFiber.stateNode;
|
|
var before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its
|
|
// children to find all the terminal nodes.
|
|
|
|
insertOrAppendPlacementNode(finishedWork, before, parent);
|
|
break;
|
|
}
|
|
}
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case HostComponent: {
|
|
var _parent = parentFiber.stateNode;
|
|
|
|
if (parentFiber.flags & ContentReset) {
|
|
// Reset the text content of the parent before doing any insertions
|
|
resetTextContent(_parent); // Clear ContentReset from the effect tag
|
|
|
|
parentFiber.flags &= ~ContentReset;
|
|
}
|
|
|
|
var _before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its
|
|
// children to find all the terminal nodes.
|
|
|
|
insertOrAppendPlacementNode(finishedWork, _before, _parent);
|
|
break;
|
|
}
|
|
|
|
case HostRoot:
|
|
case HostPortal: {
|
|
var _parent2 = parentFiber.stateNode.containerInfo;
|
|
|
|
var _before2 = getHostSibling(finishedWork);
|
|
|
|
insertOrAppendPlacementNodeIntoContainer(
|
|
finishedWork,
|
|
_before2,
|
|
_parent2
|
|
);
|
|
break;
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default:
|
|
throw new Error(
|
|
"Invalid host parent fiber. This error is likely caused by a bug " +
|
|
"in React. Please file an issue."
|
|
);
|
|
}
|
|
}
|
|
|
|
function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
|
|
var tag = node.tag;
|
|
var isHost = tag === HostComponent || tag === HostText;
|
|
|
|
if (isHost) {
|
|
var stateNode = node.stateNode;
|
|
|
|
if (before) {
|
|
insertInContainerBefore(parent, stateNode, before);
|
|
} else {
|
|
appendChildToContainer(parent, stateNode);
|
|
}
|
|
} else if (tag === HostPortal || tag === HostSingleton);
|
|
else {
|
|
var child = node.child;
|
|
|
|
if (child !== null) {
|
|
insertOrAppendPlacementNodeIntoContainer(child, before, parent);
|
|
var sibling = child.sibling;
|
|
|
|
while (sibling !== null) {
|
|
insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
|
|
sibling = sibling.sibling;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertOrAppendPlacementNode(node, before, parent) {
|
|
var tag = node.tag;
|
|
var isHost = tag === HostComponent || tag === HostText;
|
|
|
|
if (isHost) {
|
|
var stateNode = node.stateNode;
|
|
|
|
if (before) {
|
|
insertBefore(parent, stateNode, before);
|
|
} else {
|
|
appendChild(parent, stateNode);
|
|
}
|
|
} else if (tag === HostPortal || tag === HostSingleton);
|
|
else {
|
|
var child = node.child;
|
|
|
|
if (child !== null) {
|
|
insertOrAppendPlacementNode(child, before, parent);
|
|
var sibling = child.sibling;
|
|
|
|
while (sibling !== null) {
|
|
insertOrAppendPlacementNode(sibling, before, parent);
|
|
sibling = sibling.sibling;
|
|
}
|
|
}
|
|
}
|
|
} // These are tracked on the stack as we recursively traverse a
|
|
// deleted subtree.
|
|
// TODO: Update these during the whole mutation phase, not just during
|
|
// a deletion.
|
|
|
|
var hostParent = null;
|
|
var hostParentIsContainer = false;
|
|
|
|
function commitDeletionEffects(root, returnFiber, deletedFiber) {
|
|
{
|
|
// We only have the top Fiber that was deleted but we need to recurse down its
|
|
// children to find all the terminal nodes.
|
|
// Recursively delete all host nodes from the parent, detach refs, clean
|
|
// up mounted layout effects, and call componentWillUnmount.
|
|
// We only need to remove the topmost host child in each branch. But then we
|
|
// still need to keep traversing to unmount effects, refs, and cWU. TODO: We
|
|
// could split this into two separate traversals functions, where the second
|
|
// one doesn't include any removeChild logic. This is maybe the same
|
|
// function as "disappearLayoutEffects" (or whatever that turns into after
|
|
// the layout phase is refactored to use recursion).
|
|
// Before starting, find the nearest host parent on the stack so we know
|
|
// which instance/container to remove the children from.
|
|
// TODO: Instead of searching up the fiber return path on every deletion, we
|
|
// can track the nearest host component on the JS stack as we traverse the
|
|
// tree during the commit phase. This would make insertions faster, too.
|
|
var parent = returnFiber;
|
|
|
|
findParent: while (parent !== null) {
|
|
switch (parent.tag) {
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
hostParent = parent.stateNode;
|
|
hostParentIsContainer = false;
|
|
break findParent;
|
|
}
|
|
|
|
case HostRoot: {
|
|
hostParent = parent.stateNode.containerInfo;
|
|
hostParentIsContainer = true;
|
|
break findParent;
|
|
}
|
|
|
|
case HostPortal: {
|
|
hostParent = parent.stateNode.containerInfo;
|
|
hostParentIsContainer = true;
|
|
break findParent;
|
|
}
|
|
}
|
|
|
|
parent = parent.return;
|
|
}
|
|
|
|
if (hostParent === null) {
|
|
throw new Error(
|
|
"Expected to find a host parent. This error is likely caused by " +
|
|
"a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
|
|
hostParent = null;
|
|
hostParentIsContainer = false;
|
|
}
|
|
|
|
detachFiberMutation(deletedFiber);
|
|
}
|
|
|
|
function recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
parent
|
|
) {
|
|
// TODO: Use a static flag to skip trees that don't have unmount effects
|
|
var child = parent.child;
|
|
|
|
while (child !== null) {
|
|
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
function commitDeletionEffectsOnFiber(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
) {
|
|
onCommitUnmount(deletedFiber); // The cases in this outer switch modify the stack before they traverse
|
|
// into their subtree. There are simpler cases in the inner switch
|
|
// that don't modify the stack.
|
|
|
|
switch (deletedFiber.tag) {
|
|
case HostResource: {
|
|
{
|
|
if (!offscreenSubtreeWasHidden) {
|
|
safelyDetachRef(deletedFiber, nearestMountedAncestor);
|
|
}
|
|
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
|
|
if (deletedFiber.memoizedState) {
|
|
releaseResource(deletedFiber.memoizedState);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case HostSingleton: {
|
|
{
|
|
if (!offscreenSubtreeWasHidden) {
|
|
safelyDetachRef(deletedFiber, nearestMountedAncestor);
|
|
}
|
|
|
|
var prevHostParent = hostParent;
|
|
var prevHostParentIsContainer = hostParentIsContainer;
|
|
hostParent = deletedFiber.stateNode;
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
); // Normally this is called in passive unmount effect phase however with
|
|
// HostSingleton we warn if you acquire one that is already associated to
|
|
// a different fiber. To increase our chances of avoiding this, specifically
|
|
// if you keyed a HostSingleton so there will be a delete followed by a Placement
|
|
// we treat detach eagerly here
|
|
|
|
releaseSingletonInstance(deletedFiber.stateNode);
|
|
hostParent = prevHostParent;
|
|
hostParentIsContainer = prevHostParentIsContainer;
|
|
return;
|
|
}
|
|
}
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case HostComponent: {
|
|
if (!offscreenSubtreeWasHidden) {
|
|
safelyDetachRef(deletedFiber, nearestMountedAncestor);
|
|
} // Intentional fallthrough to next branch
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case HostText: {
|
|
// We only need to remove the nearest host child. Set the host parent
|
|
// to `null` on the stack to indicate that nested children don't
|
|
// need to be removed.
|
|
{
|
|
var _prevHostParent = hostParent;
|
|
var _prevHostParentIsContainer = hostParentIsContainer;
|
|
hostParent = null;
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
hostParent = _prevHostParent;
|
|
hostParentIsContainer = _prevHostParentIsContainer;
|
|
|
|
if (hostParent !== null) {
|
|
// Now that all the child effects have unmounted, we can remove the
|
|
// node from the tree.
|
|
if (hostParentIsContainer) {
|
|
removeChildFromContainer(hostParent, deletedFiber.stateNode);
|
|
} else {
|
|
removeChild(hostParent, deletedFiber.stateNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case DehydratedFragment: {
|
|
{
|
|
var hydrationCallbacks = finishedRoot.hydrationCallbacks;
|
|
|
|
if (hydrationCallbacks !== null) {
|
|
var onDeleted = hydrationCallbacks.onDeleted;
|
|
|
|
if (onDeleted) {
|
|
onDeleted(deletedFiber.stateNode);
|
|
}
|
|
}
|
|
} // Dehydrated fragments don't have any children
|
|
// Delete the dehydrated suspense boundary and all of its content.
|
|
|
|
{
|
|
if (hostParent !== null) {
|
|
if (hostParentIsContainer) {
|
|
clearSuspenseBoundaryFromContainer(
|
|
hostParent,
|
|
deletedFiber.stateNode
|
|
);
|
|
} else {
|
|
clearSuspenseBoundary(hostParent, deletedFiber.stateNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case HostPortal: {
|
|
{
|
|
// When we go into a portal, it becomes the parent to remove from.
|
|
var _prevHostParent2 = hostParent;
|
|
var _prevHostParentIsContainer2 = hostParentIsContainer;
|
|
hostParent = deletedFiber.stateNode.containerInfo;
|
|
hostParentIsContainer = true;
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
hostParent = _prevHostParent2;
|
|
hostParentIsContainer = _prevHostParentIsContainer2;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case MemoComponent:
|
|
case SimpleMemoComponent: {
|
|
if (!offscreenSubtreeWasHidden) {
|
|
var updateQueue = deletedFiber.updateQueue;
|
|
|
|
if (updateQueue !== null) {
|
|
var lastEffect = updateQueue.lastEffect;
|
|
|
|
if (lastEffect !== null) {
|
|
var firstEffect = lastEffect.next;
|
|
var effect = firstEffect;
|
|
|
|
do {
|
|
var _effect = effect,
|
|
destroy = _effect.destroy,
|
|
tag = _effect.tag;
|
|
|
|
if (destroy !== undefined) {
|
|
if ((tag & Insertion) !== NoFlags$1) {
|
|
safelyCallDestroy(
|
|
deletedFiber,
|
|
nearestMountedAncestor,
|
|
destroy
|
|
);
|
|
} else if ((tag & Layout) !== NoFlags$1) {
|
|
{
|
|
safelyCallDestroy(
|
|
deletedFiber,
|
|
nearestMountedAncestor,
|
|
destroy
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
effect = effect.next;
|
|
} while (effect !== firstEffect);
|
|
}
|
|
}
|
|
}
|
|
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
return;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
if (!offscreenSubtreeWasHidden) {
|
|
safelyDetachRef(deletedFiber, nearestMountedAncestor);
|
|
var instance = deletedFiber.stateNode;
|
|
|
|
if (typeof instance.componentWillUnmount === "function") {
|
|
safelyCallComponentWillUnmount(
|
|
deletedFiber,
|
|
nearestMountedAncestor,
|
|
instance
|
|
);
|
|
}
|
|
}
|
|
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
return;
|
|
}
|
|
|
|
case ScopeComponent: {
|
|
{
|
|
safelyDetachRef(deletedFiber, nearestMountedAncestor);
|
|
}
|
|
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
return;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
safelyDetachRef(deletedFiber, nearestMountedAncestor);
|
|
|
|
if (deletedFiber.mode & ConcurrentMode) {
|
|
// If this offscreen component is hidden, we already unmounted it. Before
|
|
// deleting the children, track that it's already unmounted so that we
|
|
// don't attempt to unmount the effects again.
|
|
// TODO: If the tree is hidden, in most cases we should be able to skip
|
|
// over the nested children entirely. An exception is we haven't yet found
|
|
// the topmost host node to delete, which we already track on the stack.
|
|
// But the other case is portals, which need to be detached no matter how
|
|
// deeply they are nested. We should use a subtree flag to track whether a
|
|
// subtree includes a nested portal.
|
|
var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
|
|
offscreenSubtreeWasHidden =
|
|
prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
|
|
} else {
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
recursivelyTraverseDeletionEffects(
|
|
finishedRoot,
|
|
nearestMountedAncestor,
|
|
deletedFiber
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitSuspenseCallback(finishedWork) {
|
|
// TODO: Move this to passive phase
|
|
var newState = finishedWork.memoizedState;
|
|
|
|
if (newState !== null) {
|
|
var suspenseCallback = finishedWork.memoizedProps.suspenseCallback;
|
|
|
|
if (typeof suspenseCallback === "function") {
|
|
var wakeables = finishedWork.updateQueue;
|
|
|
|
if (wakeables !== null) {
|
|
suspenseCallback(new Set(wakeables));
|
|
}
|
|
} else {
|
|
if (suspenseCallback !== undefined) {
|
|
error("Unexpected type for suspenseCallback.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitSuspenseHydrationCallbacks(finishedRoot, finishedWork) {
|
|
var newState = finishedWork.memoizedState;
|
|
|
|
if (newState === null) {
|
|
var current = finishedWork.alternate;
|
|
|
|
if (current !== null) {
|
|
var prevState = current.memoizedState;
|
|
|
|
if (prevState !== null) {
|
|
var suspenseInstance = prevState.dehydrated;
|
|
|
|
if (suspenseInstance !== null) {
|
|
try {
|
|
commitHydratedSuspenseInstance(suspenseInstance);
|
|
|
|
if (enableSuspenseCallback) {
|
|
var hydrationCallbacks = finishedRoot.hydrationCallbacks;
|
|
|
|
if (hydrationCallbacks !== null) {
|
|
var onHydrated = hydrationCallbacks.onHydrated;
|
|
|
|
if (onHydrated) {
|
|
onHydrated(suspenseInstance);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getRetryCache(finishedWork) {
|
|
// TODO: Unify the interface for the retry cache so we don't have to switch
|
|
// on the tag like this.
|
|
switch (finishedWork.tag) {
|
|
case SuspenseComponent:
|
|
case SuspenseListComponent: {
|
|
var retryCache = finishedWork.stateNode;
|
|
|
|
if (retryCache === null) {
|
|
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
|
|
}
|
|
|
|
return retryCache;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
var instance = finishedWork.stateNode;
|
|
var _retryCache = instance._retryCache;
|
|
|
|
if (_retryCache === null) {
|
|
_retryCache = instance._retryCache = new PossiblyWeakSet();
|
|
}
|
|
|
|
return _retryCache;
|
|
}
|
|
|
|
default: {
|
|
throw new Error(
|
|
"Unexpected Suspense handler tag (" +
|
|
finishedWork.tag +
|
|
"). This is a " +
|
|
"bug in React."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function detachOffscreenInstance(instance) {
|
|
var fiber = instance._current;
|
|
|
|
if (fiber === null) {
|
|
throw new Error(
|
|
"Calling Offscreen.detach before instance handle has been set."
|
|
);
|
|
}
|
|
|
|
if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags) {
|
|
// The instance is already detached, this is a noop.
|
|
return;
|
|
} // TODO: There is an opportunity to optimise this by not entering commit phase
|
|
// and unmounting effects directly.
|
|
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
instance._pendingVisibility |= OffscreenDetached;
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
}
|
|
function attachOffscreenInstance(instance) {
|
|
var fiber = instance._current;
|
|
|
|
if (fiber === null) {
|
|
throw new Error(
|
|
"Calling Offscreen.detach before instance handle has been set."
|
|
);
|
|
}
|
|
|
|
if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) {
|
|
// The instance is already attached, this is a noop.
|
|
return;
|
|
}
|
|
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
instance._pendingVisibility &= ~OffscreenDetached;
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
}
|
|
|
|
function attachSuspenseRetryListeners(finishedWork, wakeables) {
|
|
// If this boundary just timed out, then it will have a set of wakeables.
|
|
// For each wakeable, attach a listener so that when it resolves, React
|
|
// attempts to re-render the boundary in the primary (pre-timeout) state.
|
|
var retryCache = getRetryCache(finishedWork);
|
|
wakeables.forEach(function(wakeable) {
|
|
// Memoize using the boundary fiber to prevent redundant listeners.
|
|
var retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
|
|
|
|
if (!retryCache.has(wakeable)) {
|
|
retryCache.add(wakeable);
|
|
|
|
wakeable.then(retry, retry);
|
|
}
|
|
});
|
|
} // This function detects when a Suspense boundary goes from visible to hidden.
|
|
// It returns false if the boundary is already hidden.
|
|
// TODO: Use an effect tag.
|
|
|
|
function isSuspenseBoundaryBeingHidden(current, finishedWork) {
|
|
if (current !== null) {
|
|
var oldState = current.memoizedState;
|
|
|
|
if (oldState === null || oldState.dehydrated !== null) {
|
|
var newState = finishedWork.memoizedState;
|
|
return newState !== null && newState.dehydrated === null;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
function commitMutationEffects(root, finishedWork, committedLanes) {
|
|
setCurrentFiber(finishedWork);
|
|
commitMutationEffectsOnFiber(finishedWork, root);
|
|
setCurrentFiber(finishedWork);
|
|
}
|
|
|
|
function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
|
|
// Deletions effects can be scheduled on any fiber type. They need to happen
|
|
// before the children effects hae fired.
|
|
var deletions = parentFiber.deletions;
|
|
|
|
if (deletions !== null) {
|
|
for (var i = 0; i < deletions.length; i++) {
|
|
var childToDelete = deletions[i];
|
|
|
|
try {
|
|
commitDeletionEffects(root, parentFiber, childToDelete);
|
|
} catch (error) {
|
|
captureCommitPhaseError(childToDelete, parentFiber, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
var prevDebugFiber = getCurrentFiber();
|
|
|
|
if (parentFiber.subtreeFlags & MutationMask) {
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
setCurrentFiber(child);
|
|
commitMutationEffectsOnFiber(child, root);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
setCurrentFiber(prevDebugFiber);
|
|
}
|
|
|
|
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
|
|
var current = finishedWork.alternate;
|
|
var flags = finishedWork.flags; // The effect flag should be checked *after* we refine the type of fiber,
|
|
// because the fiber tag is more specific. An exception is any flag related
|
|
// to reconciliation, because those can be set on all fiber types.
|
|
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case MemoComponent:
|
|
case SimpleMemoComponent: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
|
|
if (flags & Update) {
|
|
try {
|
|
commitHookEffectListUnmount(
|
|
Insertion | HasEffect,
|
|
finishedWork,
|
|
finishedWork.return
|
|
);
|
|
commitHookEffectListMount(Insertion | HasEffect, finishedWork);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
} // Layout effects are destroyed during the mutation phase so that all
|
|
// destroy functions for all fibers are called before any create functions.
|
|
// This prevents sibling component effects from interfering with each other,
|
|
// e.g. a destroy function in one component should never override a ref set
|
|
// by a create function in another component during the same commit.
|
|
|
|
{
|
|
try {
|
|
commitHookEffectListUnmount(
|
|
Layout | HasEffect,
|
|
finishedWork,
|
|
finishedWork.return
|
|
);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
|
|
if (flags & Ref) {
|
|
if (current !== null) {
|
|
safelyDetachRef(current, current.return);
|
|
}
|
|
}
|
|
|
|
if (flags & Callback && offscreenSubtreeIsHidden) {
|
|
var updateQueue = finishedWork.updateQueue;
|
|
|
|
if (updateQueue !== null) {
|
|
deferHiddenCallbacks(updateQueue);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case HostResource: {
|
|
{
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
|
|
if (flags & Ref) {
|
|
if (current !== null) {
|
|
safelyDetachRef(current, current.return);
|
|
}
|
|
}
|
|
|
|
if (flags & Update) {
|
|
var newResource = finishedWork.memoizedState;
|
|
|
|
if (current !== null) {
|
|
var currentResource = current.memoizedState;
|
|
|
|
if (currentResource !== newResource) {
|
|
releaseResource(currentResource);
|
|
}
|
|
}
|
|
|
|
finishedWork.stateNode = newResource
|
|
? acquireResource(newResource)
|
|
: null;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case HostSingleton: {
|
|
{
|
|
if (flags & Update) {
|
|
var previousWork = finishedWork.alternate;
|
|
|
|
if (previousWork === null) {
|
|
var singleton = finishedWork.stateNode;
|
|
var props = finishedWork.memoizedProps; // This was a new mount, we need to clear and set initial properties
|
|
|
|
clearSingleton(singleton);
|
|
acquireSingletonInstance(
|
|
finishedWork.type,
|
|
props,
|
|
singleton,
|
|
finishedWork
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
case HostComponent: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
|
|
if (flags & Ref) {
|
|
if (current !== null) {
|
|
safelyDetachRef(current, current.return);
|
|
}
|
|
}
|
|
|
|
{
|
|
// TODO: ContentReset gets cleared by the children during the commit
|
|
// phase. This is a refactor hazard because it means we must read
|
|
// flags the flags after `commitReconciliationEffects` has already run;
|
|
// the order matters. We should refactor so that ContentReset does not
|
|
// rely on mutating the flag during commit. Like by setting a flag
|
|
// during the render phase instead.
|
|
if (finishedWork.flags & ContentReset) {
|
|
var instance = finishedWork.stateNode;
|
|
|
|
try {
|
|
resetTextContent(instance);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
|
|
if (flags & Update) {
|
|
var _instance2 = finishedWork.stateNode;
|
|
|
|
if (_instance2 != null) {
|
|
// Commit the work prepared earlier.
|
|
var newProps = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps
|
|
// as the newProps. The updatePayload will contain the real change in
|
|
// this case.
|
|
|
|
var oldProps = current !== null ? current.memoizedProps : newProps;
|
|
var type = finishedWork.type; // TODO: Type the updateQueue to be specific to host components.
|
|
|
|
var updatePayload = finishedWork.updateQueue;
|
|
finishedWork.updateQueue = null;
|
|
|
|
if (updatePayload !== null) {
|
|
try {
|
|
commitUpdate(
|
|
_instance2,
|
|
updatePayload,
|
|
type,
|
|
oldProps,
|
|
newProps,
|
|
finishedWork
|
|
);
|
|
} catch (error) {
|
|
captureCommitPhaseError(
|
|
finishedWork,
|
|
finishedWork.return,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case HostText: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
|
|
if (flags & Update) {
|
|
{
|
|
if (finishedWork.stateNode === null) {
|
|
throw new Error(
|
|
"This should have a text node initialized. This error is likely " +
|
|
"caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
var textInstance = finishedWork.stateNode;
|
|
var newText = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps
|
|
// as the newProps. The updatePayload will contain the real change in
|
|
// this case.
|
|
|
|
var oldText = current !== null ? current.memoizedProps : newText;
|
|
|
|
try {
|
|
commitTextUpdate(textInstance, oldText, newText);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case HostRoot: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
|
|
if (flags & Update) {
|
|
{
|
|
if (current !== null) {
|
|
var prevRootState = current.memoizedState;
|
|
|
|
if (prevRootState.isDehydrated) {
|
|
try {
|
|
commitHydratedContainer(root.containerInfo);
|
|
} catch (error) {
|
|
captureCommitPhaseError(
|
|
finishedWork,
|
|
finishedWork.return,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case HostPortal: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
|
|
return;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
var offscreenFiber = finishedWork.child;
|
|
|
|
if (offscreenFiber.flags & Visibility) {
|
|
var newState = offscreenFiber.memoizedState;
|
|
var isHidden = newState !== null;
|
|
|
|
if (isHidden) {
|
|
var wasHidden =
|
|
offscreenFiber.alternate !== null &&
|
|
offscreenFiber.alternate.memoizedState !== null;
|
|
|
|
if (!wasHidden) {
|
|
// TODO: Move to passive phase
|
|
markCommitTimeOfFallback();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flags & Update) {
|
|
try {
|
|
commitSuspenseCallback(finishedWork);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
|
|
var wakeables = finishedWork.updateQueue;
|
|
|
|
if (wakeables !== null) {
|
|
finishedWork.updateQueue = null;
|
|
attachSuspenseRetryListeners(finishedWork, wakeables);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
if (flags & Ref) {
|
|
if (current !== null) {
|
|
safelyDetachRef(current, current.return);
|
|
}
|
|
}
|
|
|
|
var _newState = finishedWork.memoizedState;
|
|
|
|
var _isHidden = _newState !== null;
|
|
|
|
var _wasHidden = current !== null && current.memoizedState !== null;
|
|
|
|
if (finishedWork.mode & ConcurrentMode) {
|
|
// Before committing the children, track on the stack whether this
|
|
// offscreen subtree was already hidden, so that we don't unmount the
|
|
// effects again.
|
|
var prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
|
|
var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
|
|
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden || _isHidden;
|
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || _wasHidden;
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
|
|
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
|
|
} else {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
}
|
|
|
|
commitReconciliationEffects(finishedWork);
|
|
var offscreenInstance = finishedWork.stateNode; // TODO: Add explicit effect flag to set _current.
|
|
|
|
offscreenInstance._current = finishedWork; // Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
|
|
// to support batching of `attach` and `detach` calls.
|
|
|
|
offscreenInstance._visibility &= ~OffscreenDetached;
|
|
offscreenInstance._visibility |=
|
|
offscreenInstance._pendingVisibility & OffscreenDetached;
|
|
|
|
if (flags & Visibility) {
|
|
// Track the current state on the Offscreen instance so we can
|
|
// read it during an event
|
|
if (_isHidden) {
|
|
offscreenInstance._visibility &= ~OffscreenVisible;
|
|
} else {
|
|
offscreenInstance._visibility |= OffscreenVisible;
|
|
}
|
|
|
|
if (_isHidden) {
|
|
var isUpdate = current !== null;
|
|
var wasHiddenByAncestorOffscreen =
|
|
offscreenSubtreeIsHidden || offscreenSubtreeWasHidden; // Only trigger disapper layout effects if:
|
|
// - This is an update, not first mount.
|
|
// - This Offscreen was not hidden before.
|
|
// - Ancestor Offscreen was not hidden in previous commit.
|
|
|
|
if (isUpdate && !_wasHidden && !wasHiddenByAncestorOffscreen) {
|
|
if ((finishedWork.mode & ConcurrentMode) !== NoMode) {
|
|
// Disappear the layout effects of all the children
|
|
recursivelyTraverseDisappearLayoutEffects(finishedWork);
|
|
}
|
|
}
|
|
} // Offscreen with manual mode manages visibility manually.
|
|
|
|
if (!isOffscreenManual(finishedWork)) {
|
|
// TODO: This needs to run whenever there's an insertion or update
|
|
// inside a hidden Offscreen tree.
|
|
hideOrUnhideAllChildren(finishedWork, _isHidden);
|
|
}
|
|
} // TODO: Move to passive phase
|
|
|
|
if (flags & Update) {
|
|
var offscreenQueue = finishedWork.updateQueue;
|
|
|
|
if (offscreenQueue !== null) {
|
|
var _wakeables = offscreenQueue.wakeables;
|
|
|
|
if (_wakeables !== null) {
|
|
offscreenQueue.wakeables = null;
|
|
attachSuspenseRetryListeners(finishedWork, _wakeables);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case SuspenseListComponent: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
|
|
if (flags & Update) {
|
|
var _wakeables2 = finishedWork.updateQueue;
|
|
|
|
if (_wakeables2 !== null) {
|
|
finishedWork.updateQueue = null;
|
|
attachSuspenseRetryListeners(finishedWork, _wakeables2);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case ScopeComponent: {
|
|
{
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork); // TODO: This is a temporary solution that allowed us to transition away
|
|
// from React Flare on www.
|
|
|
|
if (flags & Ref) {
|
|
if (current !== null) {
|
|
safelyDetachRef(finishedWork, finishedWork.return);
|
|
}
|
|
|
|
safelyAttachRef(finishedWork, finishedWork.return);
|
|
}
|
|
|
|
if (flags & Update) {
|
|
var scopeInstance = finishedWork.stateNode;
|
|
prepareScopeUpdate(scopeInstance, finishedWork);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
default: {
|
|
recursivelyTraverseMutationEffects(root, finishedWork);
|
|
commitReconciliationEffects(finishedWork);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitReconciliationEffects(finishedWork) {
|
|
// Placement effects (insertions, reorders) can be scheduled on any fiber
|
|
// type. They needs to happen after the children effects have fired, but
|
|
// before the effects on this fiber have fired.
|
|
var flags = finishedWork.flags;
|
|
|
|
if (flags & Placement) {
|
|
try {
|
|
commitPlacement(finishedWork);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
} // Clear the "placement" from effect tag so that we know that this is
|
|
// inserted, before any life-cycles like componentDidMount gets called.
|
|
// TODO: findDOMNode doesn't rely on this any more but isMounted does
|
|
// and isMounted is deprecated anyway so we should be able to kill this.
|
|
|
|
finishedWork.flags &= ~Placement;
|
|
}
|
|
|
|
if (flags & Hydrating) {
|
|
finishedWork.flags &= ~Hydrating;
|
|
}
|
|
}
|
|
|
|
function commitLayoutEffects(finishedWork, root, committedLanes) {
|
|
var current = finishedWork.alternate;
|
|
commitLayoutEffectOnFiber(root, current, finishedWork);
|
|
}
|
|
|
|
function recursivelyTraverseLayoutEffects(root, parentFiber, lanes) {
|
|
var prevDebugFiber = getCurrentFiber();
|
|
|
|
if (parentFiber.subtreeFlags & LayoutMask) {
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
setCurrentFiber(child);
|
|
var current = child.alternate;
|
|
commitLayoutEffectOnFiber(root, current, child);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
setCurrentFiber(prevDebugFiber);
|
|
}
|
|
|
|
function disappearLayoutEffects(finishedWork) {
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case MemoComponent:
|
|
case SimpleMemoComponent: {
|
|
// TODO (Offscreen) Check: flags & LayoutStatic
|
|
{
|
|
commitHookEffectListUnmount(Layout, finishedWork, finishedWork.return);
|
|
}
|
|
|
|
recursivelyTraverseDisappearLayoutEffects(finishedWork);
|
|
break;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
// TODO (Offscreen) Check: flags & RefStatic
|
|
safelyDetachRef(finishedWork, finishedWork.return);
|
|
var instance = finishedWork.stateNode;
|
|
|
|
if (typeof instance.componentWillUnmount === "function") {
|
|
safelyCallComponentWillUnmount(
|
|
finishedWork,
|
|
finishedWork.return,
|
|
instance
|
|
);
|
|
}
|
|
|
|
recursivelyTraverseDisappearLayoutEffects(finishedWork);
|
|
break;
|
|
}
|
|
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
// TODO (Offscreen) Check: flags & RefStatic
|
|
safelyDetachRef(finishedWork, finishedWork.return);
|
|
recursivelyTraverseDisappearLayoutEffects(finishedWork);
|
|
break;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
// TODO (Offscreen) Check: flags & RefStatic
|
|
safelyDetachRef(finishedWork, finishedWork.return);
|
|
var isHidden = finishedWork.memoizedState !== null;
|
|
|
|
if (isHidden);
|
|
else {
|
|
recursivelyTraverseDisappearLayoutEffects(finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
recursivelyTraverseDisappearLayoutEffects(finishedWork);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function recursivelyTraverseDisappearLayoutEffects(parentFiber) {
|
|
// TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
disappearLayoutEffects(child);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
function reappearLayoutEffects(
|
|
finishedRoot,
|
|
current,
|
|
finishedWork, // This function visits both newly finished work and nodes that were re-used
|
|
// from a previously committed tree. We cannot check non-static flags if the
|
|
// node was reused.
|
|
includeWorkInProgressEffects
|
|
) {
|
|
// Turn on layout effects in a tree that previously disappeared.
|
|
var flags = finishedWork.flags;
|
|
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
includeWorkInProgressEffects
|
|
); // TODO: Check flags & LayoutStatic
|
|
|
|
commitHookLayoutEffects(finishedWork, Layout);
|
|
break;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
includeWorkInProgressEffects
|
|
); // TODO: Check for LayoutStatic flag
|
|
|
|
var instance = finishedWork.stateNode;
|
|
|
|
if (typeof instance.componentDidMount === "function") {
|
|
try {
|
|
instance.componentDidMount();
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
} // Commit any callbacks that would have fired while the component
|
|
// was hidden.
|
|
|
|
var updateQueue = finishedWork.updateQueue;
|
|
|
|
if (updateQueue !== null) {
|
|
commitHiddenCallbacks(updateQueue, instance);
|
|
} // If this is newly finished work, check for setState callbacks
|
|
|
|
if (includeWorkInProgressEffects && flags & Callback) {
|
|
commitClassCallbacks(finishedWork);
|
|
} // TODO: Check flags & RefStatic
|
|
|
|
safelyAttachRef(finishedWork, finishedWork.return);
|
|
break;
|
|
}
|
|
// Unlike commitLayoutEffectsOnFiber, we don't need to handle HostRoot
|
|
// because this function only visits nodes that are inside an
|
|
// Offscreen fiber.
|
|
// case HostRoot: {
|
|
// ...
|
|
// }
|
|
|
|
case HostResource:
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
includeWorkInProgressEffects
|
|
); // Renderers may schedule work to be done after host components are mounted
|
|
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
|
|
// These effects should only be committed when components are first mounted,
|
|
// aka when there is no current/alternate.
|
|
|
|
if (includeWorkInProgressEffects && current === null && flags & Update) {
|
|
commitHostComponentMount(finishedWork);
|
|
} // TODO: Check flags & Ref
|
|
|
|
safelyAttachRef(finishedWork, finishedWork.return);
|
|
break;
|
|
}
|
|
|
|
case Profiler: {
|
|
recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
includeWorkInProgressEffects
|
|
); // TODO: Figure out how Profiler updates should work with Offscreen
|
|
|
|
break;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
includeWorkInProgressEffects
|
|
); // TODO: Figure out how Suspense hydration callbacks should work
|
|
// with Offscreen.
|
|
|
|
if (includeWorkInProgressEffects && flags & Update) {
|
|
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
var offscreenState = finishedWork.memoizedState;
|
|
var isHidden = offscreenState !== null;
|
|
|
|
if (isHidden);
|
|
else {
|
|
recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
includeWorkInProgressEffects
|
|
);
|
|
} // TODO: Check flags & Ref
|
|
|
|
safelyAttachRef(finishedWork, finishedWork.return);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
includeWorkInProgressEffects
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function recursivelyTraverseReappearLayoutEffects(
|
|
finishedRoot,
|
|
parentFiber,
|
|
includeWorkInProgressEffects
|
|
) {
|
|
// This function visits both newly finished work and nodes that were re-used
|
|
// from a previously committed tree. We cannot check non-static flags if the
|
|
// node was reused.
|
|
var childShouldIncludeWorkInProgressEffects =
|
|
includeWorkInProgressEffects &&
|
|
(parentFiber.subtreeFlags & LayoutMask) !== NoFlags; // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
|
|
|
|
var prevDebugFiber = getCurrentFiber();
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
var current = child.alternate;
|
|
reappearLayoutEffects(
|
|
finishedRoot,
|
|
current,
|
|
child,
|
|
childShouldIncludeWorkInProgressEffects
|
|
);
|
|
child = child.sibling;
|
|
}
|
|
|
|
setCurrentFiber(prevDebugFiber);
|
|
}
|
|
|
|
function commitHookPassiveMountEffects(finishedWork, hookFlags) {
|
|
{
|
|
try {
|
|
commitHookEffectListMount(hookFlags, finishedWork);
|
|
} catch (error) {
|
|
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitOffscreenPassiveMountEffects(current, finishedWork, instance) {
|
|
{
|
|
var previousCache = null;
|
|
|
|
if (
|
|
current !== null &&
|
|
current.memoizedState !== null &&
|
|
current.memoizedState.cachePool !== null
|
|
) {
|
|
previousCache = current.memoizedState.cachePool.pool;
|
|
}
|
|
|
|
var nextCache = null;
|
|
|
|
if (
|
|
finishedWork.memoizedState !== null &&
|
|
finishedWork.memoizedState.cachePool !== null
|
|
) {
|
|
nextCache = finishedWork.memoizedState.cachePool.pool;
|
|
} // Retain/release the cache used for pending (suspended) nodes.
|
|
// Note that this is only reached in the non-suspended/visible case:
|
|
// when the content is suspended/hidden, the retain/release occurs
|
|
// via the parent Suspense component (see case above).
|
|
|
|
if (nextCache !== previousCache) {
|
|
if (nextCache != null) {
|
|
retainCache(nextCache);
|
|
}
|
|
|
|
if (previousCache != null) {
|
|
releaseCache(previousCache);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitCachePassiveMountEffect(current, finishedWork) {
|
|
{
|
|
var previousCache = null;
|
|
|
|
if (finishedWork.alternate !== null) {
|
|
previousCache = finishedWork.alternate.memoizedState.cache;
|
|
}
|
|
|
|
var nextCache = finishedWork.memoizedState.cache; // Retain/release the cache. In theory the cache component
|
|
// could be "borrowing" a cache instance owned by some parent,
|
|
// in which case we could avoid retaining/releasing. But it
|
|
// is non-trivial to determine when that is the case, so we
|
|
// always retain/release.
|
|
|
|
if (nextCache !== previousCache) {
|
|
retainCache(nextCache);
|
|
|
|
if (previousCache != null) {
|
|
releaseCache(previousCache);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitPassiveMountEffects(
|
|
root,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
) {
|
|
setCurrentFiber(finishedWork);
|
|
commitPassiveMountOnFiber(
|
|
root,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
resetCurrentFiber();
|
|
}
|
|
|
|
function recursivelyTraversePassiveMountEffects(
|
|
root,
|
|
parentFiber,
|
|
committedLanes,
|
|
committedTransitions
|
|
) {
|
|
var prevDebugFiber = getCurrentFiber();
|
|
|
|
if (parentFiber.subtreeFlags & PassiveMask) {
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
setCurrentFiber(child);
|
|
commitPassiveMountOnFiber(
|
|
root,
|
|
child,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
setCurrentFiber(prevDebugFiber);
|
|
}
|
|
|
|
function commitPassiveMountOnFiber(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
) {
|
|
// When updating this function, also update reconnectPassiveEffects, which does
|
|
// most of the same things when an offscreen tree goes from hidden -> visible,
|
|
// or when toggling effects inside a hidden tree.
|
|
var flags = finishedWork.flags;
|
|
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
recursivelyTraversePassiveMountEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
|
|
if (flags & Passive) {
|
|
commitHookPassiveMountEffects(finishedWork, Passive$1 | HasEffect);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HostRoot: {
|
|
recursivelyTraversePassiveMountEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
|
|
if (flags & Passive) {
|
|
{
|
|
var previousCache = null;
|
|
|
|
if (finishedWork.alternate !== null) {
|
|
previousCache = finishedWork.alternate.memoizedState.cache;
|
|
}
|
|
|
|
var nextCache = finishedWork.memoizedState.cache; // Retain/release the root cache.
|
|
// Note that on initial mount, previousCache and nextCache will be the same
|
|
// and this retain won't occur. To counter this, we instead retain the HostRoot's
|
|
// initial cache when creating the root itself (see createFiberRoot() in
|
|
// ReactFiberRoot.js). Subsequent updates that change the cache are reflected
|
|
// here, such that previous/next caches are retained correctly.
|
|
|
|
if (nextCache !== previousCache) {
|
|
retainCache(nextCache);
|
|
|
|
if (previousCache != null) {
|
|
releaseCache(previousCache);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case LegacyHiddenComponent: {
|
|
break;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
// TODO: Pass `current` as argument to this function
|
|
var _instance3 = finishedWork.stateNode;
|
|
var nextState = finishedWork.memoizedState;
|
|
var isHidden = nextState !== null;
|
|
|
|
if (isHidden) {
|
|
if (_instance3._visibility & OffscreenPassiveEffectsConnected) {
|
|
// The effects are currently connected. Update them.
|
|
recursivelyTraversePassiveMountEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
} else {
|
|
if (finishedWork.mode & ConcurrentMode) {
|
|
// The effects are currently disconnected. Since the tree is hidden,
|
|
// don't connect them. This also applies to the initial render.
|
|
{
|
|
// "Atomic" effects are ones that need to fire on every commit,
|
|
// even during pre-rendering. An example is updating the reference
|
|
// count on cache instances.
|
|
recursivelyTraverseAtomicPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork
|
|
);
|
|
}
|
|
} else {
|
|
// Legacy Mode: Fire the effects even if the tree is hidden.
|
|
_instance3._visibility |= OffscreenPassiveEffectsConnected;
|
|
recursivelyTraversePassiveMountEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// Tree is visible
|
|
if (_instance3._visibility & OffscreenPassiveEffectsConnected) {
|
|
// The effects are currently connected. Update them.
|
|
recursivelyTraversePassiveMountEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
} else {
|
|
// The effects are currently disconnected. Reconnect them, while also
|
|
// firing effects inside newly mounted trees. This also applies to
|
|
// the initial render.
|
|
_instance3._visibility |= OffscreenPassiveEffectsConnected;
|
|
var includeWorkInProgressEffects =
|
|
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags;
|
|
recursivelyTraverseReconnectPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions,
|
|
includeWorkInProgressEffects
|
|
);
|
|
}
|
|
}
|
|
|
|
if (flags & Passive) {
|
|
var _current = finishedWork.alternate;
|
|
commitOffscreenPassiveMountEffects(_current, finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case CacheComponent: {
|
|
recursivelyTraversePassiveMountEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
|
|
if (flags & Passive) {
|
|
// TODO: Pass `current` as argument to this function
|
|
var _current2 = finishedWork.alternate;
|
|
commitCachePassiveMountEffect(_current2, finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case TracingMarkerComponent:
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default: {
|
|
recursivelyTraversePassiveMountEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function recursivelyTraverseReconnectPassiveEffects(
|
|
finishedRoot,
|
|
parentFiber,
|
|
committedLanes,
|
|
committedTransitions,
|
|
includeWorkInProgressEffects
|
|
) {
|
|
// This function visits both newly finished work and nodes that were re-used
|
|
// from a previously committed tree. We cannot check non-static flags if the
|
|
// node was reused.
|
|
var childShouldIncludeWorkInProgressEffects =
|
|
includeWorkInProgressEffects &&
|
|
(parentFiber.subtreeFlags & PassiveMask) !== NoFlags; // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
|
|
|
|
var prevDebugFiber = getCurrentFiber();
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
reconnectPassiveEffects(
|
|
finishedRoot,
|
|
child,
|
|
committedLanes,
|
|
committedTransitions,
|
|
childShouldIncludeWorkInProgressEffects
|
|
);
|
|
child = child.sibling;
|
|
}
|
|
|
|
setCurrentFiber(prevDebugFiber);
|
|
}
|
|
|
|
function reconnectPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions, // This function visits both newly finished work and nodes that were re-used
|
|
// from a previously committed tree. We cannot check non-static flags if the
|
|
// node was reused.
|
|
includeWorkInProgressEffects
|
|
) {
|
|
var flags = finishedWork.flags;
|
|
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
recursivelyTraverseReconnectPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions,
|
|
includeWorkInProgressEffects
|
|
); // TODO: Check for PassiveStatic flag
|
|
|
|
commitHookPassiveMountEffects(finishedWork, Passive$1);
|
|
break;
|
|
}
|
|
// Unlike commitPassiveMountOnFiber, we don't need to handle HostRoot
|
|
// because this function only visits nodes that are inside an
|
|
// Offscreen fiber.
|
|
// case HostRoot: {
|
|
// ...
|
|
// }
|
|
|
|
case LegacyHiddenComponent: {
|
|
break;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
var _instance4 = finishedWork.stateNode;
|
|
var nextState = finishedWork.memoizedState;
|
|
var isHidden = nextState !== null;
|
|
|
|
if (isHidden) {
|
|
if (_instance4._visibility & OffscreenPassiveEffectsConnected) {
|
|
// The effects are currently connected. Update them.
|
|
recursivelyTraverseReconnectPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions,
|
|
includeWorkInProgressEffects
|
|
);
|
|
} else {
|
|
if (finishedWork.mode & ConcurrentMode) {
|
|
// The effects are currently disconnected. Since the tree is hidden,
|
|
// don't connect them. This also applies to the initial render.
|
|
{
|
|
// "Atomic" effects are ones that need to fire on every commit,
|
|
// even during pre-rendering. An example is updating the reference
|
|
// count on cache instances.
|
|
recursivelyTraverseAtomicPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork
|
|
);
|
|
}
|
|
} else {
|
|
// Legacy Mode: Fire the effects even if the tree is hidden.
|
|
_instance4._visibility |= OffscreenPassiveEffectsConnected;
|
|
recursivelyTraverseReconnectPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions,
|
|
includeWorkInProgressEffects
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// Tree is visible
|
|
// Since we're already inside a reconnecting tree, it doesn't matter
|
|
// whether the effects are currently connected. In either case, we'll
|
|
// continue traversing the tree and firing all the effects.
|
|
//
|
|
// We do need to set the "connected" flag on the instance, though.
|
|
_instance4._visibility |= OffscreenPassiveEffectsConnected;
|
|
recursivelyTraverseReconnectPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions,
|
|
includeWorkInProgressEffects
|
|
);
|
|
}
|
|
|
|
if (includeWorkInProgressEffects && flags & Passive) {
|
|
// TODO: Pass `current` as argument to this function
|
|
var _current3 = finishedWork.alternate;
|
|
commitOffscreenPassiveMountEffects(_current3, finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case CacheComponent: {
|
|
recursivelyTraverseReconnectPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions,
|
|
includeWorkInProgressEffects
|
|
);
|
|
|
|
if (includeWorkInProgressEffects && flags & Passive) {
|
|
// TODO: Pass `current` as argument to this function
|
|
var _current4 = finishedWork.alternate;
|
|
commitCachePassiveMountEffect(_current4, finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case TracingMarkerComponent:
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default: {
|
|
recursivelyTraverseReconnectPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions,
|
|
includeWorkInProgressEffects
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function recursivelyTraverseAtomicPassiveEffects(
|
|
finishedRoot,
|
|
parentFiber,
|
|
committedLanes,
|
|
committedTransitions
|
|
) {
|
|
// "Atomic" effects are ones that need to fire on every commit, even during
|
|
// pre-rendering. We call this function when traversing a hidden tree whose
|
|
// regular effects are currently disconnected.
|
|
var prevDebugFiber = getCurrentFiber(); // TODO: Add special flag for atomic effects
|
|
|
|
if (parentFiber.subtreeFlags & PassiveMask) {
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
setCurrentFiber(child);
|
|
commitAtomicPassiveEffects(finishedRoot, child);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
setCurrentFiber(prevDebugFiber);
|
|
}
|
|
|
|
function commitAtomicPassiveEffects(
|
|
finishedRoot,
|
|
finishedWork,
|
|
committedLanes,
|
|
committedTransitions
|
|
) {
|
|
// "Atomic" effects are ones that need to fire on every commit, even during
|
|
// pre-rendering. We call this function when traversing a hidden tree whose
|
|
// regular effects are currently disconnected.
|
|
var flags = finishedWork.flags;
|
|
|
|
switch (finishedWork.tag) {
|
|
case OffscreenComponent: {
|
|
recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
|
|
|
|
if (flags & Passive) {
|
|
// TODO: Pass `current` as argument to this function
|
|
var current = finishedWork.alternate;
|
|
var instance = finishedWork.stateNode;
|
|
commitOffscreenPassiveMountEffects(current, finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case CacheComponent: {
|
|
recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
|
|
|
|
if (flags & Passive) {
|
|
// TODO: Pass `current` as argument to this function
|
|
var _current5 = finishedWork.alternate;
|
|
commitCachePassiveMountEffect(_current5, finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
// eslint-disable-next-line-no-fallthrough
|
|
|
|
default: {
|
|
recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitPassiveUnmountEffects(finishedWork) {
|
|
setCurrentFiber(finishedWork);
|
|
commitPassiveUnmountOnFiber(finishedWork);
|
|
resetCurrentFiber();
|
|
}
|
|
|
|
function detachAlternateSiblings(parentFiber) {
|
|
{
|
|
// A fiber was deleted from this parent fiber, but it's still part of the
|
|
// previous (alternate) parent fiber's list of children. Because children
|
|
// are a linked list, an earlier sibling that's still alive will be
|
|
// connected to the deleted fiber via its `alternate`:
|
|
//
|
|
// live fiber --alternate--> previous live fiber --sibling--> deleted
|
|
// fiber
|
|
//
|
|
// We can't disconnect `alternate` on nodes that haven't been deleted yet,
|
|
// but we can disconnect the `sibling` and `child` pointers.
|
|
var previousFiber = parentFiber.alternate;
|
|
|
|
if (previousFiber !== null) {
|
|
var detachedChild = previousFiber.child;
|
|
|
|
if (detachedChild !== null) {
|
|
previousFiber.child = null;
|
|
|
|
do {
|
|
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
var detachedSibling = detachedChild.sibling; // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
detachedChild.sibling = null;
|
|
detachedChild = detachedSibling;
|
|
} while (detachedChild !== null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitHookPassiveUnmountEffects(
|
|
finishedWork,
|
|
nearestMountedAncestor,
|
|
hookFlags
|
|
) {
|
|
{
|
|
commitHookEffectListUnmount(
|
|
hookFlags,
|
|
finishedWork,
|
|
nearestMountedAncestor
|
|
);
|
|
}
|
|
}
|
|
|
|
function recursivelyTraversePassiveUnmountEffects(parentFiber) {
|
|
// Deletions effects can be scheduled on any fiber type. They need to happen
|
|
// before the children effects have fired.
|
|
var deletions = parentFiber.deletions;
|
|
|
|
if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
|
|
if (deletions !== null) {
|
|
for (var i = 0; i < deletions.length; i++) {
|
|
var childToDelete = deletions[i]; // TODO: Convert this to use recursion
|
|
|
|
nextEffect = childToDelete;
|
|
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
|
|
childToDelete,
|
|
parentFiber
|
|
);
|
|
}
|
|
}
|
|
|
|
detachAlternateSiblings(parentFiber);
|
|
}
|
|
|
|
var prevDebugFiber = getCurrentFiber(); // TODO: Split PassiveMask into separate masks for mount and unmount?
|
|
|
|
if (parentFiber.subtreeFlags & PassiveMask) {
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
setCurrentFiber(child);
|
|
commitPassiveUnmountOnFiber(child);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
setCurrentFiber(prevDebugFiber);
|
|
}
|
|
|
|
function commitPassiveUnmountOnFiber(finishedWork) {
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
recursivelyTraversePassiveUnmountEffects(finishedWork);
|
|
|
|
if (finishedWork.flags & Passive) {
|
|
commitHookPassiveUnmountEffects(
|
|
finishedWork,
|
|
finishedWork.return,
|
|
Passive$1 | HasEffect
|
|
);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
var instance = finishedWork.stateNode;
|
|
var nextState = finishedWork.memoizedState;
|
|
var isHidden = nextState !== null;
|
|
|
|
if (
|
|
isHidden &&
|
|
instance._visibility & OffscreenPassiveEffectsConnected && // For backwards compatibility, don't unmount when a tree suspends. In
|
|
// the future we may change this to unmount after a delay.
|
|
(finishedWork.return === null ||
|
|
finishedWork.return.tag !== SuspenseComponent)
|
|
) {
|
|
// The effects are currently connected. Disconnect them.
|
|
// TODO: Add option or heuristic to delay before disconnecting the
|
|
// effects. Then if the tree reappears before the delay has elapsed, we
|
|
// can skip toggling the effects entirely.
|
|
instance._visibility &= ~OffscreenPassiveEffectsConnected;
|
|
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
|
|
} else {
|
|
recursivelyTraversePassiveUnmountEffects(finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
recursivelyTraversePassiveUnmountEffects(finishedWork);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function recursivelyTraverseDisconnectPassiveEffects(parentFiber) {
|
|
// Deletions effects can be scheduled on any fiber type. They need to happen
|
|
// before the children effects have fired.
|
|
var deletions = parentFiber.deletions;
|
|
|
|
if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
|
|
if (deletions !== null) {
|
|
for (var i = 0; i < deletions.length; i++) {
|
|
var childToDelete = deletions[i]; // TODO: Convert this to use recursion
|
|
|
|
nextEffect = childToDelete;
|
|
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
|
|
childToDelete,
|
|
parentFiber
|
|
);
|
|
}
|
|
}
|
|
|
|
detachAlternateSiblings(parentFiber);
|
|
}
|
|
|
|
var prevDebugFiber = getCurrentFiber(); // TODO: Check PassiveStatic flag
|
|
|
|
var child = parentFiber.child;
|
|
|
|
while (child !== null) {
|
|
setCurrentFiber(child);
|
|
disconnectPassiveEffect(child);
|
|
child = child.sibling;
|
|
}
|
|
|
|
setCurrentFiber(prevDebugFiber);
|
|
}
|
|
|
|
function disconnectPassiveEffect(finishedWork) {
|
|
switch (finishedWork.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
// TODO: Check PassiveStatic flag
|
|
commitHookPassiveUnmountEffects(
|
|
finishedWork,
|
|
finishedWork.return,
|
|
Passive$1
|
|
); // When disconnecting passive effects, we fire the effects in the same
|
|
// order as during a deletiong: parent before child
|
|
|
|
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
|
|
break;
|
|
}
|
|
|
|
case OffscreenComponent: {
|
|
var instance = finishedWork.stateNode;
|
|
|
|
if (instance._visibility & OffscreenPassiveEffectsConnected) {
|
|
instance._visibility &= ~OffscreenPassiveEffectsConnected;
|
|
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
|
|
deletedSubtreeRoot,
|
|
nearestMountedAncestor
|
|
) {
|
|
while (nextEffect !== null) {
|
|
var fiber = nextEffect; // Deletion effects fire in parent -> child order
|
|
// TODO: Check if fiber has a PassiveStatic flag
|
|
|
|
setCurrentFiber(fiber);
|
|
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
|
|
resetCurrentFiber();
|
|
var child = fiber.child; // TODO: Only traverse subtree if it has a PassiveStatic flag. (But, if we
|
|
// do this, still need to handle `deletedTreeCleanUpLevel` correctly.)
|
|
|
|
if (child !== null) {
|
|
child.return = fiber;
|
|
nextEffect = child;
|
|
} else {
|
|
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
|
|
deletedSubtreeRoot
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
|
|
deletedSubtreeRoot
|
|
) {
|
|
while (nextEffect !== null) {
|
|
var fiber = nextEffect;
|
|
var sibling = fiber.sibling;
|
|
var returnFiber = fiber.return;
|
|
|
|
{
|
|
// Recursively traverse the entire deleted tree and clean up fiber fields.
|
|
// This is more aggressive than ideal, and the long term goal is to only
|
|
// have to detach the deleted tree at the root.
|
|
detachFiberAfterEffects(fiber);
|
|
|
|
if (fiber === deletedSubtreeRoot) {
|
|
nextEffect = null;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (sibling !== null) {
|
|
sibling.return = returnFiber;
|
|
nextEffect = sibling;
|
|
return;
|
|
}
|
|
|
|
nextEffect = returnFiber;
|
|
}
|
|
}
|
|
|
|
function commitPassiveUnmountInsideDeletedTreeOnFiber(
|
|
current,
|
|
nearestMountedAncestor
|
|
) {
|
|
switch (current.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
commitHookPassiveUnmountEffects(
|
|
current,
|
|
nearestMountedAncestor,
|
|
Passive$1
|
|
);
|
|
break;
|
|
}
|
|
// TODO: run passive unmount effects when unmounting a root.
|
|
// Because passive unmount effects are not currently run,
|
|
// the cache instance owned by the root will never be freed.
|
|
// When effects are run, the cache should be freed here:
|
|
// case HostRoot: {
|
|
// if (enableCache) {
|
|
// const cache = current.memoizedState.cache;
|
|
// releaseCache(cache);
|
|
// }
|
|
// break;
|
|
// }
|
|
|
|
case LegacyHiddenComponent:
|
|
case OffscreenComponent: {
|
|
{
|
|
if (
|
|
current.memoizedState !== null &&
|
|
current.memoizedState.cachePool !== null
|
|
) {
|
|
var cache = current.memoizedState.cachePool.pool; // Retain/release the cache used for pending (suspended) nodes.
|
|
// Note that this is only reached in the non-suspended/visible case:
|
|
// when the content is suspended/hidden, the retain/release occurs
|
|
// via the parent Suspense component (see case above).
|
|
|
|
if (cache != null) {
|
|
retainCache(cache);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
break;
|
|
}
|
|
|
|
case CacheComponent: {
|
|
{
|
|
var _cache = current.memoizedState.cache;
|
|
releaseCache(_cache);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function invokeLayoutEffectMountInDEV(fiber) {
|
|
{
|
|
// We don't need to re-check StrictEffectsMode here.
|
|
// This function is only called if that check has already passed.
|
|
switch (fiber.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
try {
|
|
commitHookEffectListMount(Layout | HasEffect, fiber);
|
|
} catch (error) {
|
|
captureCommitPhaseError(fiber, fiber.return, error);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
var instance = fiber.stateNode;
|
|
|
|
try {
|
|
instance.componentDidMount();
|
|
} catch (error) {
|
|
captureCommitPhaseError(fiber, fiber.return, error);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function invokePassiveEffectMountInDEV(fiber) {
|
|
{
|
|
// We don't need to re-check StrictEffectsMode here.
|
|
// This function is only called if that check has already passed.
|
|
switch (fiber.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
try {
|
|
commitHookEffectListMount(Passive$1 | HasEffect, fiber);
|
|
} catch (error) {
|
|
captureCommitPhaseError(fiber, fiber.return, error);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function invokeLayoutEffectUnmountInDEV(fiber) {
|
|
{
|
|
// We don't need to re-check StrictEffectsMode here.
|
|
// This function is only called if that check has already passed.
|
|
switch (fiber.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
try {
|
|
commitHookEffectListUnmount(Layout | HasEffect, fiber, fiber.return);
|
|
} catch (error) {
|
|
captureCommitPhaseError(fiber, fiber.return, error);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
var instance = fiber.stateNode;
|
|
|
|
if (typeof instance.componentWillUnmount === "function") {
|
|
safelyCallComponentWillUnmount(fiber, fiber.return, instance);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function invokePassiveEffectUnmountInDEV(fiber) {
|
|
{
|
|
// We don't need to re-check StrictEffectsMode here.
|
|
// This function is only called if that check has already passed.
|
|
switch (fiber.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
try {
|
|
commitHookEffectListUnmount(
|
|
Passive$1 | HasEffect,
|
|
fiber,
|
|
fiber.return
|
|
);
|
|
} catch (error) {
|
|
captureCommitPhaseError(fiber, fiber.return, error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getCacheSignal() {
|
|
var cache = readContext(CacheContext);
|
|
return cache.controller.signal;
|
|
}
|
|
|
|
function getCacheForType(resourceType) {
|
|
var cache = readContext(CacheContext);
|
|
var cacheForType = cache.data.get(resourceType);
|
|
|
|
if (cacheForType === undefined) {
|
|
cacheForType = resourceType();
|
|
cache.data.set(resourceType, cacheForType);
|
|
}
|
|
|
|
return cacheForType;
|
|
}
|
|
|
|
var DefaultCacheDispatcher = {
|
|
getCacheSignal: getCacheSignal,
|
|
getCacheForType: getCacheForType
|
|
};
|
|
|
|
var COMPONENT_TYPE = 0;
|
|
var HAS_PSEUDO_CLASS_TYPE = 1;
|
|
var ROLE_TYPE = 2;
|
|
var TEST_NAME_TYPE = 3;
|
|
var TEXT_TYPE = 4;
|
|
|
|
if (typeof Symbol === "function" && Symbol.for) {
|
|
var symbolFor = Symbol.for;
|
|
COMPONENT_TYPE = symbolFor("selector.component");
|
|
HAS_PSEUDO_CLASS_TYPE = symbolFor("selector.has_pseudo_class");
|
|
ROLE_TYPE = symbolFor("selector.role");
|
|
TEST_NAME_TYPE = symbolFor("selector.test_id");
|
|
TEXT_TYPE = symbolFor("selector.text");
|
|
}
|
|
|
|
function createComponentSelector(component) {
|
|
return {
|
|
$$typeof: COMPONENT_TYPE,
|
|
value: component
|
|
};
|
|
}
|
|
function createHasPseudoClassSelector(selectors) {
|
|
return {
|
|
$$typeof: HAS_PSEUDO_CLASS_TYPE,
|
|
value: selectors
|
|
};
|
|
}
|
|
function createRoleSelector(role) {
|
|
return {
|
|
$$typeof: ROLE_TYPE,
|
|
value: role
|
|
};
|
|
}
|
|
function createTextSelector(text) {
|
|
return {
|
|
$$typeof: TEXT_TYPE,
|
|
value: text
|
|
};
|
|
}
|
|
function createTestNameSelector(id) {
|
|
return {
|
|
$$typeof: TEST_NAME_TYPE,
|
|
value: id
|
|
};
|
|
}
|
|
|
|
function findFiberRootForHostRoot(hostRoot) {
|
|
var maybeFiber = getInstanceFromNode(hostRoot);
|
|
|
|
if (maybeFiber != null) {
|
|
if (typeof maybeFiber.memoizedProps["data-testname"] !== "string") {
|
|
throw new Error(
|
|
"Invalid host root specified. Should be either a React container or a node with a testname attribute."
|
|
);
|
|
}
|
|
|
|
return maybeFiber;
|
|
} else {
|
|
var fiberRoot = findFiberRoot(hostRoot);
|
|
|
|
if (fiberRoot === null) {
|
|
throw new Error(
|
|
"Could not find React container within specified host subtree."
|
|
);
|
|
} // The Flow type for FiberRoot is a little funky.
|
|
// createFiberRoot() cheats this by treating the root as :any and adding stateNode lazily.
|
|
|
|
return fiberRoot.stateNode.current;
|
|
}
|
|
}
|
|
|
|
function matchSelector(fiber, selector) {
|
|
var tag = fiber.tag;
|
|
|
|
switch (selector.$$typeof) {
|
|
case COMPONENT_TYPE:
|
|
if (fiber.type === selector.value) {
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
|
|
case HAS_PSEUDO_CLASS_TYPE:
|
|
return hasMatchingPaths(fiber, selector.value);
|
|
|
|
case ROLE_TYPE:
|
|
if (
|
|
tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton
|
|
) {
|
|
var node = fiber.stateNode;
|
|
|
|
if (matchAccessibilityRole(node, selector.value)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case TEXT_TYPE:
|
|
if (
|
|
tag === HostComponent ||
|
|
tag === HostText ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton
|
|
) {
|
|
var textContent = getTextContent(fiber);
|
|
|
|
if (textContent !== null && textContent.indexOf(selector.value) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case TEST_NAME_TYPE:
|
|
if (
|
|
tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton
|
|
) {
|
|
var dataTestID = fiber.memoizedProps["data-testname"];
|
|
|
|
if (
|
|
typeof dataTestID === "string" &&
|
|
dataTestID.toLowerCase() === selector.value.toLowerCase()
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
throw new Error("Invalid selector type specified.");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function selectorToString(selector) {
|
|
switch (selector.$$typeof) {
|
|
case COMPONENT_TYPE:
|
|
var displayName = getComponentNameFromType(selector.value) || "Unknown";
|
|
return "<" + displayName + ">";
|
|
|
|
case HAS_PSEUDO_CLASS_TYPE:
|
|
return ":has(" + (selectorToString(selector) || "") + ")";
|
|
|
|
case ROLE_TYPE:
|
|
return '[role="' + selector.value + '"]';
|
|
|
|
case TEXT_TYPE:
|
|
return '"' + selector.value + '"';
|
|
|
|
case TEST_NAME_TYPE:
|
|
return '[data-testname="' + selector.value + '"]';
|
|
|
|
default:
|
|
throw new Error("Invalid selector type specified.");
|
|
}
|
|
}
|
|
|
|
function findPaths(root, selectors) {
|
|
var matchingFibers = [];
|
|
var stack = [root, 0];
|
|
var index = 0;
|
|
|
|
while (index < stack.length) {
|
|
var fiber = stack[index++];
|
|
var tag = fiber.tag;
|
|
var selectorIndex = stack[index++];
|
|
var selector = selectors[selectorIndex];
|
|
|
|
if (
|
|
(tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton) &&
|
|
isHiddenSubtree(fiber)
|
|
) {
|
|
continue;
|
|
} else {
|
|
while (selector != null && matchSelector(fiber, selector)) {
|
|
selectorIndex++;
|
|
selector = selectors[selectorIndex];
|
|
}
|
|
}
|
|
|
|
if (selectorIndex === selectors.length) {
|
|
matchingFibers.push(fiber);
|
|
} else {
|
|
var child = fiber.child;
|
|
|
|
while (child !== null) {
|
|
stack.push(child, selectorIndex);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
return matchingFibers;
|
|
} // Same as findPaths but with eager bailout on first match
|
|
|
|
function hasMatchingPaths(root, selectors) {
|
|
var stack = [root, 0];
|
|
var index = 0;
|
|
|
|
while (index < stack.length) {
|
|
var fiber = stack[index++];
|
|
var tag = fiber.tag;
|
|
var selectorIndex = stack[index++];
|
|
var selector = selectors[selectorIndex];
|
|
|
|
if (
|
|
(tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton) &&
|
|
isHiddenSubtree(fiber)
|
|
) {
|
|
continue;
|
|
} else {
|
|
while (selector != null && matchSelector(fiber, selector)) {
|
|
selectorIndex++;
|
|
selector = selectors[selectorIndex];
|
|
}
|
|
}
|
|
|
|
if (selectorIndex === selectors.length) {
|
|
return true;
|
|
} else {
|
|
var child = fiber.child;
|
|
|
|
while (child !== null) {
|
|
stack.push(child, selectorIndex);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function findAllNodes(hostRoot, selectors) {
|
|
var root = findFiberRootForHostRoot(hostRoot);
|
|
var matchingFibers = findPaths(root, selectors);
|
|
var instanceRoots = [];
|
|
var stack = Array.from(matchingFibers);
|
|
var index = 0;
|
|
|
|
while (index < stack.length) {
|
|
var node = stack[index++];
|
|
var tag = node.tag;
|
|
|
|
if (
|
|
tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton
|
|
) {
|
|
if (isHiddenSubtree(node)) {
|
|
continue;
|
|
}
|
|
|
|
instanceRoots.push(node.stateNode);
|
|
} else {
|
|
var child = node.child;
|
|
|
|
while (child !== null) {
|
|
stack.push(child);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
return instanceRoots;
|
|
}
|
|
function getFindAllNodesFailureDescription(hostRoot, selectors) {
|
|
var root = findFiberRootForHostRoot(hostRoot);
|
|
var maxSelectorIndex = 0;
|
|
var matchedNames = []; // The logic of this loop should be kept in sync with findPaths()
|
|
|
|
var stack = [root, 0];
|
|
var index = 0;
|
|
|
|
while (index < stack.length) {
|
|
var fiber = stack[index++];
|
|
var tag = fiber.tag;
|
|
var selectorIndex = stack[index++];
|
|
var selector = selectors[selectorIndex];
|
|
|
|
if (
|
|
(tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton) &&
|
|
isHiddenSubtree(fiber)
|
|
) {
|
|
continue;
|
|
} else if (matchSelector(fiber, selector)) {
|
|
matchedNames.push(selectorToString(selector));
|
|
selectorIndex++;
|
|
|
|
if (selectorIndex > maxSelectorIndex) {
|
|
maxSelectorIndex = selectorIndex;
|
|
}
|
|
}
|
|
|
|
if (selectorIndex < selectors.length) {
|
|
var child = fiber.child;
|
|
|
|
while (child !== null) {
|
|
stack.push(child, selectorIndex);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (maxSelectorIndex < selectors.length) {
|
|
var unmatchedNames = [];
|
|
|
|
for (var i = maxSelectorIndex; i < selectors.length; i++) {
|
|
unmatchedNames.push(selectorToString(selectors[i]));
|
|
}
|
|
|
|
return (
|
|
"findAllNodes was able to match part of the selector:\n" +
|
|
(" " + matchedNames.join(" > ") + "\n\n") +
|
|
"No matching component was found for:\n" +
|
|
(" " + unmatchedNames.join(" > "))
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
function findBoundingRects(hostRoot, selectors) {
|
|
var instanceRoots = findAllNodes(hostRoot, selectors);
|
|
var boundingRects = [];
|
|
|
|
for (var i = 0; i < instanceRoots.length; i++) {
|
|
boundingRects.push(getBoundingRect(instanceRoots[i]));
|
|
}
|
|
|
|
for (var _i = boundingRects.length - 1; _i > 0; _i--) {
|
|
var targetRect = boundingRects[_i];
|
|
var targetLeft = targetRect.x;
|
|
var targetRight = targetLeft + targetRect.width;
|
|
var targetTop = targetRect.y;
|
|
var targetBottom = targetTop + targetRect.height;
|
|
|
|
for (var j = _i - 1; j >= 0; j--) {
|
|
if (_i !== j) {
|
|
var otherRect = boundingRects[j];
|
|
var otherLeft = otherRect.x;
|
|
var otherRight = otherLeft + otherRect.width;
|
|
var otherTop = otherRect.y;
|
|
var otherBottom = otherTop + otherRect.height; // Merging all rects to the minimums set would be complicated,
|
|
// but we can handle the most common cases:
|
|
// 1. completely overlapping rects
|
|
// 2. adjacent rects that are the same width or height (e.g. items in a list)
|
|
//
|
|
// Even given the above constraints,
|
|
// we still won't end up with the fewest possible rects without doing multiple passes,
|
|
// but it's good enough for this purpose.
|
|
|
|
if (
|
|
targetLeft >= otherLeft &&
|
|
targetTop >= otherTop &&
|
|
targetRight <= otherRight &&
|
|
targetBottom <= otherBottom
|
|
) {
|
|
// Complete overlapping rects; remove the inner one.
|
|
boundingRects.splice(_i, 1);
|
|
break;
|
|
} else if (
|
|
targetLeft === otherLeft &&
|
|
targetRect.width === otherRect.width &&
|
|
!(otherBottom < targetTop) &&
|
|
!(otherTop > targetBottom)
|
|
) {
|
|
// Adjacent vertical rects; merge them.
|
|
if (otherTop > targetTop) {
|
|
otherRect.height += otherTop - targetTop;
|
|
otherRect.y = targetTop;
|
|
}
|
|
|
|
if (otherBottom < targetBottom) {
|
|
otherRect.height = targetBottom - otherTop;
|
|
}
|
|
|
|
boundingRects.splice(_i, 1);
|
|
break;
|
|
} else if (
|
|
targetTop === otherTop &&
|
|
targetRect.height === otherRect.height &&
|
|
!(otherRight < targetLeft) &&
|
|
!(otherLeft > targetRight)
|
|
) {
|
|
// Adjacent horizontal rects; merge them.
|
|
if (otherLeft > targetLeft) {
|
|
otherRect.width += otherLeft - targetLeft;
|
|
otherRect.x = targetLeft;
|
|
}
|
|
|
|
if (otherRight < targetRight) {
|
|
otherRect.width = targetRight - otherLeft;
|
|
}
|
|
|
|
boundingRects.splice(_i, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return boundingRects;
|
|
}
|
|
function focusWithin(hostRoot, selectors) {
|
|
var root = findFiberRootForHostRoot(hostRoot);
|
|
var matchingFibers = findPaths(root, selectors);
|
|
var stack = Array.from(matchingFibers);
|
|
var index = 0;
|
|
|
|
while (index < stack.length) {
|
|
var fiber = stack[index++];
|
|
var tag = fiber.tag;
|
|
|
|
if (isHiddenSubtree(fiber)) {
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
tag === HostComponent ||
|
|
tag === HostResource ||
|
|
tag === HostSingleton
|
|
) {
|
|
var node = fiber.stateNode;
|
|
|
|
if (setFocusIfFocusable(node)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
var child = fiber.child;
|
|
|
|
while (child !== null) {
|
|
stack.push(child);
|
|
child = child.sibling;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
var commitHooks = [];
|
|
function onCommitRoot$1() {
|
|
{
|
|
commitHooks.forEach(function(commitHook) {
|
|
return commitHook();
|
|
});
|
|
}
|
|
}
|
|
function observeVisibleRects(hostRoot, selectors, callback, options) {
|
|
var instanceRoots = findAllNodes(hostRoot, selectors);
|
|
|
|
var _setupIntersectionObs = setupIntersectionObserver(
|
|
instanceRoots,
|
|
callback,
|
|
options
|
|
),
|
|
disconnect = _setupIntersectionObs.disconnect,
|
|
observe = _setupIntersectionObs.observe,
|
|
unobserve = _setupIntersectionObs.unobserve; // When React mutates the host environment, we may need to change what we're listening to.
|
|
|
|
var commitHook = function() {
|
|
var nextInstanceRoots = findAllNodes(hostRoot, selectors);
|
|
instanceRoots.forEach(function(target) {
|
|
if (nextInstanceRoots.indexOf(target) < 0) {
|
|
unobserve(target);
|
|
}
|
|
});
|
|
nextInstanceRoots.forEach(function(target) {
|
|
if (instanceRoots.indexOf(target) < 0) {
|
|
observe(target);
|
|
}
|
|
});
|
|
};
|
|
|
|
commitHooks.push(commitHook);
|
|
return {
|
|
disconnect: function() {
|
|
// Stop listening for React mutations:
|
|
var index = commitHooks.indexOf(commitHook);
|
|
|
|
if (index >= 0) {
|
|
commitHooks.splice(index, 1);
|
|
} // Disconnect the host observer:
|
|
|
|
disconnect();
|
|
}
|
|
};
|
|
}
|
|
|
|
var ReactCurrentActQueue$1 = ReactSharedInternals.ReactCurrentActQueue;
|
|
function isLegacyActEnvironment(fiber) {
|
|
{
|
|
// Legacy mode. We preserve the behavior of React 17's act. It assumes an
|
|
// act environment whenever `jest` is defined, but you can still turn off
|
|
// spurious warnings by setting IS_REACT_ACT_ENVIRONMENT explicitly
|
|
// to false.
|
|
var isReactActEnvironmentGlobal = // $FlowFixMe[cannot-resolve-name] Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global
|
|
typeof IS_REACT_ACT_ENVIRONMENT !== "undefined" // $FlowFixMe[cannot-resolve-name]
|
|
? IS_REACT_ACT_ENVIRONMENT
|
|
: undefined; // $FlowFixMe - Flow doesn't know about jest
|
|
|
|
var jestIsDefined = typeof jest !== "undefined";
|
|
return jestIsDefined && isReactActEnvironmentGlobal !== false;
|
|
}
|
|
}
|
|
function isConcurrentActEnvironment() {
|
|
{
|
|
var isReactActEnvironmentGlobal = // $FlowFixMe[cannot-resolve-name] Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global
|
|
typeof IS_REACT_ACT_ENVIRONMENT !== "undefined" // $FlowFixMe[cannot-resolve-name]
|
|
? IS_REACT_ACT_ENVIRONMENT
|
|
: undefined;
|
|
|
|
if (
|
|
!isReactActEnvironmentGlobal &&
|
|
ReactCurrentActQueue$1.current !== null
|
|
) {
|
|
// TODO: Include link to relevant documentation page.
|
|
error(
|
|
"The current testing environment is not configured to support " +
|
|
"act(...)"
|
|
);
|
|
}
|
|
|
|
return isReactActEnvironmentGlobal;
|
|
}
|
|
}
|
|
|
|
var ceil = Math.ceil;
|
|
var PossiblyWeakMap$1 = typeof WeakMap === "function" ? WeakMap : Map;
|
|
var ReactCurrentDispatcher$2 = ReactSharedInternals.ReactCurrentDispatcher,
|
|
ReactCurrentCache = ReactSharedInternals.ReactCurrentCache,
|
|
ReactCurrentOwner$2 = ReactSharedInternals.ReactCurrentOwner,
|
|
ReactCurrentBatchConfig$3 = ReactSharedInternals.ReactCurrentBatchConfig,
|
|
ReactCurrentActQueue$2 = ReactSharedInternals.ReactCurrentActQueue;
|
|
var NoContext =
|
|
/* */
|
|
0;
|
|
var BatchedContext =
|
|
/* */
|
|
1;
|
|
var RenderContext =
|
|
/* */
|
|
2;
|
|
var CommitContext =
|
|
/* */
|
|
4;
|
|
var RootInProgress = 0;
|
|
var RootFatalErrored = 1;
|
|
var RootErrored = 2;
|
|
var RootSuspended = 3;
|
|
var RootSuspendedWithDelay = 4;
|
|
var RootCompleted = 5;
|
|
var RootDidNotComplete = 6; // Describes where we are in the React execution stack
|
|
|
|
var executionContext = NoContext; // The root we're working on
|
|
|
|
var workInProgressRoot = null; // The fiber we're working on
|
|
|
|
var workInProgress = null; // The lanes we're rendering
|
|
|
|
var workInProgressRootRenderLanes = NoLanes;
|
|
var NotSuspended = 0;
|
|
var SuspendedOnError = 1;
|
|
var SuspendedOnData = 2;
|
|
var SuspendedOnImmediate = 3;
|
|
var SuspendedOnDeprecatedThrowPromise = 4;
|
|
var SuspendedAndReadyToUnwind = 5;
|
|
var SuspendedOnHydration = 6; // When this is true, the work-in-progress fiber just suspended (or errored) and
|
|
// we've yet to unwind the stack. In some cases, we may yield to the main thread
|
|
// after this happens. If the fiber is pinged before we resume, we can retry
|
|
// immediately instead of unwinding the stack.
|
|
|
|
var workInProgressSuspendedReason = NotSuspended;
|
|
var workInProgressThrownValue = null; // Whether a ping listener was attached during this render. This is slightly
|
|
// different that whether something suspended, because we don't add multiple
|
|
// listeners to a promise we've already seen (per root and lane).
|
|
|
|
var workInProgressRootDidAttachPingListener = false; // A contextual version of workInProgressRootRenderLanes. It is a superset of
|
|
// the lanes that we started working on at the root. When we enter a subtree
|
|
// that is currently hidden, we add the lanes that would have committed if
|
|
// the hidden tree hadn't been deferred. This is modified by the
|
|
// HiddenContext module.
|
|
//
|
|
// Most things in the work loop should deal with workInProgressRootRenderLanes.
|
|
// Most things in begin/complete phases should deal with renderLanes.
|
|
|
|
var renderLanes$1 = NoLanes; // Whether to root completed, errored, suspended, etc.
|
|
|
|
var workInProgressRootExitStatus = RootInProgress; // A fatal error, if one is thrown
|
|
|
|
var workInProgressRootFatalError = null; // The work left over by components that were visited during this render. Only
|
|
// includes unprocessed updates, not work in bailed out children.
|
|
|
|
var workInProgressRootSkippedLanes = NoLanes; // Lanes that were updated (in an interleaved event) during this render.
|
|
|
|
var workInProgressRootInterleavedUpdatedLanes = NoLanes; // Lanes that were updated during the render phase (*not* an interleaved event).
|
|
|
|
var workInProgressRootPingedLanes = NoLanes; // Errors that are thrown during the render phase.
|
|
|
|
var workInProgressRootConcurrentErrors = null; // These are errors that we recovered from without surfacing them to the UI.
|
|
// We will log them once the tree commits.
|
|
|
|
var workInProgressRootRecoverableErrors = null; // The most recent time we committed a fallback. This lets us ensure a train
|
|
// model where we don't commit new loading states in too quick succession.
|
|
|
|
var globalMostRecentFallbackTime = 0;
|
|
var FALLBACK_THROTTLE_MS = 500; // The absolute time for when we should start giving up on rendering
|
|
// more and prefer CPU suspense heuristics instead.
|
|
|
|
var workInProgressRootRenderTargetTime = Infinity; // How long a render is supposed to take before we start following CPU
|
|
// suspense heuristics and opt out of rendering more content.
|
|
|
|
var RENDER_TIMEOUT_MS = 500;
|
|
var workInProgressTransitions = null;
|
|
|
|
function resetRenderTimer() {
|
|
workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS;
|
|
}
|
|
|
|
function getRenderTargetTime() {
|
|
return workInProgressRootRenderTargetTime;
|
|
}
|
|
var hasUncaughtError = false;
|
|
var firstUncaughtError = null;
|
|
var legacyErrorBoundariesThatAlreadyFailed = null; // Only used when enableProfilerNestedUpdateScheduledHook is true;
|
|
var rootDoesHavePassiveEffects = false;
|
|
var rootWithPendingPassiveEffects = null;
|
|
var pendingPassiveEffectsLanes = NoLanes;
|
|
var pendingPassiveEffectsRemainingLanes = NoLanes;
|
|
var pendingPassiveTransitions = null; // Use these to prevent an infinite loop of nested updates
|
|
|
|
var NESTED_UPDATE_LIMIT = 50;
|
|
var nestedUpdateCount = 0;
|
|
var rootWithNestedUpdates = null;
|
|
var isFlushingPassiveEffects = false;
|
|
var didScheduleUpdateDuringPassiveEffects = false;
|
|
var NESTED_PASSIVE_UPDATE_LIMIT = 50;
|
|
var nestedPassiveUpdateCount = 0;
|
|
var rootWithPassiveNestedUpdates = null; // If two updates are scheduled within the same event, we should treat their
|
|
// event times as simultaneous, even if the actual clock time has advanced
|
|
// between the first and second call.
|
|
|
|
var currentEventTime = NoTimestamp;
|
|
var currentEventTransitionLane = NoLanes;
|
|
var isRunningInsertionEffect = false;
|
|
function getWorkInProgressRoot() {
|
|
return workInProgressRoot;
|
|
}
|
|
function getWorkInProgressRootRenderLanes() {
|
|
return workInProgressRootRenderLanes;
|
|
}
|
|
function requestEventTime() {
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
|
|
// We're inside React, so it's fine to read the actual time.
|
|
return now();
|
|
} // We're not inside React, so we may be in the middle of a browser event.
|
|
|
|
if (currentEventTime !== NoTimestamp) {
|
|
// Use the same start time for all updates until we enter React again.
|
|
return currentEventTime;
|
|
} // This is the first update since React yielded. Compute a new start time.
|
|
|
|
currentEventTime = now();
|
|
return currentEventTime;
|
|
}
|
|
function requestUpdateLane(fiber) {
|
|
// Special cases
|
|
var mode = fiber.mode;
|
|
|
|
if ((mode & ConcurrentMode) === NoMode) {
|
|
return SyncLane;
|
|
} else if (
|
|
(executionContext & RenderContext) !== NoContext &&
|
|
workInProgressRootRenderLanes !== NoLanes
|
|
) {
|
|
// This is a render phase update. These are not officially supported. The
|
|
// old behavior is to give this the same "thread" (lanes) as
|
|
// whatever is currently rendering. So if you call `setState` on a component
|
|
// that happens later in the same render, it will flush. Ideally, we want to
|
|
// remove the special case and treat them as if they came from an
|
|
// interleaved event. Regardless, this pattern is not officially supported.
|
|
// This behavior is only a fallback. The flag only exists until we can roll
|
|
// out the setState warning, since existing code might accidentally rely on
|
|
// the current behavior.
|
|
return pickArbitraryLane(workInProgressRootRenderLanes);
|
|
}
|
|
|
|
var isTransition = requestCurrentTransition() !== NoTransition;
|
|
|
|
if (isTransition) {
|
|
if (ReactCurrentBatchConfig$3.transition !== null) {
|
|
var transition = ReactCurrentBatchConfig$3.transition;
|
|
|
|
if (!transition._updatedFibers) {
|
|
transition._updatedFibers = new Set();
|
|
}
|
|
|
|
transition._updatedFibers.add(fiber);
|
|
} // The algorithm for assigning an update to a lane should be stable for all
|
|
// updates at the same priority within the same event. To do this, the
|
|
// inputs to the algorithm must be the same.
|
|
//
|
|
// The trick we use is to cache the first of each of these inputs within an
|
|
// event. Then reset the cached values once we can be sure the event is
|
|
// over. Our heuristic for that is whenever we enter a concurrent work loop.
|
|
|
|
if (currentEventTransitionLane === NoLane) {
|
|
// All transitions within the same event are assigned the same lane.
|
|
currentEventTransitionLane = claimNextTransitionLane();
|
|
}
|
|
|
|
return currentEventTransitionLane;
|
|
} // Updates originating inside certain React methods, like flushSync, have
|
|
// their priority set by tracking it with a context variable.
|
|
//
|
|
// The opaque type returned by the host config is internally a lane, so we can
|
|
// use that directly.
|
|
// TODO: Move this type conversion to the event priority module.
|
|
|
|
var updateLane = getCurrentUpdatePriority();
|
|
|
|
if (updateLane !== NoLane) {
|
|
return updateLane;
|
|
} // This update originated outside React. Ask the host environment for an
|
|
// appropriate priority, based on the type of event.
|
|
//
|
|
// The opaque type returned by the host config is internally a lane, so we can
|
|
// use that directly.
|
|
// TODO: Move this type conversion to the event priority module.
|
|
|
|
var eventLane = getCurrentEventPriority();
|
|
return eventLane;
|
|
}
|
|
|
|
function requestRetryLane(fiber) {
|
|
// This is a fork of `requestUpdateLane` designed specifically for Suspense
|
|
// "retries" — a special update that attempts to flip a Suspense boundary
|
|
// from its placeholder state to its primary/resolved state.
|
|
// Special cases
|
|
var mode = fiber.mode;
|
|
|
|
if ((mode & ConcurrentMode) === NoMode) {
|
|
return SyncLane;
|
|
}
|
|
|
|
return claimNextRetryLane();
|
|
}
|
|
|
|
function scheduleUpdateOnFiber(root, fiber, lane, eventTime) {
|
|
{
|
|
if (isRunningInsertionEffect) {
|
|
error("useInsertionEffect must not schedule updates.");
|
|
}
|
|
}
|
|
|
|
{
|
|
if (isFlushingPassiveEffects) {
|
|
didScheduleUpdateDuringPassiveEffects = true;
|
|
}
|
|
} // Check if the work loop is currently suspended and waiting for data to
|
|
// finish loading.
|
|
|
|
if (
|
|
workInProgressSuspendedReason === SuspendedOnData &&
|
|
root === workInProgressRoot
|
|
) {
|
|
// The incoming update might unblock the current render. Interrupt the
|
|
// current attempt and restart from the top.
|
|
prepareFreshStack(root, NoLanes);
|
|
markRootSuspended$1(root, workInProgressRootRenderLanes);
|
|
} // Mark that the root has a pending update.
|
|
|
|
markRootUpdated(root, lane, eventTime);
|
|
|
|
if (
|
|
(executionContext & RenderContext) !== NoLanes &&
|
|
root === workInProgressRoot
|
|
) {
|
|
// This update was dispatched during the render phase. This is a mistake
|
|
// if the update originates from user space (with the exception of local
|
|
// hook updates, which are handled differently and don't reach this
|
|
// function), but there are some internal React features that use this as
|
|
// an implementation detail, like selective hydration.
|
|
warnAboutRenderPhaseUpdatesInDEV(fiber); // Track lanes that were updated during the render phase
|
|
} else {
|
|
warnIfUpdatesNotWrappedWithActDEV(fiber);
|
|
|
|
if (root === workInProgressRoot) {
|
|
// Received an update to a tree that's in the middle of rendering. Mark
|
|
// that there was an interleaved update work on this root. Unless the
|
|
// `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
|
|
// phase update. In that case, we don't treat render phase updates as if
|
|
// they were interleaved, for backwards compat reasons.
|
|
if ((executionContext & RenderContext) === NoContext) {
|
|
workInProgressRootInterleavedUpdatedLanes = mergeLanes(
|
|
workInProgressRootInterleavedUpdatedLanes,
|
|
lane
|
|
);
|
|
}
|
|
|
|
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
|
|
// The root already suspended with a delay, which means this render
|
|
// definitely won't finish. Since we have a new update, let's mark it as
|
|
// suspended now, right before marking the incoming update. This has the
|
|
// effect of interrupting the current render and switching to the update.
|
|
// TODO: Make sure this doesn't override pings that happen while we've
|
|
// already started rendering.
|
|
markRootSuspended$1(root, workInProgressRootRenderLanes);
|
|
}
|
|
}
|
|
|
|
ensureRootIsScheduled(root, eventTime);
|
|
|
|
if (
|
|
lane === SyncLane &&
|
|
executionContext === NoContext &&
|
|
(fiber.mode & ConcurrentMode) === NoMode && // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
|
|
!ReactCurrentActQueue$2.isBatchingLegacy
|
|
) {
|
|
// Flush the synchronous work now, unless we're already working or inside
|
|
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
|
|
// scheduleCallbackForFiber to preserve the ability to schedule a callback
|
|
// without immediately flushing it. We only do this for user-initiated
|
|
// updates, to preserve historical behavior of legacy mode.
|
|
resetRenderTimer();
|
|
flushSyncCallbacksOnlyInLegacyMode();
|
|
}
|
|
}
|
|
}
|
|
function scheduleInitialHydrationOnRoot(root, lane, eventTime) {
|
|
// This is a special fork of scheduleUpdateOnFiber that is only used to
|
|
// schedule the initial hydration of a root that has just been created. Most
|
|
// of the stuff in scheduleUpdateOnFiber can be skipped.
|
|
//
|
|
// The main reason for this separate path, though, is to distinguish the
|
|
// initial children from subsequent updates. In fully client-rendered roots
|
|
// (createRoot instead of hydrateRoot), all top-level renders are modeled as
|
|
// updates, but hydration roots are special because the initial render must
|
|
// match what was rendered on the server.
|
|
var current = root.current;
|
|
current.lanes = lane;
|
|
markRootUpdated(root, lane, eventTime);
|
|
ensureRootIsScheduled(root, eventTime);
|
|
}
|
|
function isUnsafeClassRenderPhaseUpdate(fiber) {
|
|
// Check if this is a render phase update. Only called by class components,
|
|
// which special (deprecated) behavior for UNSAFE_componentWillReceive props.
|
|
return (
|
|
// TODO: Remove outdated deferRenderPhaseUpdateToNextBatch experiment. We
|
|
// decided not to enable it.
|
|
(executionContext & RenderContext) !== NoContext
|
|
);
|
|
} // Use this function to schedule a task for a root. There's only one task per
|
|
// root; if a task was already scheduled, we'll check to make sure the priority
|
|
// of the existing task is the same as the priority of the next level that the
|
|
// root has work on. This function is called on every update, and right before
|
|
// exiting a task.
|
|
|
|
function ensureRootIsScheduled(root, currentTime) {
|
|
var existingCallbackNode = root.callbackNode; // Check if any lanes are being starved by other work. If so, mark them as
|
|
// expired so we know to work on those next.
|
|
|
|
markStarvedLanesAsExpired(root, currentTime); // Determine the next lanes to work on, and their priority.
|
|
|
|
var nextLanes = getNextLanes(
|
|
root,
|
|
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
|
|
);
|
|
|
|
if (nextLanes === NoLanes) {
|
|
// Special case: There's nothing to work on.
|
|
if (existingCallbackNode !== null) {
|
|
cancelCallback$1(existingCallbackNode);
|
|
}
|
|
|
|
root.callbackNode = null;
|
|
root.callbackPriority = NoLane;
|
|
return;
|
|
} // We use the highest priority lane to represent the priority of the callback.
|
|
|
|
var newCallbackPriority = getHighestPriorityLane(nextLanes); // Check if there's an existing task. We may be able to reuse it.
|
|
|
|
var existingCallbackPriority = root.callbackPriority;
|
|
|
|
if (
|
|
existingCallbackPriority === newCallbackPriority && // Special case related to `act`. If the currently scheduled task is a
|
|
// Scheduler task, rather than an `act` task, cancel it and re-scheduled
|
|
// on the `act` queue.
|
|
!(
|
|
ReactCurrentActQueue$2.current !== null &&
|
|
existingCallbackNode !== fakeActCallbackNode
|
|
)
|
|
) {
|
|
{
|
|
// If we're going to re-use an existing task, it needs to exist.
|
|
// Assume that discrete update microtasks are non-cancellable and null.
|
|
// TODO: Temporary until we confirm this warning is not fired.
|
|
if (
|
|
existingCallbackNode == null &&
|
|
!includesSyncLane(existingCallbackPriority)
|
|
) {
|
|
error(
|
|
"Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue."
|
|
);
|
|
}
|
|
} // The priority hasn't changed. We can reuse the existing task. Exit.
|
|
|
|
return;
|
|
}
|
|
|
|
if (existingCallbackNode != null) {
|
|
// Cancel the existing callback. We'll schedule a new one below.
|
|
cancelCallback$1(existingCallbackNode);
|
|
} // Schedule a new callback.
|
|
|
|
var newCallbackNode;
|
|
|
|
if (includesSyncLane(newCallbackPriority)) {
|
|
// Special case: Sync React callbacks are scheduled on a special
|
|
// internal queue
|
|
if (root.tag === LegacyRoot) {
|
|
if (ReactCurrentActQueue$2.isBatchingLegacy !== null) {
|
|
ReactCurrentActQueue$2.didScheduleLegacyUpdate = true;
|
|
}
|
|
|
|
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
|
|
} else {
|
|
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
|
|
}
|
|
|
|
{
|
|
// Flush the queue in a microtask.
|
|
if (ReactCurrentActQueue$2.current !== null) {
|
|
// Inside `act`, use our internal `act` queue so that these get flushed
|
|
// at the end of the current scope even when using the sync version
|
|
// of `act`.
|
|
ReactCurrentActQueue$2.current.push(flushSyncCallbacks);
|
|
} else {
|
|
scheduleMicrotask(function() {
|
|
// In Safari, appending an iframe forces microtasks to run.
|
|
// https://github.com/facebook/react/issues/22459
|
|
// We don't support running callbacks in the middle of render
|
|
// or commit so we need to check against that.
|
|
if (
|
|
(executionContext & (RenderContext | CommitContext)) ===
|
|
NoContext
|
|
) {
|
|
// Note that this would still prematurely flush the callbacks
|
|
// if this happens outside render or commit phase (e.g. in an event).
|
|
flushSyncCallbacks();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
newCallbackNode = null;
|
|
} else {
|
|
var schedulerPriorityLevel;
|
|
|
|
switch (lanesToEventPriority(nextLanes)) {
|
|
case DiscreteEventPriority:
|
|
schedulerPriorityLevel = ImmediatePriority;
|
|
break;
|
|
|
|
case ContinuousEventPriority:
|
|
schedulerPriorityLevel = UserBlockingPriority;
|
|
break;
|
|
|
|
case DefaultEventPriority:
|
|
schedulerPriorityLevel = NormalPriority;
|
|
break;
|
|
|
|
case IdleEventPriority:
|
|
schedulerPriorityLevel = IdlePriority;
|
|
break;
|
|
|
|
default:
|
|
schedulerPriorityLevel = NormalPriority;
|
|
break;
|
|
}
|
|
|
|
newCallbackNode = scheduleCallback$2(
|
|
schedulerPriorityLevel,
|
|
performConcurrentWorkOnRoot.bind(null, root)
|
|
);
|
|
}
|
|
|
|
root.callbackPriority = newCallbackPriority;
|
|
root.callbackNode = newCallbackNode;
|
|
} // This is the entry point for every concurrent task, i.e. anything that
|
|
// goes through Scheduler.
|
|
|
|
function performConcurrentWorkOnRoot(root, didTimeout) {
|
|
// event time. The next update will compute a new event time.
|
|
|
|
currentEventTime = NoTimestamp;
|
|
currentEventTransitionLane = NoLanes;
|
|
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
|
|
throw new Error("Should not already be working.");
|
|
} // Flush any pending passive effects before deciding which lanes to work on,
|
|
// in case they schedule additional work.
|
|
|
|
var originalCallbackNode = root.callbackNode;
|
|
var didFlushPassiveEffects = flushPassiveEffects();
|
|
|
|
if (didFlushPassiveEffects) {
|
|
// Something in the passive effect phase may have canceled the current task.
|
|
// Check if the task node for this root was changed.
|
|
if (root.callbackNode !== originalCallbackNode) {
|
|
// The current task was canceled. Exit. We don't need to call
|
|
// `ensureRootIsScheduled` because the check above implies either that
|
|
// there's a new task, or that there's no remaining work on this root.
|
|
return null;
|
|
}
|
|
} // Determine the next lanes to work on, using the fields stored
|
|
// on the root.
|
|
|
|
var lanes = getNextLanes(
|
|
root,
|
|
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
|
|
);
|
|
|
|
if (lanes === NoLanes) {
|
|
// Defensive coding. This is never expected to happen.
|
|
return null;
|
|
} // We disable time-slicing in some cases: if the work has been CPU-bound
|
|
// for too long ("expired" work, to prevent starvation), or we're in
|
|
// sync-updates-by-default mode.
|
|
// TODO: We only check `didTimeout` defensively, to account for a Scheduler
|
|
// bug we're still investigating. Once the bug in Scheduler is fixed,
|
|
// we can remove this, since we track expiration ourselves.
|
|
|
|
var shouldTimeSlice =
|
|
!includesBlockingLane(root, lanes) &&
|
|
!includesExpiredLane(root, lanes) &&
|
|
!didTimeout;
|
|
var exitStatus = shouldTimeSlice
|
|
? renderRootConcurrent(root, lanes)
|
|
: renderRootSync(root, lanes);
|
|
|
|
if (exitStatus !== RootInProgress) {
|
|
if (exitStatus === RootErrored) {
|
|
// If something threw an error, try rendering one more time. We'll
|
|
// render synchronously to block concurrent data mutations, and we'll
|
|
// includes all pending updates are included. If it still fails after
|
|
// the second attempt, we'll give up and commit the resulting tree.
|
|
var originallyAttemptedLanes = lanes;
|
|
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(
|
|
root,
|
|
originallyAttemptedLanes
|
|
);
|
|
|
|
if (errorRetryLanes !== NoLanes) {
|
|
lanes = errorRetryLanes;
|
|
exitStatus = recoverFromConcurrentError(
|
|
root,
|
|
originallyAttemptedLanes,
|
|
errorRetryLanes
|
|
);
|
|
}
|
|
}
|
|
|
|
if (exitStatus === RootFatalErrored) {
|
|
var fatalError = workInProgressRootFatalError;
|
|
prepareFreshStack(root, NoLanes);
|
|
markRootSuspended$1(root, lanes);
|
|
ensureRootIsScheduled(root, now());
|
|
throw fatalError;
|
|
}
|
|
|
|
if (exitStatus === RootDidNotComplete) {
|
|
// The render unwound without completing the tree. This happens in special
|
|
// cases where need to exit the current render without producing a
|
|
// consistent tree or committing.
|
|
markRootSuspended$1(root, lanes);
|
|
} else {
|
|
// The render completed.
|
|
// Check if this render may have yielded to a concurrent event, and if so,
|
|
// confirm that any newly rendered stores are consistent.
|
|
// TODO: It's possible that even a concurrent render may never have yielded
|
|
// to the main thread, if it was fast enough, or if it expired. We could
|
|
// skip the consistency check in that case, too.
|
|
var renderWasConcurrent = !includesBlockingLane(root, lanes);
|
|
var finishedWork = root.current.alternate;
|
|
|
|
if (
|
|
renderWasConcurrent &&
|
|
!isRenderConsistentWithExternalStores(finishedWork)
|
|
) {
|
|
// A store was mutated in an interleaved event. Render again,
|
|
// synchronously, to block further mutations.
|
|
exitStatus = renderRootSync(root, lanes); // We need to check again if something threw
|
|
|
|
if (exitStatus === RootErrored) {
|
|
var _originallyAttemptedLanes = lanes;
|
|
|
|
var _errorRetryLanes = getLanesToRetrySynchronouslyOnError(
|
|
root,
|
|
_originallyAttemptedLanes
|
|
);
|
|
|
|
if (_errorRetryLanes !== NoLanes) {
|
|
lanes = _errorRetryLanes;
|
|
exitStatus = recoverFromConcurrentError(
|
|
root,
|
|
_originallyAttemptedLanes,
|
|
_errorRetryLanes
|
|
); // We assume the tree is now consistent because we didn't yield to any
|
|
// concurrent events.
|
|
}
|
|
}
|
|
|
|
if (exitStatus === RootFatalErrored) {
|
|
var _fatalError = workInProgressRootFatalError;
|
|
prepareFreshStack(root, NoLanes);
|
|
markRootSuspended$1(root, lanes);
|
|
ensureRootIsScheduled(root, now());
|
|
throw _fatalError;
|
|
} // FIXME: Need to check for RootDidNotComplete again. The factoring here
|
|
// isn't ideal.
|
|
} // We now have a consistent tree. The next step is either to commit it,
|
|
// or, if something suspended, wait to commit it after a timeout.
|
|
|
|
root.finishedWork = finishedWork;
|
|
root.finishedLanes = lanes;
|
|
finishConcurrentRender(root, exitStatus, lanes);
|
|
}
|
|
}
|
|
|
|
ensureRootIsScheduled(root, now());
|
|
|
|
if (root.callbackNode === originalCallbackNode) {
|
|
// The task node scheduled for this root is the same one that's
|
|
// currently executed. Need to return a continuation.
|
|
if (
|
|
workInProgressSuspendedReason === SuspendedOnData &&
|
|
workInProgressRoot === root
|
|
) {
|
|
// Special case: The work loop is currently suspended and waiting for
|
|
// data to resolve. Unschedule the current task.
|
|
//
|
|
// TODO: The factoring is a little weird. Arguably this should be checked
|
|
// in ensureRootIsScheduled instead. I went back and forth, not totally
|
|
// sure yet.
|
|
root.callbackPriority = NoLane;
|
|
root.callbackNode = null;
|
|
return null;
|
|
}
|
|
|
|
return performConcurrentWorkOnRoot.bind(null, root);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function recoverFromConcurrentError(
|
|
root,
|
|
originallyAttemptedLanes,
|
|
errorRetryLanes
|
|
) {
|
|
// If an error occurred during hydration, discard server response and fall
|
|
// back to client side render.
|
|
// Before rendering again, save the errors from the previous attempt.
|
|
var errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
|
|
var wasRootDehydrated = isRootDehydrated(root);
|
|
|
|
if (wasRootDehydrated) {
|
|
// The shell failed to hydrate. Set a flag to force a client rendering
|
|
// during the next attempt. To do this, we call prepareFreshStack now
|
|
// to create the root work-in-progress fiber. This is a bit weird in terms
|
|
// of factoring, because it relies on renderRootSync not calling
|
|
// prepareFreshStack again in the call below, which happens because the
|
|
// root and lanes haven't changed.
|
|
//
|
|
// TODO: I think what we should do is set ForceClientRender inside
|
|
// throwException, like we do for nested Suspense boundaries. The reason
|
|
// it's here instead is so we can switch to the synchronous work loop, too.
|
|
// Something to consider for a future refactor.
|
|
var rootWorkInProgress = prepareFreshStack(root, errorRetryLanes);
|
|
rootWorkInProgress.flags |= ForceClientRender;
|
|
|
|
{
|
|
errorHydratingContainer(root.containerInfo);
|
|
}
|
|
}
|
|
|
|
var exitStatus = renderRootSync(root, errorRetryLanes);
|
|
|
|
if (exitStatus !== RootErrored) {
|
|
// Successfully finished rendering on retry
|
|
if (workInProgressRootDidAttachPingListener && !wasRootDehydrated) {
|
|
// During the synchronous render, we attached additional ping listeners.
|
|
// This is highly suggestive of an uncached promise (though it's not the
|
|
// only reason this would happen). If it was an uncached promise, then
|
|
// it may have masked a downstream error from ocurring without actually
|
|
// fixing it. Example:
|
|
//
|
|
// use(Promise.resolve('uncached'))
|
|
// throw new Error('Oops!')
|
|
//
|
|
// When this happens, there's a conflict between blocking potential
|
|
// concurrent data races and unwrapping uncached promise values. We
|
|
// have to choose one or the other. Because the data race recovery is
|
|
// a last ditch effort, we'll disable it.
|
|
root.errorRecoveryDisabledLanes = mergeLanes(
|
|
root.errorRecoveryDisabledLanes,
|
|
originallyAttemptedLanes
|
|
); // Mark the current render as suspended and force it to restart. Once
|
|
// these lanes finish successfully, we'll re-enable the error recovery
|
|
// mechanism for subsequent updates.
|
|
|
|
workInProgressRootInterleavedUpdatedLanes |= originallyAttemptedLanes;
|
|
return RootSuspendedWithDelay;
|
|
} // The errors from the failed first attempt have been recovered. Add
|
|
// them to the collection of recoverable errors. We'll log them in the
|
|
// commit phase.
|
|
|
|
var errorsFromSecondAttempt = workInProgressRootRecoverableErrors;
|
|
workInProgressRootRecoverableErrors = errorsFromFirstAttempt; // The errors from the second attempt should be queued after the errors
|
|
// from the first attempt, to preserve the causal sequence.
|
|
|
|
if (errorsFromSecondAttempt !== null) {
|
|
queueRecoverableErrors(errorsFromSecondAttempt);
|
|
}
|
|
}
|
|
|
|
return exitStatus;
|
|
}
|
|
|
|
function queueRecoverableErrors(errors) {
|
|
if (workInProgressRootRecoverableErrors === null) {
|
|
workInProgressRootRecoverableErrors = errors;
|
|
} else {
|
|
// $FlowFixMe[method-unbinding]
|
|
workInProgressRootRecoverableErrors.push.apply(
|
|
workInProgressRootRecoverableErrors,
|
|
errors
|
|
);
|
|
}
|
|
}
|
|
|
|
function finishConcurrentRender(root, exitStatus, lanes) {
|
|
switch (exitStatus) {
|
|
case RootInProgress:
|
|
case RootFatalErrored: {
|
|
throw new Error("Root did not complete. This is a bug in React.");
|
|
}
|
|
// Flow knows about invariant, so it complains if I add a break
|
|
// statement, but eslint doesn't know about invariant, so it complains
|
|
// if I do. eslint-disable-next-line no-fallthrough
|
|
|
|
case RootErrored: {
|
|
// We should have already attempted to retry this tree. If we reached
|
|
// this point, it errored again. Commit it.
|
|
commitRoot(
|
|
root,
|
|
workInProgressRootRecoverableErrors,
|
|
workInProgressTransitions
|
|
);
|
|
break;
|
|
}
|
|
|
|
case RootSuspended: {
|
|
markRootSuspended$1(root, lanes); // We have an acceptable loading state. We need to figure out if we
|
|
// should immediately commit it or wait a bit.
|
|
|
|
if (
|
|
includesOnlyRetries(lanes) && // do not delay if we're inside an act() scope
|
|
!shouldForceFlushFallbacksInDEV()
|
|
) {
|
|
// This render only included retries, no updates. Throttle committing
|
|
// retries so that we don't show too many loading states too quickly.
|
|
var msUntilTimeout =
|
|
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now(); // Don't bother with a very short suspense time.
|
|
|
|
if (msUntilTimeout > 10) {
|
|
var nextLanes = getNextLanes(root, NoLanes);
|
|
|
|
if (nextLanes !== NoLanes) {
|
|
// There's additional work on this root.
|
|
break;
|
|
} // The render is suspended, it hasn't timed out, and there's no
|
|
// lower priority work to do. Instead of committing the fallback
|
|
// immediately, wait for more data to arrive.
|
|
|
|
root.timeoutHandle = scheduleTimeout(
|
|
commitRoot.bind(
|
|
null,
|
|
root,
|
|
workInProgressRootRecoverableErrors,
|
|
workInProgressTransitions
|
|
),
|
|
msUntilTimeout
|
|
);
|
|
break;
|
|
}
|
|
} // The work expired. Commit immediately.
|
|
|
|
commitRoot(
|
|
root,
|
|
workInProgressRootRecoverableErrors,
|
|
workInProgressTransitions
|
|
);
|
|
break;
|
|
}
|
|
|
|
case RootSuspendedWithDelay: {
|
|
markRootSuspended$1(root, lanes);
|
|
|
|
if (includesOnlyTransitions(lanes)) {
|
|
// This is a transition, so we should exit without committing a
|
|
// placeholder and without scheduling a timeout. Delay indefinitely
|
|
// until we receive more data.
|
|
break;
|
|
}
|
|
|
|
if (!shouldForceFlushFallbacksInDEV()) {
|
|
// This is not a transition, but we did trigger an avoided state.
|
|
// Schedule a placeholder to display after a short delay, using the Just
|
|
// Noticeable Difference.
|
|
// TODO: Is the JND optimization worth the added complexity? If this is
|
|
// the only reason we track the event time, then probably not.
|
|
// Consider removing.
|
|
var mostRecentEventTime = getMostRecentEventTime(root, lanes);
|
|
var eventTimeMs = mostRecentEventTime;
|
|
var timeElapsedMs = now() - eventTimeMs;
|
|
|
|
var _msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; // Don't bother with a very short suspense time.
|
|
|
|
if (_msUntilTimeout > 10) {
|
|
// Instead of committing the fallback immediately, wait for more data
|
|
// to arrive.
|
|
root.timeoutHandle = scheduleTimeout(
|
|
commitRoot.bind(
|
|
null,
|
|
root,
|
|
workInProgressRootRecoverableErrors,
|
|
workInProgressTransitions
|
|
),
|
|
_msUntilTimeout
|
|
);
|
|
break;
|
|
}
|
|
} // Commit the placeholder.
|
|
|
|
commitRoot(
|
|
root,
|
|
workInProgressRootRecoverableErrors,
|
|
workInProgressTransitions
|
|
);
|
|
break;
|
|
}
|
|
|
|
case RootCompleted: {
|
|
// The work completed. Ready to commit.
|
|
commitRoot(
|
|
root,
|
|
workInProgressRootRecoverableErrors,
|
|
workInProgressTransitions
|
|
);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
throw new Error("Unknown root exit status.");
|
|
}
|
|
}
|
|
}
|
|
|
|
function isRenderConsistentWithExternalStores(finishedWork) {
|
|
// Search the rendered tree for external store reads, and check whether the
|
|
// stores were mutated in a concurrent event. Intentionally using an iterative
|
|
// loop instead of recursion so we can exit early.
|
|
var node = finishedWork;
|
|
|
|
while (true) {
|
|
if (node.flags & StoreConsistency) {
|
|
var updateQueue = node.updateQueue;
|
|
|
|
if (updateQueue !== null) {
|
|
var checks = updateQueue.stores;
|
|
|
|
if (checks !== null) {
|
|
for (var i = 0; i < checks.length; i++) {
|
|
var check = checks[i];
|
|
var getSnapshot = check.getSnapshot;
|
|
var renderedValue = check.value;
|
|
|
|
try {
|
|
if (!objectIs(getSnapshot(), renderedValue)) {
|
|
// Found an inconsistent store.
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
// If `getSnapshot` throws, return `false`. This will schedule
|
|
// a re-render, and the error will be rethrown during render.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var child = node.child;
|
|
|
|
if (node.subtreeFlags & StoreConsistency && child !== null) {
|
|
child.return = node;
|
|
node = child;
|
|
continue;
|
|
}
|
|
|
|
if (node === finishedWork) {
|
|
return true;
|
|
}
|
|
|
|
while (node.sibling === null) {
|
|
if (node.return === null || node.return === finishedWork) {
|
|
return true;
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
|
|
node.sibling.return = node.return;
|
|
node = node.sibling;
|
|
} // Flow doesn't know this is unreachable, but eslint does
|
|
// eslint-disable-next-line no-unreachable
|
|
|
|
return true;
|
|
}
|
|
|
|
function markRootSuspended$1(root, suspendedLanes) {
|
|
// When suspending, we should always exclude lanes that were pinged or (more
|
|
// rarely, since we try to avoid it) updated during the render phase.
|
|
// TODO: Lol maybe there's a better way to factor this besides this
|
|
// obnoxiously named function :)
|
|
suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
|
|
suspendedLanes = removeLanes(
|
|
suspendedLanes,
|
|
workInProgressRootInterleavedUpdatedLanes
|
|
);
|
|
markRootSuspended(root, suspendedLanes);
|
|
} // This is the entry point for synchronous tasks that don't go
|
|
// through Scheduler
|
|
|
|
function performSyncWorkOnRoot(root) {
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
|
|
throw new Error("Should not already be working.");
|
|
}
|
|
|
|
flushPassiveEffects();
|
|
var lanes = getNextLanes(root, NoLanes);
|
|
|
|
if (!includesSyncLane(lanes)) {
|
|
// There's no remaining sync work left.
|
|
ensureRootIsScheduled(root, now());
|
|
return null;
|
|
}
|
|
|
|
var exitStatus = renderRootSync(root, lanes);
|
|
|
|
if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
|
|
// If something threw an error, try rendering one more time. We'll render
|
|
// synchronously to block concurrent data mutations, and we'll includes
|
|
// all pending updates are included. If it still fails after the second
|
|
// attempt, we'll give up and commit the resulting tree.
|
|
var originallyAttemptedLanes = lanes;
|
|
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(
|
|
root,
|
|
originallyAttemptedLanes
|
|
);
|
|
|
|
if (errorRetryLanes !== NoLanes) {
|
|
lanes = errorRetryLanes;
|
|
exitStatus = recoverFromConcurrentError(
|
|
root,
|
|
originallyAttemptedLanes,
|
|
errorRetryLanes
|
|
);
|
|
}
|
|
}
|
|
|
|
if (exitStatus === RootFatalErrored) {
|
|
var fatalError = workInProgressRootFatalError;
|
|
prepareFreshStack(root, NoLanes);
|
|
markRootSuspended$1(root, lanes);
|
|
ensureRootIsScheduled(root, now());
|
|
throw fatalError;
|
|
}
|
|
|
|
if (exitStatus === RootDidNotComplete) {
|
|
// The render unwound without completing the tree. This happens in special
|
|
// cases where need to exit the current render without producing a
|
|
// consistent tree or committing.
|
|
markRootSuspended$1(root, lanes);
|
|
ensureRootIsScheduled(root, now());
|
|
return null;
|
|
} // We now have a consistent tree. Because this is a sync render, we
|
|
// will commit it even if something suspended.
|
|
|
|
var finishedWork = root.current.alternate;
|
|
root.finishedWork = finishedWork;
|
|
root.finishedLanes = lanes;
|
|
commitRoot(
|
|
root,
|
|
workInProgressRootRecoverableErrors,
|
|
workInProgressTransitions
|
|
); // Before exiting, make sure there's a callback scheduled for the next
|
|
// pending level.
|
|
|
|
ensureRootIsScheduled(root, now());
|
|
return null;
|
|
}
|
|
|
|
function flushRoot(root, lanes) {
|
|
if (lanes !== NoLanes) {
|
|
markRootEntangled(root, mergeLanes(lanes, SyncLane));
|
|
ensureRootIsScheduled(root, now());
|
|
|
|
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
|
|
resetRenderTimer();
|
|
flushSyncCallbacks();
|
|
}
|
|
}
|
|
}
|
|
function batchedUpdates$1(fn, a) {
|
|
var prevExecutionContext = executionContext;
|
|
executionContext |= BatchedContext;
|
|
|
|
try {
|
|
return fn(a);
|
|
} finally {
|
|
executionContext = prevExecutionContext; // If there were legacy sync updates, flush them at the end of the outer
|
|
// most batchedUpdates-like method.
|
|
|
|
if (
|
|
executionContext === NoContext && // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
|
|
!ReactCurrentActQueue$2.isBatchingLegacy
|
|
) {
|
|
resetRenderTimer();
|
|
flushSyncCallbacksOnlyInLegacyMode();
|
|
}
|
|
}
|
|
}
|
|
function discreteUpdates(fn, a, b, c, d) {
|
|
var previousPriority = getCurrentUpdatePriority();
|
|
var prevTransition = ReactCurrentBatchConfig$3.transition;
|
|
|
|
try {
|
|
ReactCurrentBatchConfig$3.transition = null;
|
|
setCurrentUpdatePriority(DiscreteEventPriority);
|
|
return fn(a, b, c, d);
|
|
} finally {
|
|
setCurrentUpdatePriority(previousPriority);
|
|
ReactCurrentBatchConfig$3.transition = prevTransition;
|
|
|
|
if (executionContext === NoContext) {
|
|
resetRenderTimer();
|
|
}
|
|
}
|
|
} // Overload the definition to the two valid signatures.
|
|
// Warning, this opts-out of checking the function body.
|
|
// eslint-disable-next-line no-unused-vars
|
|
// eslint-disable-next-line no-redeclare
|
|
// eslint-disable-next-line no-redeclare
|
|
|
|
function flushSync(fn) {
|
|
// In legacy mode, we flush pending passive effects at the beginning of the
|
|
// next event, not at the end of the previous one.
|
|
if (
|
|
rootWithPendingPassiveEffects !== null &&
|
|
rootWithPendingPassiveEffects.tag === LegacyRoot &&
|
|
(executionContext & (RenderContext | CommitContext)) === NoContext
|
|
) {
|
|
flushPassiveEffects();
|
|
}
|
|
|
|
var prevExecutionContext = executionContext;
|
|
executionContext |= BatchedContext;
|
|
var prevTransition = ReactCurrentBatchConfig$3.transition;
|
|
var previousPriority = getCurrentUpdatePriority();
|
|
|
|
try {
|
|
ReactCurrentBatchConfig$3.transition = null;
|
|
setCurrentUpdatePriority(DiscreteEventPriority);
|
|
|
|
if (fn) {
|
|
return fn();
|
|
} else {
|
|
return undefined;
|
|
}
|
|
} finally {
|
|
setCurrentUpdatePriority(previousPriority);
|
|
ReactCurrentBatchConfig$3.transition = prevTransition;
|
|
executionContext = prevExecutionContext; // Flush the immediate callbacks that were scheduled during this batch.
|
|
// Note that this will happen even if batchedUpdates is higher up
|
|
// the stack.
|
|
|
|
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
|
|
flushSyncCallbacks();
|
|
}
|
|
}
|
|
}
|
|
function isAlreadyRendering() {
|
|
// Used by the renderer to print a warning if certain APIs are called from
|
|
// the wrong context.
|
|
return (executionContext & (RenderContext | CommitContext)) !== NoContext;
|
|
}
|
|
function flushControlled(fn) {
|
|
var prevExecutionContext = executionContext;
|
|
executionContext |= BatchedContext;
|
|
var prevTransition = ReactCurrentBatchConfig$3.transition;
|
|
var previousPriority = getCurrentUpdatePriority();
|
|
|
|
try {
|
|
ReactCurrentBatchConfig$3.transition = null;
|
|
setCurrentUpdatePriority(DiscreteEventPriority);
|
|
fn();
|
|
} finally {
|
|
setCurrentUpdatePriority(previousPriority);
|
|
ReactCurrentBatchConfig$3.transition = prevTransition;
|
|
executionContext = prevExecutionContext;
|
|
|
|
if (executionContext === NoContext) {
|
|
// Flush the immediate callbacks that were scheduled during this batch
|
|
resetRenderTimer();
|
|
flushSyncCallbacks();
|
|
}
|
|
}
|
|
} // This is called by the HiddenContext module when we enter or leave a
|
|
// hidden subtree. The stack logic is managed there because that's the only
|
|
// place that ever modifies it. Which module it lives in doesn't matter for
|
|
// performance because this function will get inlined regardless
|
|
|
|
function setRenderLanes(subtreeRenderLanes) {
|
|
renderLanes$1 = subtreeRenderLanes;
|
|
}
|
|
function getRenderLanes() {
|
|
return renderLanes$1;
|
|
}
|
|
|
|
function resetWorkInProgressStack() {
|
|
if (workInProgress === null) return;
|
|
var interruptedWork;
|
|
|
|
if (workInProgressSuspendedReason === NotSuspended) {
|
|
// Normal case. Work-in-progress hasn't started yet. Unwind all
|
|
// its parents.
|
|
interruptedWork = workInProgress.return;
|
|
} else {
|
|
// Work-in-progress is in suspended state. Reset the work loop and unwind
|
|
// both the suspended fiber and all its parents.
|
|
resetSuspendedWorkLoopOnUnwind();
|
|
interruptedWork = workInProgress;
|
|
}
|
|
|
|
while (interruptedWork !== null) {
|
|
var current = interruptedWork.alternate;
|
|
unwindInterruptedWork(current, interruptedWork);
|
|
interruptedWork = interruptedWork.return;
|
|
}
|
|
|
|
workInProgress = null;
|
|
}
|
|
|
|
function prepareFreshStack(root, lanes) {
|
|
root.finishedWork = null;
|
|
root.finishedLanes = NoLanes;
|
|
var timeoutHandle = root.timeoutHandle;
|
|
|
|
if (timeoutHandle !== noTimeout) {
|
|
// The root previous suspended and scheduled a timeout to commit a fallback
|
|
// state. Now that we have additional work, cancel the timeout.
|
|
root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
|
|
|
|
cancelTimeout(timeoutHandle);
|
|
}
|
|
|
|
resetWorkInProgressStack();
|
|
workInProgressRoot = root;
|
|
var rootWorkInProgress = createWorkInProgress(root.current, null);
|
|
workInProgress = rootWorkInProgress;
|
|
workInProgressRootRenderLanes = renderLanes$1 = lanes;
|
|
workInProgressSuspendedReason = NotSuspended;
|
|
workInProgressThrownValue = null;
|
|
workInProgressRootDidAttachPingListener = false;
|
|
workInProgressRootExitStatus = RootInProgress;
|
|
workInProgressRootFatalError = null;
|
|
workInProgressRootSkippedLanes = NoLanes;
|
|
workInProgressRootInterleavedUpdatedLanes = NoLanes;
|
|
workInProgressRootPingedLanes = NoLanes;
|
|
workInProgressRootConcurrentErrors = null;
|
|
workInProgressRootRecoverableErrors = null;
|
|
finishQueueingConcurrentUpdates();
|
|
|
|
{
|
|
ReactStrictModeWarnings.discardPendingWarnings();
|
|
}
|
|
|
|
return rootWorkInProgress;
|
|
}
|
|
|
|
function resetSuspendedWorkLoopOnUnwind() {
|
|
// Reset module-level state that was set during the render phase.
|
|
resetContextDependencies();
|
|
resetHooksOnUnwind();
|
|
}
|
|
|
|
function handleThrow(root, thrownValue) {
|
|
// A component threw an exception. Usually this is because it suspended, but
|
|
// it also includes regular program errors.
|
|
//
|
|
// We're either going to unwind the stack to show a Suspense or error
|
|
// boundary, or we're going to replay the component again. Like after a
|
|
// promise resolves.
|
|
//
|
|
// Until we decide whether we're going to unwind or replay, we should preserve
|
|
// the current state of the work loop without resetting anything.
|
|
//
|
|
// If we do decide to unwind the stack, module-level variables will be reset
|
|
// in resetSuspendedWorkLoopOnUnwind.
|
|
// These should be reset immediately because they're only supposed to be set
|
|
// when React is executing user code.
|
|
resetHooksAfterThrow();
|
|
resetCurrentFiber();
|
|
ReactCurrentOwner$2.current = null;
|
|
|
|
if (thrownValue === SuspenseException) {
|
|
// This is a special type of exception used for Suspense. For historical
|
|
// reasons, the rest of the Suspense implementation expects the thrown value
|
|
// to be a thenable, because before `use` existed that was the (unstable)
|
|
// API for suspending. This implementation detail can change later, once we
|
|
// deprecate the old API in favor of `use`.
|
|
thrownValue = getSuspendedThenable();
|
|
workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves()
|
|
? SuspendedOnData
|
|
: SuspendedOnImmediate;
|
|
} else if (thrownValue === SelectiveHydrationException) {
|
|
// An update flowed into a dehydrated boundary. Before we can apply the
|
|
// update, we need to finish hydrating. Interrupt the work-in-progress
|
|
// render so we can restart at the hydration lane.
|
|
//
|
|
// The ideal implementation would be able to switch contexts without
|
|
// unwinding the current stack.
|
|
//
|
|
// We could name this something more general but as of now it's the only
|
|
// case where we think this should happen.
|
|
workInProgressSuspendedReason = SuspendedOnHydration;
|
|
} else {
|
|
// This is a regular error.
|
|
var isWakeable =
|
|
thrownValue !== null &&
|
|
typeof thrownValue === "object" &&
|
|
typeof thrownValue.then === "function";
|
|
workInProgressSuspendedReason = isWakeable // A wakeable object was thrown by a legacy Suspense implementation.
|
|
? // This has slightly different behavior than suspending with `use`.
|
|
SuspendedOnDeprecatedThrowPromise // This is a regular error. If something earlier in the component already
|
|
: // suspended, we must clear the thenable state to unblock the work loop.
|
|
SuspendedOnError;
|
|
}
|
|
|
|
workInProgressThrownValue = thrownValue;
|
|
var erroredWork = workInProgress;
|
|
|
|
if (erroredWork === null) {
|
|
// This is a fatal error
|
|
workInProgressRootExitStatus = RootFatalErrored;
|
|
workInProgressRootFatalError = thrownValue;
|
|
return;
|
|
}
|
|
}
|
|
|
|
function shouldAttemptToSuspendUntilDataResolves() {
|
|
// Check if there are other pending updates that might possibly unblock this
|
|
// component from suspending. This mirrors the check in
|
|
// renderDidSuspendDelayIfPossible. We should attempt to unify them somehow.
|
|
// TODO: Consider unwinding immediately, using the
|
|
// SuspendedOnHydration mechanism.
|
|
if (
|
|
includesNonIdleWork(workInProgressRootSkippedLanes) ||
|
|
includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)
|
|
) {
|
|
// Suspend normally. renderDidSuspendDelayIfPossible will handle
|
|
// interrupting the work loop.
|
|
return false;
|
|
} // TODO: We should be able to remove the equivalent check in
|
|
// finishConcurrentRender, and rely just on this one.
|
|
|
|
if (includesOnlyTransitions(workInProgressRootRenderLanes)) {
|
|
// If we're rendering inside the "shell" of the app, it's better to suspend
|
|
// rendering and wait for the data to resolve. Otherwise, we should switch
|
|
// to a fallback and continue rendering.
|
|
return getShellBoundary() === null;
|
|
}
|
|
|
|
var handler = getSuspenseHandler();
|
|
|
|
if (handler === null);
|
|
else {
|
|
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
|
|
// During a retry, we can suspend rendering if the nearest Suspense boundary
|
|
// is the boundary of the "shell", because we're guaranteed not to block
|
|
// any new content from appearing.
|
|
return handler === getShellBoundary();
|
|
}
|
|
} // For all other Lanes besides Transitions and Retries, we should not wait
|
|
// for the data to load.
|
|
// TODO: We should wait during Offscreen prerendering, too.
|
|
|
|
return false;
|
|
}
|
|
|
|
function pushDispatcher(container) {
|
|
prepareRendererToRender(container);
|
|
var prevDispatcher = ReactCurrentDispatcher$2.current;
|
|
ReactCurrentDispatcher$2.current = ContextOnlyDispatcher;
|
|
|
|
if (prevDispatcher === null) {
|
|
// The React isomorphic package does not include a default dispatcher.
|
|
// Instead the first renderer will lazily attach one, in order to give
|
|
// nicer error messages.
|
|
return ContextOnlyDispatcher;
|
|
} else {
|
|
return prevDispatcher;
|
|
}
|
|
}
|
|
|
|
function popDispatcher(prevDispatcher) {
|
|
resetRendererAfterRender();
|
|
ReactCurrentDispatcher$2.current = prevDispatcher;
|
|
}
|
|
|
|
function pushCacheDispatcher() {
|
|
{
|
|
var prevCacheDispatcher = ReactCurrentCache.current;
|
|
ReactCurrentCache.current = DefaultCacheDispatcher;
|
|
return prevCacheDispatcher;
|
|
}
|
|
}
|
|
|
|
function popCacheDispatcher(prevCacheDispatcher) {
|
|
{
|
|
ReactCurrentCache.current = prevCacheDispatcher;
|
|
}
|
|
}
|
|
|
|
function markCommitTimeOfFallback() {
|
|
globalMostRecentFallbackTime = now();
|
|
}
|
|
function markSkippedUpdateLanes(lane) {
|
|
workInProgressRootSkippedLanes = mergeLanes(
|
|
lane,
|
|
workInProgressRootSkippedLanes
|
|
);
|
|
}
|
|
function renderDidSuspend() {
|
|
if (workInProgressRootExitStatus === RootInProgress) {
|
|
workInProgressRootExitStatus = RootSuspended;
|
|
}
|
|
}
|
|
function renderDidSuspendDelayIfPossible() {
|
|
workInProgressRootExitStatus = RootSuspendedWithDelay; // Check if there are updates that we skipped tree that might have unblocked
|
|
// this render.
|
|
|
|
if (
|
|
workInProgressRoot !== null &&
|
|
(includesNonIdleWork(workInProgressRootSkippedLanes) ||
|
|
includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes))
|
|
) {
|
|
// Mark the current render as suspended so that we switch to working on
|
|
// the updates that were skipped. Usually we only suspend at the end of
|
|
// the render phase.
|
|
// TODO: We should probably always mark the root as suspended immediately
|
|
// (inside this function), since by suspending at the end of the render
|
|
// phase introduces a potential mistake where we suspend lanes that were
|
|
// pinged or updated while we were rendering.
|
|
// TODO: Consider unwinding immediately, using the
|
|
// SuspendedOnHydration mechanism.
|
|
// $FlowFixMe[incompatible-call] need null check workInProgressRoot
|
|
markRootSuspended$1(workInProgressRoot, workInProgressRootRenderLanes);
|
|
}
|
|
}
|
|
function renderDidError(error) {
|
|
if (workInProgressRootExitStatus !== RootSuspendedWithDelay) {
|
|
workInProgressRootExitStatus = RootErrored;
|
|
}
|
|
|
|
if (workInProgressRootConcurrentErrors === null) {
|
|
workInProgressRootConcurrentErrors = [error];
|
|
} else {
|
|
workInProgressRootConcurrentErrors.push(error);
|
|
}
|
|
} // Called during render to determine if anything has suspended.
|
|
// Returns false if we're not sure.
|
|
|
|
function renderHasNotSuspendedYet() {
|
|
// If something errored or completed, we can't really be sure,
|
|
// so those are false.
|
|
return workInProgressRootExitStatus === RootInProgress;
|
|
} // TODO: Over time, this function and renderRootConcurrent have become more
|
|
// and more similar. Not sure it makes sense to maintain forked paths. Consider
|
|
// unifying them again.
|
|
|
|
function renderRootSync(root, lanes) {
|
|
var prevExecutionContext = executionContext;
|
|
executionContext |= RenderContext;
|
|
var prevDispatcher = pushDispatcher(root.containerInfo);
|
|
var prevCacheDispatcher = pushCacheDispatcher(); // If the root or lanes have changed, throw out the existing stack
|
|
// and prepare a fresh one. Otherwise we'll continue where we left off.
|
|
|
|
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
|
|
workInProgressTransitions = getTransitionsForLanes();
|
|
prepareFreshStack(root, lanes);
|
|
}
|
|
|
|
outer: do {
|
|
try {
|
|
if (
|
|
workInProgressSuspendedReason !== NotSuspended &&
|
|
workInProgress !== null
|
|
) {
|
|
// The work loop is suspended. During a synchronous render, we don't
|
|
// yield to the main thread. Immediately unwind the stack. This will
|
|
// trigger either a fallback or an error boundary.
|
|
// TODO: For discrete and "default" updates (anything that's not
|
|
// flushSync), we want to wait for the microtasks the flush before
|
|
// unwinding. Will probably implement this using renderRootConcurrent,
|
|
// or merge renderRootSync and renderRootConcurrent into the same
|
|
// function and fork the behavior some other way.
|
|
var unitOfWork = workInProgress;
|
|
var thrownValue = workInProgressThrownValue;
|
|
|
|
switch (workInProgressSuspendedReason) {
|
|
case SuspendedOnHydration: {
|
|
// Selective hydration. An update flowed into a dehydrated tree.
|
|
// Interrupt the current render so the work loop can switch to the
|
|
// hydration lane.
|
|
resetWorkInProgressStack();
|
|
workInProgressRootExitStatus = RootDidNotComplete;
|
|
break outer;
|
|
}
|
|
|
|
default: {
|
|
// Continue with the normal work loop.
|
|
workInProgressSuspendedReason = NotSuspended;
|
|
workInProgressThrownValue = null;
|
|
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
workLoopSync();
|
|
break;
|
|
} catch (thrownValue) {
|
|
handleThrow(root, thrownValue);
|
|
}
|
|
} while (true);
|
|
|
|
resetContextDependencies();
|
|
executionContext = prevExecutionContext;
|
|
popDispatcher(prevDispatcher);
|
|
popCacheDispatcher(prevCacheDispatcher);
|
|
|
|
if (workInProgress !== null) {
|
|
// This is a sync render, so we should have finished the whole tree.
|
|
throw new Error(
|
|
"Cannot commit an incomplete root. This error is likely caused by a " +
|
|
"bug in React. Please file an issue."
|
|
);
|
|
}
|
|
|
|
workInProgressRoot = null;
|
|
workInProgressRootRenderLanes = NoLanes; // It's safe to process the queue now that the render phase is complete.
|
|
|
|
finishQueueingConcurrentUpdates();
|
|
return workInProgressRootExitStatus;
|
|
} // The work loop is an extremely hot path. Tell Closure not to inline it.
|
|
|
|
/** @noinline */
|
|
|
|
function workLoopSync() {
|
|
// Perform work without checking if we need to yield between fiber.
|
|
while (workInProgress !== null) {
|
|
performUnitOfWork(workInProgress);
|
|
}
|
|
}
|
|
|
|
function renderRootConcurrent(root, lanes) {
|
|
var prevExecutionContext = executionContext;
|
|
executionContext |= RenderContext;
|
|
var prevDispatcher = pushDispatcher(root.containerInfo);
|
|
var prevCacheDispatcher = pushCacheDispatcher(); // If the root or lanes have changed, throw out the existing stack
|
|
// and prepare a fresh one. Otherwise we'll continue where we left off.
|
|
|
|
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
|
|
workInProgressTransitions = getTransitionsForLanes();
|
|
resetRenderTimer();
|
|
prepareFreshStack(root, lanes);
|
|
}
|
|
|
|
outer: do {
|
|
try {
|
|
if (
|
|
workInProgressSuspendedReason !== NotSuspended &&
|
|
workInProgress !== null
|
|
) {
|
|
// The work loop is suspended. We need to either unwind the stack or
|
|
// replay the suspended component.
|
|
var unitOfWork = workInProgress;
|
|
var thrownValue = workInProgressThrownValue;
|
|
|
|
switch (workInProgressSuspendedReason) {
|
|
case SuspendedOnError: {
|
|
// Unwind then continue with the normal work loop.
|
|
workInProgressSuspendedReason = NotSuspended;
|
|
workInProgressThrownValue = null;
|
|
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
|
|
break;
|
|
}
|
|
|
|
case SuspendedOnData: {
|
|
var thenable = thrownValue;
|
|
|
|
if (isThenableResolved(thenable)) {
|
|
// The data resolved. Try rendering the component again.
|
|
workInProgressSuspendedReason = NotSuspended;
|
|
workInProgressThrownValue = null;
|
|
replaySuspendedUnitOfWork(unitOfWork);
|
|
break;
|
|
} // The work loop is suspended on data. We should wait for it to
|
|
// resolve before continuing to render.
|
|
// TODO: Handle the case where the promise resolves synchronously.
|
|
// Usually this is handled when we instrument the promise to add a
|
|
// `status` field, but if the promise already has a status, we won't
|
|
// have added a listener until right here.
|
|
|
|
var onResolution = function() {
|
|
ensureRootIsScheduled(root, now());
|
|
};
|
|
|
|
thenable.then(onResolution, onResolution);
|
|
break outer;
|
|
}
|
|
|
|
case SuspendedOnImmediate: {
|
|
// If this fiber just suspended, it's possible the data is already
|
|
// cached. Yield to the main thread to give it a chance to ping. If
|
|
// it does, we can retry immediately without unwinding the stack.
|
|
workInProgressSuspendedReason = SuspendedAndReadyToUnwind;
|
|
break outer;
|
|
}
|
|
|
|
case SuspendedAndReadyToUnwind: {
|
|
var _thenable = thrownValue;
|
|
|
|
if (isThenableResolved(_thenable)) {
|
|
// The data resolved. Try rendering the component again.
|
|
workInProgressSuspendedReason = NotSuspended;
|
|
workInProgressThrownValue = null;
|
|
replaySuspendedUnitOfWork(unitOfWork);
|
|
} else {
|
|
// Otherwise, unwind then continue with the normal work loop.
|
|
workInProgressSuspendedReason = NotSuspended;
|
|
workInProgressThrownValue = null;
|
|
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SuspendedOnDeprecatedThrowPromise: {
|
|
// Suspended by an old implementation that uses the `throw promise`
|
|
// pattern. The newer replaying behavior can cause subtle issues
|
|
// like infinite ping loops. So we maintain the old behavior and
|
|
// always unwind.
|
|
workInProgressSuspendedReason = NotSuspended;
|
|
workInProgressThrownValue = null;
|
|
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
|
|
break;
|
|
}
|
|
|
|
case SuspendedOnHydration: {
|
|
// Selective hydration. An update flowed into a dehydrated tree.
|
|
// Interrupt the current render so the work loop can switch to the
|
|
// hydration lane.
|
|
resetWorkInProgressStack();
|
|
workInProgressRootExitStatus = RootDidNotComplete;
|
|
break outer;
|
|
}
|
|
|
|
default: {
|
|
throw new Error(
|
|
"Unexpected SuspendedReason. This is a bug in React."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
workLoopConcurrent();
|
|
break;
|
|
} catch (thrownValue) {
|
|
handleThrow(root, thrownValue);
|
|
}
|
|
} while (true);
|
|
|
|
resetContextDependencies();
|
|
popDispatcher(prevDispatcher);
|
|
popCacheDispatcher(prevCacheDispatcher);
|
|
executionContext = prevExecutionContext;
|
|
|
|
if (workInProgress !== null) {
|
|
return RootInProgress;
|
|
} else {
|
|
workInProgressRoot = null;
|
|
workInProgressRootRenderLanes = NoLanes; // It's safe to process the queue now that the render phase is complete.
|
|
|
|
finishQueueingConcurrentUpdates(); // Return the final exit status.
|
|
|
|
return workInProgressRootExitStatus;
|
|
}
|
|
}
|
|
/** @noinline */
|
|
|
|
function workLoopConcurrent() {
|
|
// Perform work until Scheduler asks us to yield
|
|
while (workInProgress !== null && !shouldYield()) {
|
|
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
|
performUnitOfWork(workInProgress);
|
|
}
|
|
}
|
|
|
|
function performUnitOfWork(unitOfWork) {
|
|
// The current, flushed, state of this fiber is the alternate. Ideally
|
|
// nothing should rely on this, but relying on it here means that we don't
|
|
// need an additional field on the work in progress.
|
|
var current = unitOfWork.alternate;
|
|
setCurrentFiber(unitOfWork);
|
|
var next;
|
|
|
|
{
|
|
next = beginWork$1(current, unitOfWork, renderLanes$1);
|
|
}
|
|
|
|
resetCurrentFiber();
|
|
unitOfWork.memoizedProps = unitOfWork.pendingProps;
|
|
|
|
if (next === null) {
|
|
// If this doesn't spawn new work, complete the current work.
|
|
completeUnitOfWork(unitOfWork);
|
|
} else {
|
|
workInProgress = next;
|
|
}
|
|
|
|
ReactCurrentOwner$2.current = null;
|
|
}
|
|
|
|
function replaySuspendedUnitOfWork(unitOfWork) {
|
|
// This is a fork of performUnitOfWork specifcally for replaying a fiber that
|
|
// just suspended.
|
|
//
|
|
var current = unitOfWork.alternate;
|
|
setCurrentFiber(unitOfWork);
|
|
var next;
|
|
setCurrentFiber(unitOfWork);
|
|
|
|
switch (unitOfWork.tag) {
|
|
case IndeterminateComponent: {
|
|
// Because it suspended with `use`, we can assume it's a
|
|
// function component.
|
|
unitOfWork.tag = FunctionComponent; // Fallthrough to the next branch.
|
|
}
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case FunctionComponent:
|
|
case ForwardRef: {
|
|
// Resolve `defaultProps`. This logic is copied from `beginWork`.
|
|
// TODO: Consider moving this switch statement into that module. Also,
|
|
// could maybe use this as an opportunity to say `use` doesn't work with
|
|
// `defaultProps` :)
|
|
var Component = unitOfWork.type;
|
|
var unresolvedProps = unitOfWork.pendingProps;
|
|
var resolvedProps =
|
|
unitOfWork.elementType === Component
|
|
? unresolvedProps
|
|
: resolveDefaultProps(Component, unresolvedProps);
|
|
next = replayFunctionComponent(
|
|
current,
|
|
unitOfWork,
|
|
resolvedProps,
|
|
Component,
|
|
workInProgressRootRenderLanes
|
|
);
|
|
break;
|
|
}
|
|
|
|
case SimpleMemoComponent: {
|
|
var _Component = unitOfWork.type;
|
|
var nextProps = unitOfWork.pendingProps;
|
|
next = replayFunctionComponent(
|
|
current,
|
|
unitOfWork,
|
|
nextProps,
|
|
_Component,
|
|
workInProgressRootRenderLanes
|
|
);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
{
|
|
error(
|
|
"Unexpected type of work: %s, Currently only function " +
|
|
"components are replayed after suspending. This is a bug in React.",
|
|
unitOfWork.tag
|
|
);
|
|
}
|
|
|
|
resetSuspendedWorkLoopOnUnwind();
|
|
unwindInterruptedWork(current, unitOfWork);
|
|
unitOfWork = workInProgress = resetWorkInProgress(
|
|
unitOfWork,
|
|
renderLanes$1
|
|
);
|
|
next = beginWork$1(current, unitOfWork, renderLanes$1);
|
|
break;
|
|
}
|
|
}
|
|
// normal work loop.
|
|
|
|
resetCurrentFiber();
|
|
unitOfWork.memoizedProps = unitOfWork.pendingProps;
|
|
|
|
if (next === null) {
|
|
// If this doesn't spawn new work, complete the current work.
|
|
completeUnitOfWork(unitOfWork);
|
|
} else {
|
|
workInProgress = next;
|
|
}
|
|
|
|
ReactCurrentOwner$2.current = null;
|
|
}
|
|
|
|
function unwindSuspendedUnitOfWork(unitOfWork, thrownValue) {
|
|
// This is a fork of performUnitOfWork specifcally for unwinding a fiber
|
|
// that threw an exception.
|
|
//
|
|
// Return to the normal work loop. This will unwind the stack, and potentially
|
|
// result in showing a fallback.
|
|
resetSuspendedWorkLoopOnUnwind();
|
|
var returnFiber = unitOfWork.return;
|
|
|
|
if (returnFiber === null || workInProgressRoot === null) {
|
|
// Expected to be working on a non-root fiber. This is a fatal error
|
|
// because there's no ancestor that can handle it; the root is
|
|
// supposed to capture all errors that weren't caught by an error
|
|
// boundary.
|
|
workInProgressRootExitStatus = RootFatalErrored;
|
|
workInProgressRootFatalError = thrownValue; // Set `workInProgress` to null. This represents advancing to the next
|
|
// sibling, or the parent if there are no siblings. But since the root
|
|
// has no siblings nor a parent, we set it to null. Usually this is
|
|
// handled by `completeUnitOfWork` or `unwindWork`, but since we're
|
|
// intentionally not calling those, we need set it here.
|
|
// TODO: Consider calling `unwindWork` to pop the contexts.
|
|
|
|
workInProgress = null;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Find and mark the nearest Suspense or error boundary that can handle
|
|
// this "exception".
|
|
throwException(
|
|
workInProgressRoot,
|
|
returnFiber,
|
|
unitOfWork,
|
|
thrownValue,
|
|
workInProgressRootRenderLanes
|
|
);
|
|
} catch (error) {
|
|
// We had trouble processing the error. An example of this happening is
|
|
// when accessing the `componentDidCatch` property of an error boundary
|
|
// throws an error. A weird edge case. There's a regression test for this.
|
|
// To prevent an infinite loop, bubble the error up to the next parent.
|
|
workInProgress = returnFiber;
|
|
throw error;
|
|
} // Return to the normal work loop.
|
|
|
|
completeUnitOfWork(unitOfWork);
|
|
}
|
|
|
|
function completeUnitOfWork(unitOfWork) {
|
|
// Attempt to complete the current unit of work, then move to the next
|
|
// sibling. If there are no more siblings, return to the parent fiber.
|
|
var completedWork = unitOfWork;
|
|
|
|
do {
|
|
// The current, flushed, state of this fiber is the alternate. Ideally
|
|
// nothing should rely on this, but relying on it here means that we don't
|
|
// need an additional field on the work in progress.
|
|
var current = completedWork.alternate;
|
|
var returnFiber = completedWork.return; // Check if the work completed or if something threw.
|
|
|
|
if ((completedWork.flags & Incomplete) === NoFlags) {
|
|
setCurrentFiber(completedWork);
|
|
var next = void 0;
|
|
|
|
{
|
|
next = completeWork(current, completedWork, renderLanes$1);
|
|
}
|
|
|
|
resetCurrentFiber();
|
|
|
|
if (next !== null) {
|
|
// Completing this fiber spawned new work. Work on that next.
|
|
workInProgress = next;
|
|
return;
|
|
}
|
|
} else {
|
|
// This fiber did not complete because something threw. Pop values off
|
|
// the stack without entering the complete phase. If this is a boundary,
|
|
// capture values if possible.
|
|
var _next = unwindWork(current, completedWork); // Because this fiber did not complete, don't reset its lanes.
|
|
|
|
if (_next !== null) {
|
|
// If completing this work spawned new work, do that next. We'll come
|
|
// back here again.
|
|
// Since we're restarting, remove anything that is not a host effect
|
|
// from the effect tag.
|
|
_next.flags &= HostEffectMask;
|
|
workInProgress = _next;
|
|
return;
|
|
}
|
|
|
|
if (returnFiber !== null) {
|
|
// Mark the parent fiber as incomplete and clear its subtree flags.
|
|
returnFiber.flags |= Incomplete;
|
|
returnFiber.subtreeFlags = NoFlags;
|
|
returnFiber.deletions = null;
|
|
} else {
|
|
// We've unwound all the way to the root.
|
|
workInProgressRootExitStatus = RootDidNotComplete;
|
|
workInProgress = null;
|
|
return;
|
|
}
|
|
}
|
|
|
|
var siblingFiber = completedWork.sibling;
|
|
|
|
if (siblingFiber !== null) {
|
|
// If there is more work to do in this returnFiber, do that next.
|
|
workInProgress = siblingFiber;
|
|
return;
|
|
} // Otherwise, return to the parent
|
|
// $FlowFixMe[incompatible-type] we bail out when we get a null
|
|
|
|
completedWork = returnFiber; // Update the next thing we're working on in case something throws.
|
|
|
|
workInProgress = completedWork;
|
|
} while (completedWork !== null); // We've reached the root.
|
|
|
|
if (workInProgressRootExitStatus === RootInProgress) {
|
|
workInProgressRootExitStatus = RootCompleted;
|
|
}
|
|
}
|
|
|
|
function commitRoot(root, recoverableErrors, transitions) {
|
|
// TODO: This no longer makes any sense. We already wrap the mutation and
|
|
// layout phases. Should be able to remove.
|
|
var previousUpdateLanePriority = getCurrentUpdatePriority();
|
|
var prevTransition = ReactCurrentBatchConfig$3.transition;
|
|
|
|
try {
|
|
ReactCurrentBatchConfig$3.transition = null;
|
|
setCurrentUpdatePriority(DiscreteEventPriority);
|
|
commitRootImpl(
|
|
root,
|
|
recoverableErrors,
|
|
transitions,
|
|
previousUpdateLanePriority
|
|
);
|
|
} finally {
|
|
ReactCurrentBatchConfig$3.transition = prevTransition;
|
|
setCurrentUpdatePriority(previousUpdateLanePriority);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function commitRootImpl(
|
|
root,
|
|
recoverableErrors,
|
|
transitions,
|
|
renderPriorityLevel
|
|
) {
|
|
do {
|
|
// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
|
|
// means `flushPassiveEffects` will sometimes result in additional
|
|
// passive effects. So we need to keep flushing in a loop until there are
|
|
// no more pending effects.
|
|
// TODO: Might be better if `flushPassiveEffects` did not automatically
|
|
// flush synchronous work at the end, to avoid factoring hazards like this.
|
|
flushPassiveEffects();
|
|
} while (rootWithPendingPassiveEffects !== null);
|
|
|
|
flushRenderPhaseStrictModeWarningsInDEV();
|
|
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
|
|
throw new Error("Should not already be working.");
|
|
}
|
|
|
|
var finishedWork = root.finishedWork;
|
|
var lanes = root.finishedLanes;
|
|
|
|
if (finishedWork === null) {
|
|
return null;
|
|
} else {
|
|
{
|
|
if (lanes === NoLanes) {
|
|
error(
|
|
"root.finishedLanes should not be empty during a commit. This is a " +
|
|
"bug in React."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
root.finishedWork = null;
|
|
root.finishedLanes = NoLanes;
|
|
|
|
if (finishedWork === root.current) {
|
|
throw new Error(
|
|
"Cannot commit the same tree as before. This error is likely caused by " +
|
|
"a bug in React. Please file an issue."
|
|
);
|
|
} // commitRoot never returns a continuation; it always finishes synchronously.
|
|
// So we can clear these now to allow a new callback to be scheduled.
|
|
|
|
root.callbackNode = null;
|
|
root.callbackPriority = NoLane; // Check which lanes no longer have any work scheduled on them, and mark
|
|
// those as finished.
|
|
|
|
var remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes); // Make sure to account for lanes that were updated by a concurrent event
|
|
// during the render phase; don't mark them as finished.
|
|
|
|
var concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
|
|
remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
|
|
markRootFinished(root, remainingLanes);
|
|
|
|
if (root === workInProgressRoot) {
|
|
// We can reset these now that they are finished.
|
|
workInProgressRoot = null;
|
|
workInProgress = null;
|
|
workInProgressRootRenderLanes = NoLanes;
|
|
} // If there are pending passive effects, schedule a callback to process them.
|
|
// Do this as early as possible, so it is queued before anything else that
|
|
// might get scheduled in the commit phase. (See #16714.)
|
|
// TODO: Delete all other places that schedule the passive effect callback
|
|
// They're redundant.
|
|
|
|
if (
|
|
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
|
|
(finishedWork.flags & PassiveMask) !== NoFlags
|
|
) {
|
|
if (!rootDoesHavePassiveEffects) {
|
|
rootDoesHavePassiveEffects = true;
|
|
pendingPassiveEffectsRemainingLanes = remainingLanes; // workInProgressTransitions might be overwritten, so we want
|
|
// to store it in pendingPassiveTransitions until they get processed
|
|
// We need to pass this through as an argument to commitRoot
|
|
// because workInProgressTransitions might have changed between
|
|
// the previous render and commit if we throttle the commit
|
|
// with setTimeout
|
|
|
|
pendingPassiveTransitions = transitions;
|
|
scheduleCallback$2(NormalPriority, function() {
|
|
flushPassiveEffects(); // This render triggered passive effects: release the root cache pool
|
|
// *after* passive effects fire to avoid freeing a cache pool that may
|
|
// be referenced by a node in the tree (HostRoot, Cache boundary etc)
|
|
|
|
return null;
|
|
});
|
|
}
|
|
} // Check if there are any effects in the whole tree.
|
|
// TODO: This is left over from the effect list implementation, where we had
|
|
// to check for the existence of `firstEffect` to satisfy Flow. I think the
|
|
// only other reason this optimization exists is because it affects profiling.
|
|
// Reconsider whether this is necessary.
|
|
|
|
var subtreeHasEffects =
|
|
(finishedWork.subtreeFlags &
|
|
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
|
|
NoFlags;
|
|
var rootHasEffect =
|
|
(finishedWork.flags &
|
|
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
|
|
NoFlags;
|
|
|
|
if (subtreeHasEffects || rootHasEffect) {
|
|
var prevTransition = ReactCurrentBatchConfig$3.transition;
|
|
ReactCurrentBatchConfig$3.transition = null;
|
|
var previousPriority = getCurrentUpdatePriority();
|
|
setCurrentUpdatePriority(DiscreteEventPriority);
|
|
var prevExecutionContext = executionContext;
|
|
executionContext |= CommitContext; // Reset this to null before calling lifecycles
|
|
|
|
ReactCurrentOwner$2.current = null; // The commit phase is broken into several sub-phases. We do a separate pass
|
|
// of the effect list for each phase: all mutation effects come before all
|
|
// layout effects, and so on.
|
|
// The first phase a "before mutation" phase. We use this phase to read the
|
|
// state of the host tree right before we mutate it. This is where
|
|
// getSnapshotBeforeUpdate is called.
|
|
|
|
var shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
|
|
root,
|
|
finishedWork
|
|
);
|
|
|
|
commitMutationEffects(root, finishedWork);
|
|
|
|
{
|
|
if (shouldFireAfterActiveInstanceBlur) {
|
|
afterActiveInstanceBlur();
|
|
}
|
|
}
|
|
|
|
resetAfterCommit(root.containerInfo); // The work-in-progress tree is now the current tree. This must come after
|
|
// the mutation phase, so that the previous tree is still current during
|
|
// componentWillUnmount, but before the layout phase, so that the finished
|
|
// work is current during componentDidMount/Update.
|
|
|
|
root.current = finishedWork; // The next phase is the layout phase, where we call effects that read
|
|
|
|
commitLayoutEffects(finishedWork, root);
|
|
// opportunity to paint.
|
|
|
|
requestPaint();
|
|
executionContext = prevExecutionContext; // Reset the priority to the previous non-sync value.
|
|
|
|
setCurrentUpdatePriority(previousPriority);
|
|
ReactCurrentBatchConfig$3.transition = prevTransition;
|
|
} else {
|
|
// No effects.
|
|
root.current = finishedWork; // Measure these anyway so the flamegraph explicitly shows that there were
|
|
}
|
|
|
|
var rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
|
|
|
|
if (rootDoesHavePassiveEffects) {
|
|
// This commit has passive effects. Stash a reference to them. But don't
|
|
// schedule a callback until after flushing layout work.
|
|
rootDoesHavePassiveEffects = false;
|
|
rootWithPendingPassiveEffects = root;
|
|
pendingPassiveEffectsLanes = lanes;
|
|
} else {
|
|
// There were no passive effects, so we can immediately release the cache
|
|
// pool for this render.
|
|
releaseRootPooledCache(root, remainingLanes);
|
|
|
|
{
|
|
nestedPassiveUpdateCount = 0;
|
|
rootWithPassiveNestedUpdates = null;
|
|
}
|
|
} // Read this again, since an effect might have updated it
|
|
|
|
remainingLanes = root.pendingLanes; // Check if there's remaining work on this root
|
|
// TODO: This is part of the `componentDidCatch` implementation. Its purpose
|
|
// is to detect whether something might have called setState inside
|
|
// `componentDidCatch`. The mechanism is known to be flawed because `setState`
|
|
// inside `componentDidCatch` is itself flawed — that's why we recommend
|
|
// `getDerivedStateFromError` instead. However, it could be improved by
|
|
// checking if remainingLanes includes Sync work, instead of whether there's
|
|
// any work remaining at all (which would also include stuff like Suspense
|
|
// retries or transitions). It's been like this for a while, though, so fixing
|
|
// it probably isn't that urgent.
|
|
|
|
if (remainingLanes === NoLanes) {
|
|
// If there's no remaining work, we can clear the set of already failed
|
|
// error boundaries.
|
|
legacyErrorBoundariesThatAlreadyFailed = null;
|
|
}
|
|
|
|
{
|
|
if (!rootDidHavePassiveEffects) {
|
|
commitDoubleInvokeEffectsInDEV(root, false);
|
|
}
|
|
}
|
|
|
|
onCommitRoot(finishedWork.stateNode, renderPriorityLevel);
|
|
|
|
{
|
|
onCommitRoot$1();
|
|
} // Always call this before exiting `commitRoot`, to ensure that any
|
|
// additional work on this root is scheduled.
|
|
|
|
ensureRootIsScheduled(root, now());
|
|
|
|
if (recoverableErrors !== null) {
|
|
// There were errors during this render, but recovered from them without
|
|
// needing to surface it to the UI. We log them here.
|
|
var onRecoverableError = root.onRecoverableError;
|
|
|
|
for (var i = 0; i < recoverableErrors.length; i++) {
|
|
var recoverableError = recoverableErrors[i];
|
|
var errorInfo = makeErrorInfo(
|
|
recoverableError.digest,
|
|
recoverableError.stack
|
|
);
|
|
onRecoverableError(recoverableError.value, errorInfo);
|
|
}
|
|
}
|
|
|
|
if (hasUncaughtError) {
|
|
hasUncaughtError = false;
|
|
var error$1 = firstUncaughtError;
|
|
firstUncaughtError = null;
|
|
throw error$1;
|
|
} // If the passive effects are the result of a discrete render, flush them
|
|
// synchronously at the end of the current task so that the result is
|
|
// immediately observable. Otherwise, we assume that they are not
|
|
// order-dependent and do not need to be observed by external systems, so we
|
|
// can wait until after paint.
|
|
// TODO: We can optimize this by not scheduling the callback earlier. Since we
|
|
// currently schedule the callback in multiple places, will wait until those
|
|
// are consolidated.
|
|
|
|
if (includesSyncLane(pendingPassiveEffectsLanes) && root.tag !== LegacyRoot) {
|
|
flushPassiveEffects();
|
|
} // Read this again, since a passive effect might have updated it
|
|
|
|
remainingLanes = root.pendingLanes;
|
|
|
|
if (includesSyncLane(remainingLanes)) {
|
|
// finishing. If there are too many, it indicates an infinite update loop.
|
|
|
|
if (root === rootWithNestedUpdates) {
|
|
nestedUpdateCount++;
|
|
} else {
|
|
nestedUpdateCount = 0;
|
|
rootWithNestedUpdates = root;
|
|
}
|
|
} else {
|
|
nestedUpdateCount = 0;
|
|
} // If layout work was scheduled, flush it now.
|
|
|
|
flushSyncCallbacks();
|
|
|
|
return null;
|
|
}
|
|
|
|
function makeErrorInfo(digest, componentStack) {
|
|
{
|
|
var errorInfo = {
|
|
componentStack: componentStack,
|
|
digest: digest
|
|
};
|
|
Object.defineProperty(errorInfo, "digest", {
|
|
configurable: false,
|
|
enumerable: true,
|
|
get: function() {
|
|
error(
|
|
'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
|
|
" This property is deprecated and will be removed in a future version of React." +
|
|
" To access the digest of an Error look for this property on the Error instance itself."
|
|
);
|
|
|
|
return digest;
|
|
}
|
|
});
|
|
return errorInfo;
|
|
}
|
|
}
|
|
|
|
function releaseRootPooledCache(root, remainingLanes) {
|
|
{
|
|
var pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes);
|
|
|
|
if (pooledCacheLanes === NoLanes) {
|
|
// None of the remaining work relies on the cache pool. Clear it so
|
|
// subsequent requests get a new cache
|
|
var pooledCache = root.pooledCache;
|
|
|
|
if (pooledCache != null) {
|
|
root.pooledCache = null;
|
|
releaseCache(pooledCache);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function flushPassiveEffects() {
|
|
// Returns whether passive effects were flushed.
|
|
// TODO: Combine this check with the one in flushPassiveEFfectsImpl. We should
|
|
// probably just combine the two functions. I believe they were only separate
|
|
// in the first place because we used to wrap it with
|
|
// `Scheduler.runWithPriority`, which accepts a function. But now we track the
|
|
// priority within React itself, so we can mutate the variable directly.
|
|
if (rootWithPendingPassiveEffects !== null) {
|
|
// Cache the root since rootWithPendingPassiveEffects is cleared in
|
|
// flushPassiveEffectsImpl
|
|
var root = rootWithPendingPassiveEffects; // Cache and clear the remaining lanes flag; it must be reset since this
|
|
// method can be called from various places, not always from commitRoot
|
|
// where the remaining lanes are known
|
|
|
|
var remainingLanes = pendingPassiveEffectsRemainingLanes;
|
|
pendingPassiveEffectsRemainingLanes = NoLanes;
|
|
var renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
|
|
var priority = lowerEventPriority(DefaultEventPriority, renderPriority);
|
|
var prevTransition = ReactCurrentBatchConfig$3.transition;
|
|
var previousPriority = getCurrentUpdatePriority();
|
|
|
|
try {
|
|
ReactCurrentBatchConfig$3.transition = null;
|
|
setCurrentUpdatePriority(priority);
|
|
return flushPassiveEffectsImpl();
|
|
} finally {
|
|
setCurrentUpdatePriority(previousPriority);
|
|
ReactCurrentBatchConfig$3.transition = prevTransition; // Once passive effects have run for the tree - giving components a
|
|
// chance to retain cache instances they use - release the pooled
|
|
// cache at the root (if there is one)
|
|
|
|
releaseRootPooledCache(root, remainingLanes);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function flushPassiveEffectsImpl() {
|
|
if (rootWithPendingPassiveEffects === null) {
|
|
return false;
|
|
} // Cache and clear the transitions flag
|
|
|
|
var transitions = pendingPassiveTransitions;
|
|
pendingPassiveTransitions = null;
|
|
var root = rootWithPendingPassiveEffects;
|
|
var lanes = pendingPassiveEffectsLanes;
|
|
rootWithPendingPassiveEffects = null; // TODO: This is sometimes out of sync with rootWithPendingPassiveEffects.
|
|
// Figure out why and fix it. It's not causing any known issues (probably
|
|
// because it's only used for profiling), but it's a refactor hazard.
|
|
|
|
pendingPassiveEffectsLanes = NoLanes;
|
|
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
|
|
throw new Error("Cannot flush passive effects while already rendering.");
|
|
}
|
|
|
|
{
|
|
isFlushingPassiveEffects = true;
|
|
didScheduleUpdateDuringPassiveEffects = false;
|
|
}
|
|
|
|
var prevExecutionContext = executionContext;
|
|
executionContext |= CommitContext;
|
|
commitPassiveUnmountEffects(root.current);
|
|
commitPassiveMountEffects(root, root.current, lanes, transitions); // TODO: Move to commitPassiveMountEffects
|
|
|
|
{
|
|
commitDoubleInvokeEffectsInDEV(root, true);
|
|
}
|
|
|
|
executionContext = prevExecutionContext;
|
|
flushSyncCallbacks();
|
|
|
|
{
|
|
// If additional passive effects were scheduled, increment a counter. If this
|
|
// exceeds the limit, we'll fire a warning.
|
|
if (didScheduleUpdateDuringPassiveEffects) {
|
|
if (root === rootWithPassiveNestedUpdates) {
|
|
nestedPassiveUpdateCount++;
|
|
} else {
|
|
nestedPassiveUpdateCount = 0;
|
|
rootWithPassiveNestedUpdates = root;
|
|
}
|
|
} else {
|
|
nestedPassiveUpdateCount = 0;
|
|
}
|
|
|
|
isFlushingPassiveEffects = false;
|
|
didScheduleUpdateDuringPassiveEffects = false;
|
|
} // TODO: Move to commitPassiveMountEffects
|
|
|
|
onPostCommitRoot(root);
|
|
|
|
return true;
|
|
}
|
|
|
|
function isAlreadyFailedLegacyErrorBoundary(instance) {
|
|
return (
|
|
legacyErrorBoundariesThatAlreadyFailed !== null &&
|
|
legacyErrorBoundariesThatAlreadyFailed.has(instance)
|
|
);
|
|
}
|
|
function markLegacyErrorBoundaryAsFailed(instance) {
|
|
if (legacyErrorBoundariesThatAlreadyFailed === null) {
|
|
legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
|
|
} else {
|
|
legacyErrorBoundariesThatAlreadyFailed.add(instance);
|
|
}
|
|
}
|
|
|
|
function prepareToThrowUncaughtError(error) {
|
|
if (!hasUncaughtError) {
|
|
hasUncaughtError = true;
|
|
firstUncaughtError = error;
|
|
}
|
|
}
|
|
|
|
var onUncaughtError = prepareToThrowUncaughtError;
|
|
|
|
function captureCommitPhaseErrorOnRoot(rootFiber, sourceFiber, error) {
|
|
var errorInfo = createCapturedValueAtFiber(error, sourceFiber);
|
|
var update = createRootErrorUpdate(rootFiber, errorInfo, SyncLane);
|
|
var root = enqueueUpdate$1(rootFiber, update, SyncLane);
|
|
var eventTime = requestEventTime();
|
|
|
|
if (root !== null) {
|
|
markRootUpdated(root, SyncLane, eventTime);
|
|
ensureRootIsScheduled(root, eventTime);
|
|
}
|
|
}
|
|
|
|
function captureCommitPhaseError(sourceFiber, nearestMountedAncestor, error$1) {
|
|
{
|
|
reportUncaughtErrorInDEV(error$1);
|
|
setIsRunningInsertionEffect(false);
|
|
}
|
|
|
|
if (sourceFiber.tag === HostRoot) {
|
|
// Error was thrown at the root. There is no parent, so the root
|
|
// itself should capture it.
|
|
captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error$1);
|
|
return;
|
|
}
|
|
|
|
var fiber = null;
|
|
|
|
{
|
|
fiber = nearestMountedAncestor;
|
|
}
|
|
|
|
while (fiber !== null) {
|
|
if (fiber.tag === HostRoot) {
|
|
captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error$1);
|
|
return;
|
|
} else if (fiber.tag === ClassComponent) {
|
|
var ctor = fiber.type;
|
|
var instance = fiber.stateNode;
|
|
|
|
if (
|
|
typeof ctor.getDerivedStateFromError === "function" ||
|
|
(typeof instance.componentDidCatch === "function" &&
|
|
!isAlreadyFailedLegacyErrorBoundary(instance))
|
|
) {
|
|
var errorInfo = createCapturedValueAtFiber(error$1, sourceFiber);
|
|
var update = createClassErrorUpdate(fiber, errorInfo, SyncLane);
|
|
var root = enqueueUpdate$1(fiber, update, SyncLane);
|
|
var eventTime = requestEventTime();
|
|
|
|
if (root !== null) {
|
|
markRootUpdated(root, SyncLane, eventTime);
|
|
ensureRootIsScheduled(root, eventTime);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
fiber = fiber.return;
|
|
}
|
|
|
|
{
|
|
// TODO: Until we re-land skipUnmountedBoundaries (see #20147), this warning
|
|
// will fire for errors that are thrown by destroy functions inside deleted
|
|
// trees. What it should instead do is propagate the error to the parent of
|
|
// the deleted tree. In the meantime, do not add this warning to the
|
|
// allowlist; this is only for our internal use.
|
|
error(
|
|
"Internal React error: Attempted to capture a commit phase error " +
|
|
"inside a detached tree. This indicates a bug in React. Likely " +
|
|
"causes include deleting the same fiber more than once, committing an " +
|
|
"already-finished tree, or an inconsistent return pointer.\n\n" +
|
|
"Error message:\n\n%s",
|
|
error$1
|
|
);
|
|
}
|
|
}
|
|
function attachPingListener(root, wakeable, lanes) {
|
|
// Attach a ping listener
|
|
//
|
|
// The data might resolve before we have a chance to commit the fallback. Or,
|
|
// in the case of a refresh, we'll never commit a fallback. So we need to
|
|
// attach a listener now. When it resolves ("pings"), we can decide whether to
|
|
// try rendering the tree again.
|
|
//
|
|
// Only attach a listener if one does not already exist for the lanes
|
|
// we're currently rendering (which acts like a "thread ID" here).
|
|
//
|
|
// We only need to do this in concurrent mode. Legacy Suspense always
|
|
// commits fallbacks synchronously, so there are no pings.
|
|
var pingCache = root.pingCache;
|
|
var threadIDs;
|
|
|
|
if (pingCache === null) {
|
|
pingCache = root.pingCache = new PossiblyWeakMap$1();
|
|
threadIDs = new Set();
|
|
pingCache.set(wakeable, threadIDs);
|
|
} else {
|
|
threadIDs = pingCache.get(wakeable);
|
|
|
|
if (threadIDs === undefined) {
|
|
threadIDs = new Set();
|
|
pingCache.set(wakeable, threadIDs);
|
|
}
|
|
}
|
|
|
|
if (!threadIDs.has(lanes)) {
|
|
workInProgressRootDidAttachPingListener = true; // Memoize using the thread ID to prevent redundant listeners.
|
|
|
|
threadIDs.add(lanes);
|
|
var ping = pingSuspendedRoot.bind(null, root, wakeable, lanes);
|
|
|
|
wakeable.then(ping, ping);
|
|
}
|
|
}
|
|
|
|
function pingSuspendedRoot(root, wakeable, pingedLanes) {
|
|
var pingCache = root.pingCache;
|
|
|
|
if (pingCache !== null) {
|
|
// The wakeable resolved, so we no longer need to memoize, because it will
|
|
// never be thrown again.
|
|
pingCache.delete(wakeable);
|
|
}
|
|
|
|
var eventTime = requestEventTime();
|
|
markRootPinged(root, pingedLanes);
|
|
warnIfSuspenseResolutionNotWrappedWithActDEV(root);
|
|
|
|
if (
|
|
workInProgressRoot === root &&
|
|
isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)
|
|
) {
|
|
// Received a ping at the same priority level at which we're currently
|
|
// rendering. We might want to restart this render. This should mirror
|
|
// the logic of whether or not a root suspends once it completes.
|
|
// TODO: If we're rendering sync either due to Sync, Batched or expired,
|
|
// we should probably never restart.
|
|
// If we're suspended with delay, or if it's a retry, we'll always suspend
|
|
// so we can always restart.
|
|
if (
|
|
workInProgressRootExitStatus === RootSuspendedWithDelay ||
|
|
(workInProgressRootExitStatus === RootSuspended &&
|
|
includesOnlyRetries(workInProgressRootRenderLanes) &&
|
|
now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
|
|
) {
|
|
// Force a restart from the root by unwinding the stack. Unless this is
|
|
// being called from the render phase, because that would cause a crash.
|
|
if ((executionContext & RenderContext) === NoContext) {
|
|
prepareFreshStack(root, NoLanes);
|
|
}
|
|
} else {
|
|
// Even though we can't restart right now, we might get an
|
|
// opportunity later. So we mark this render as having a ping.
|
|
workInProgressRootPingedLanes = mergeLanes(
|
|
workInProgressRootPingedLanes,
|
|
pingedLanes
|
|
);
|
|
}
|
|
}
|
|
|
|
ensureRootIsScheduled(root, eventTime);
|
|
}
|
|
|
|
function retryTimedOutBoundary(boundaryFiber, retryLane) {
|
|
// The boundary fiber (a Suspense component or SuspenseList component)
|
|
// previously was rendered in its fallback state. One of the promises that
|
|
// suspended it has resolved, which means at least part of the tree was
|
|
// likely unblocked. Try rendering again, at a new lanes.
|
|
if (retryLane === NoLane) {
|
|
// TODO: Assign this to `suspenseState.retryLane`? to avoid
|
|
// unnecessary entanglement?
|
|
retryLane = requestRetryLane(boundaryFiber);
|
|
} // TODO: Special case idle priority?
|
|
|
|
var eventTime = requestEventTime();
|
|
var root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane);
|
|
|
|
if (root !== null) {
|
|
markRootUpdated(root, retryLane, eventTime);
|
|
ensureRootIsScheduled(root, eventTime);
|
|
}
|
|
}
|
|
|
|
function retryDehydratedSuspenseBoundary(boundaryFiber) {
|
|
var suspenseState = boundaryFiber.memoizedState;
|
|
var retryLane = NoLane;
|
|
|
|
if (suspenseState !== null) {
|
|
retryLane = suspenseState.retryLane;
|
|
}
|
|
|
|
retryTimedOutBoundary(boundaryFiber, retryLane);
|
|
}
|
|
function resolveRetryWakeable(boundaryFiber, wakeable) {
|
|
var retryLane = NoLane; // Default
|
|
|
|
var retryCache;
|
|
|
|
switch (boundaryFiber.tag) {
|
|
case SuspenseComponent:
|
|
retryCache = boundaryFiber.stateNode;
|
|
var suspenseState = boundaryFiber.memoizedState;
|
|
|
|
if (suspenseState !== null) {
|
|
retryLane = suspenseState.retryLane;
|
|
}
|
|
|
|
break;
|
|
|
|
case SuspenseListComponent:
|
|
retryCache = boundaryFiber.stateNode;
|
|
break;
|
|
|
|
case OffscreenComponent: {
|
|
var instance = boundaryFiber.stateNode;
|
|
retryCache = instance._retryCache;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new Error(
|
|
"Pinged unknown suspense boundary type. " +
|
|
"This is probably a bug in React."
|
|
);
|
|
}
|
|
|
|
if (retryCache !== null) {
|
|
// The wakeable resolved, so we no longer need to memoize, because it will
|
|
// never be thrown again.
|
|
retryCache.delete(wakeable);
|
|
}
|
|
|
|
retryTimedOutBoundary(boundaryFiber, retryLane);
|
|
} // Computes the next Just Noticeable Difference (JND) boundary.
|
|
// The theory is that a person can't tell the difference between small differences in time.
|
|
// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
|
|
// difference in the experience. However, waiting for longer might mean that we can avoid
|
|
// showing an intermediate loading state. The longer we have already waited, the harder it
|
|
// is to tell small differences in time. Therefore, the longer we've already waited,
|
|
// the longer we can wait additionally. At some point we have to give up though.
|
|
// We pick a train model where the next boundary commits at a consistent schedule.
|
|
// These particular numbers are vague estimates. We expect to adjust them based on research.
|
|
|
|
function jnd(timeElapsed) {
|
|
return timeElapsed < 120
|
|
? 120
|
|
: timeElapsed < 480
|
|
? 480
|
|
: timeElapsed < 1080
|
|
? 1080
|
|
: timeElapsed < 1920
|
|
? 1920
|
|
: timeElapsed < 3000
|
|
? 3000
|
|
: timeElapsed < 4320
|
|
? 4320
|
|
: ceil(timeElapsed / 1960) * 1960;
|
|
}
|
|
|
|
function throwIfInfiniteUpdateLoopDetected() {
|
|
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
|
|
nestedUpdateCount = 0;
|
|
nestedPassiveUpdateCount = 0;
|
|
rootWithNestedUpdates = null;
|
|
rootWithPassiveNestedUpdates = null;
|
|
throw new Error(
|
|
"Maximum update depth exceeded. This can happen when a component " +
|
|
"repeatedly calls setState inside componentWillUpdate or " +
|
|
"componentDidUpdate. React limits the number of nested updates to " +
|
|
"prevent infinite loops."
|
|
);
|
|
}
|
|
|
|
{
|
|
if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
|
|
nestedPassiveUpdateCount = 0;
|
|
rootWithPassiveNestedUpdates = null;
|
|
|
|
error(
|
|
"Maximum update depth exceeded. This can happen when a component " +
|
|
"calls setState inside useEffect, but useEffect either doesn't " +
|
|
"have a dependency array, or one of the dependencies changes on " +
|
|
"every render."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function flushRenderPhaseStrictModeWarningsInDEV() {
|
|
{
|
|
ReactStrictModeWarnings.flushLegacyContextWarning();
|
|
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
|
|
}
|
|
}
|
|
|
|
function commitDoubleInvokeEffectsInDEV(root, hasPassiveEffects) {
|
|
{
|
|
{
|
|
legacyCommitDoubleInvokeEffectsInDEV(root.current, hasPassiveEffects);
|
|
}
|
|
}
|
|
}
|
|
|
|
function legacyCommitDoubleInvokeEffectsInDEV(fiber, hasPassiveEffects) {
|
|
// TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
|
|
// so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
|
|
// Maybe not a big deal since this is DEV only behavior.
|
|
setCurrentFiber(fiber);
|
|
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
|
|
|
|
if (hasPassiveEffects) {
|
|
invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectUnmountInDEV);
|
|
}
|
|
|
|
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
|
|
|
|
if (hasPassiveEffects) {
|
|
invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
|
|
}
|
|
|
|
resetCurrentFiber();
|
|
}
|
|
|
|
function invokeEffectsInDev(firstChild, fiberFlags, invokeEffectFn) {
|
|
var current = firstChild;
|
|
var subtreeRoot = null;
|
|
|
|
while (current != null) {
|
|
var primarySubtreeFlag = current.subtreeFlags & fiberFlags;
|
|
|
|
if (
|
|
current !== subtreeRoot &&
|
|
current.child != null &&
|
|
primarySubtreeFlag !== NoFlags
|
|
) {
|
|
current = current.child;
|
|
} else {
|
|
if ((current.flags & fiberFlags) !== NoFlags) {
|
|
invokeEffectFn(current);
|
|
}
|
|
|
|
if (current.sibling !== null) {
|
|
current = current.sibling;
|
|
} else {
|
|
current = subtreeRoot = current.return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var didWarnStateUpdateForNotYetMountedComponent = null;
|
|
function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
|
|
{
|
|
if ((executionContext & RenderContext) !== NoContext) {
|
|
// We let the other warning about render phase updates deal with this one.
|
|
return;
|
|
}
|
|
|
|
if (!(fiber.mode & ConcurrentMode)) {
|
|
return;
|
|
}
|
|
|
|
var tag = fiber.tag;
|
|
|
|
if (
|
|
tag !== IndeterminateComponent &&
|
|
tag !== HostRoot &&
|
|
tag !== ClassComponent &&
|
|
tag !== FunctionComponent &&
|
|
tag !== ForwardRef &&
|
|
tag !== MemoComponent &&
|
|
tag !== SimpleMemoComponent
|
|
) {
|
|
// Only warn for user-defined components, not internal ones like Suspense.
|
|
return;
|
|
} // We show the whole stack but dedupe on the top component's name because
|
|
// the problematic code almost always lies inside that component.
|
|
|
|
var componentName = getComponentNameFromFiber(fiber) || "ReactComponent";
|
|
|
|
if (didWarnStateUpdateForNotYetMountedComponent !== null) {
|
|
if (didWarnStateUpdateForNotYetMountedComponent.has(componentName)) {
|
|
return;
|
|
} // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
|
|
didWarnStateUpdateForNotYetMountedComponent.add(componentName);
|
|
} else {
|
|
didWarnStateUpdateForNotYetMountedComponent = new Set([componentName]);
|
|
}
|
|
|
|
var previousFiber = current;
|
|
|
|
try {
|
|
setCurrentFiber(fiber);
|
|
|
|
error(
|
|
"Can't perform a React state update on a component that hasn't mounted yet. " +
|
|
"This indicates that you have a side-effect in your render function that " +
|
|
"asynchronously later calls tries to update the component. Move this work to " +
|
|
"useEffect instead."
|
|
);
|
|
} finally {
|
|
if (previousFiber) {
|
|
setCurrentFiber(fiber);
|
|
} else {
|
|
resetCurrentFiber();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var beginWork$1;
|
|
|
|
{
|
|
beginWork$1 = beginWork;
|
|
}
|
|
|
|
var didWarnAboutUpdateInRender = false;
|
|
var didWarnAboutUpdateInRenderForAnotherComponent;
|
|
|
|
{
|
|
didWarnAboutUpdateInRenderForAnotherComponent = new Set();
|
|
}
|
|
|
|
function warnAboutRenderPhaseUpdatesInDEV(fiber) {
|
|
{
|
|
if (isRendering) {
|
|
switch (fiber.tag) {
|
|
case FunctionComponent:
|
|
case ForwardRef:
|
|
case SimpleMemoComponent: {
|
|
var renderingComponentName =
|
|
(workInProgress && getComponentNameFromFiber(workInProgress)) ||
|
|
"Unknown"; // Dedupe by the rendering component because it's the one that needs to be fixed.
|
|
|
|
var dedupeKey = renderingComponentName;
|
|
|
|
if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
|
|
didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
|
|
var setStateComponentName =
|
|
getComponentNameFromFiber(fiber) || "Unknown";
|
|
|
|
error(
|
|
"Cannot update a component (`%s`) while rendering a " +
|
|
"different component (`%s`). To locate the bad setState() call inside `%s`, " +
|
|
"follow the stack trace as described in https://reactjs.org/link/setstate-in-render",
|
|
setStateComponentName,
|
|
renderingComponentName,
|
|
renderingComponentName
|
|
);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ClassComponent: {
|
|
if (!didWarnAboutUpdateInRender) {
|
|
error(
|
|
"Cannot update during an existing state transition (such as " +
|
|
"within `render`). Render methods should be a pure " +
|
|
"function of props and state."
|
|
);
|
|
|
|
didWarnAboutUpdateInRender = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var fakeActCallbackNode = {}; // $FlowFixMe[missing-local-annot]
|
|
|
|
function scheduleCallback$2(priorityLevel, callback) {
|
|
{
|
|
// If we're currently inside an `act` scope, bypass Scheduler and push to
|
|
// the `act` queue instead.
|
|
var actQueue = ReactCurrentActQueue$2.current;
|
|
|
|
if (actQueue !== null) {
|
|
actQueue.push(callback);
|
|
return fakeActCallbackNode;
|
|
} else {
|
|
return scheduleCallback(priorityLevel, callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
function cancelCallback$1(callbackNode) {
|
|
if (callbackNode === fakeActCallbackNode) {
|
|
return;
|
|
} // In production, always call Scheduler. This function will be stripped out.
|
|
|
|
return cancelCallback(callbackNode);
|
|
}
|
|
|
|
function shouldForceFlushFallbacksInDEV() {
|
|
// Never force flush in production. This function should get stripped out.
|
|
return ReactCurrentActQueue$2.current !== null;
|
|
}
|
|
|
|
function warnIfUpdatesNotWrappedWithActDEV(fiber) {
|
|
{
|
|
if (fiber.mode & ConcurrentMode) {
|
|
if (!isConcurrentActEnvironment()) {
|
|
// Not in an act environment. No need to warn.
|
|
return;
|
|
}
|
|
} else {
|
|
// Legacy mode has additional cases where we suppress a warning.
|
|
if (!isLegacyActEnvironment()) {
|
|
// Not in an act environment. No need to warn.
|
|
return;
|
|
}
|
|
|
|
if (executionContext !== NoContext) {
|
|
// Legacy mode doesn't warn if the update is batched, i.e.
|
|
// batchedUpdates or flushSync.
|
|
return;
|
|
}
|
|
|
|
if (
|
|
fiber.tag !== FunctionComponent &&
|
|
fiber.tag !== ForwardRef &&
|
|
fiber.tag !== SimpleMemoComponent
|
|
) {
|
|
// For backwards compatibility with pre-hooks code, legacy mode only
|
|
// warns for updates that originate from a hook.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ReactCurrentActQueue$2.current === null) {
|
|
var previousFiber = current;
|
|
|
|
try {
|
|
setCurrentFiber(fiber);
|
|
|
|
error(
|
|
"An update to %s inside a test was not wrapped in act(...).\n\n" +
|
|
"When testing, code that causes React state updates should be " +
|
|
"wrapped into act(...):\n\n" +
|
|
"act(() => {\n" +
|
|
" /* fire events that update state */\n" +
|
|
"});\n" +
|
|
"/* assert on the output */\n\n" +
|
|
"This ensures that you're testing the behavior the user would see " +
|
|
"in the browser." +
|
|
" Learn more at https://reactjs.org/link/wrap-tests-with-act",
|
|
getComponentNameFromFiber(fiber)
|
|
);
|
|
} finally {
|
|
if (previousFiber) {
|
|
setCurrentFiber(fiber);
|
|
} else {
|
|
resetCurrentFiber();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function warnIfSuspenseResolutionNotWrappedWithActDEV(root) {
|
|
{
|
|
if (
|
|
root.tag !== LegacyRoot &&
|
|
isConcurrentActEnvironment() &&
|
|
ReactCurrentActQueue$2.current === null
|
|
) {
|
|
error(
|
|
"A suspended resource finished loading inside a test, but the event " +
|
|
"was not wrapped in act(...).\n\n" +
|
|
"When testing, code that resolves suspended data should be wrapped " +
|
|
"into act(...):\n\n" +
|
|
"act(() => {\n" +
|
|
" /* finish loading suspended data */\n" +
|
|
"});\n" +
|
|
"/* assert on the output */\n\n" +
|
|
"This ensures that you're testing the behavior the user would see " +
|
|
"in the browser." +
|
|
" Learn more at https://reactjs.org/link/wrap-tests-with-act"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function setIsRunningInsertionEffect(isRunning) {
|
|
{
|
|
isRunningInsertionEffect = isRunning;
|
|
}
|
|
}
|
|
|
|
/* eslint-disable react-internal/prod-error-codes */
|
|
// Used by React Refresh runtime through DevTools Global Hook.
|
|
|
|
var resolveFamily = null;
|
|
var failedBoundaries = null;
|
|
var setRefreshHandler = function(handler) {
|
|
{
|
|
resolveFamily = handler;
|
|
}
|
|
};
|
|
function resolveFunctionForHotReloading(type) {
|
|
{
|
|
if (resolveFamily === null) {
|
|
// Hot reloading is disabled.
|
|
return type;
|
|
}
|
|
|
|
var family = resolveFamily(type);
|
|
|
|
if (family === undefined) {
|
|
return type;
|
|
} // Use the latest known implementation.
|
|
|
|
return family.current;
|
|
}
|
|
}
|
|
function resolveClassForHotReloading(type) {
|
|
// No implementation differences.
|
|
return resolveFunctionForHotReloading(type);
|
|
}
|
|
function resolveForwardRefForHotReloading(type) {
|
|
{
|
|
if (resolveFamily === null) {
|
|
// Hot reloading is disabled.
|
|
return type;
|
|
}
|
|
|
|
var family = resolveFamily(type);
|
|
|
|
if (family === undefined) {
|
|
// Check if we're dealing with a real forwardRef. Don't want to crash early.
|
|
if (
|
|
type !== null &&
|
|
type !== undefined &&
|
|
typeof type.render === "function"
|
|
) {
|
|
// ForwardRef is special because its resolved .type is an object,
|
|
// but it's possible that we only have its inner render function in the map.
|
|
// If that inner render function is different, we'll build a new forwardRef type.
|
|
var currentRender = resolveFunctionForHotReloading(type.render);
|
|
|
|
if (type.render !== currentRender) {
|
|
var syntheticType = {
|
|
$$typeof: REACT_FORWARD_REF_TYPE,
|
|
render: currentRender
|
|
};
|
|
|
|
if (type.displayName !== undefined) {
|
|
syntheticType.displayName = type.displayName;
|
|
}
|
|
|
|
return syntheticType;
|
|
}
|
|
}
|
|
|
|
return type;
|
|
} // Use the latest known implementation.
|
|
|
|
return family.current;
|
|
}
|
|
}
|
|
function isCompatibleFamilyForHotReloading(fiber, element) {
|
|
{
|
|
if (resolveFamily === null) {
|
|
// Hot reloading is disabled.
|
|
return false;
|
|
}
|
|
|
|
var prevType = fiber.elementType;
|
|
var nextType = element.type; // If we got here, we know types aren't === equal.
|
|
|
|
var needsCompareFamilies = false;
|
|
var $$typeofNextType =
|
|
typeof nextType === "object" && nextType !== null
|
|
? nextType.$$typeof
|
|
: null;
|
|
|
|
switch (fiber.tag) {
|
|
case ClassComponent: {
|
|
if (typeof nextType === "function") {
|
|
needsCompareFamilies = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case FunctionComponent: {
|
|
if (typeof nextType === "function") {
|
|
needsCompareFamilies = true;
|
|
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
|
|
// We don't know the inner type yet.
|
|
// We're going to assume that the lazy inner type is stable,
|
|
// and so it is sufficient to avoid reconciling it away.
|
|
// We're not going to unwrap or actually use the new lazy type.
|
|
needsCompareFamilies = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ForwardRef: {
|
|
if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
|
|
needsCompareFamilies = true;
|
|
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
|
|
needsCompareFamilies = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case MemoComponent:
|
|
case SimpleMemoComponent: {
|
|
if ($$typeofNextType === REACT_MEMO_TYPE) {
|
|
// TODO: if it was but can no longer be simple,
|
|
// we shouldn't set this.
|
|
needsCompareFamilies = true;
|
|
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
|
|
needsCompareFamilies = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
} // Check if both types have a family and it's the same one.
|
|
|
|
if (needsCompareFamilies) {
|
|
// Note: memo() and forwardRef() we'll compare outer rather than inner type.
|
|
// This means both of them need to be registered to preserve state.
|
|
// If we unwrapped and compared the inner types for wrappers instead,
|
|
// then we would risk falsely saying two separate memo(Foo)
|
|
// calls are equivalent because they wrap the same Foo function.
|
|
var prevFamily = resolveFamily(prevType); // $FlowFixMe[not-a-function] found when upgrading Flow
|
|
|
|
if (prevFamily !== undefined && prevFamily === resolveFamily(nextType)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
function markFailedErrorBoundaryForHotReloading(fiber) {
|
|
{
|
|
if (resolveFamily === null) {
|
|
// Hot reloading is disabled.
|
|
return;
|
|
}
|
|
|
|
if (typeof WeakSet !== "function") {
|
|
return;
|
|
}
|
|
|
|
if (failedBoundaries === null) {
|
|
failedBoundaries = new WeakSet();
|
|
}
|
|
|
|
failedBoundaries.add(fiber);
|
|
}
|
|
}
|
|
var scheduleRefresh = function(root, update) {
|
|
{
|
|
if (resolveFamily === null) {
|
|
// Hot reloading is disabled.
|
|
return;
|
|
}
|
|
|
|
var staleFamilies = update.staleFamilies,
|
|
updatedFamilies = update.updatedFamilies;
|
|
flushPassiveEffects();
|
|
flushSync(function() {
|
|
scheduleFibersWithFamiliesRecursively(
|
|
root.current,
|
|
updatedFamilies,
|
|
staleFamilies
|
|
);
|
|
});
|
|
}
|
|
};
|
|
var scheduleRoot = function(root, element) {
|
|
{
|
|
if (root.context !== emptyContextObject) {
|
|
// Super edge case: root has a legacy _renderSubtree context
|
|
// but we don't know the parentComponent so we can't pass it.
|
|
// Just ignore. We'll delete this with _renderSubtree code path later.
|
|
return;
|
|
}
|
|
|
|
flushPassiveEffects();
|
|
flushSync(function() {
|
|
updateContainer(element, root, null, null);
|
|
});
|
|
}
|
|
};
|
|
|
|
function scheduleFibersWithFamiliesRecursively(
|
|
fiber,
|
|
updatedFamilies,
|
|
staleFamilies
|
|
) {
|
|
{
|
|
var alternate = fiber.alternate,
|
|
child = fiber.child,
|
|
sibling = fiber.sibling,
|
|
tag = fiber.tag,
|
|
type = fiber.type;
|
|
var candidateType = null;
|
|
|
|
switch (tag) {
|
|
case FunctionComponent:
|
|
case SimpleMemoComponent:
|
|
case ClassComponent:
|
|
candidateType = type;
|
|
break;
|
|
|
|
case ForwardRef:
|
|
candidateType = type.render;
|
|
break;
|
|
}
|
|
|
|
if (resolveFamily === null) {
|
|
throw new Error("Expected resolveFamily to be set during hot reload.");
|
|
}
|
|
|
|
var needsRender = false;
|
|
var needsRemount = false;
|
|
|
|
if (candidateType !== null) {
|
|
var family = resolveFamily(candidateType);
|
|
|
|
if (family !== undefined) {
|
|
if (staleFamilies.has(family)) {
|
|
needsRemount = true;
|
|
} else if (updatedFamilies.has(family)) {
|
|
if (tag === ClassComponent) {
|
|
needsRemount = true;
|
|
} else {
|
|
needsRender = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failedBoundaries !== null) {
|
|
if (
|
|
failedBoundaries.has(fiber) || // $FlowFixMe[incompatible-use] found when upgrading Flow
|
|
(alternate !== null && failedBoundaries.has(alternate))
|
|
) {
|
|
needsRemount = true;
|
|
}
|
|
}
|
|
|
|
if (needsRemount) {
|
|
fiber._debugNeedsRemount = true;
|
|
}
|
|
|
|
if (needsRemount || needsRender) {
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
}
|
|
|
|
if (child !== null && !needsRemount) {
|
|
scheduleFibersWithFamiliesRecursively(
|
|
child,
|
|
updatedFamilies,
|
|
staleFamilies
|
|
);
|
|
}
|
|
|
|
if (sibling !== null) {
|
|
scheduleFibersWithFamiliesRecursively(
|
|
sibling,
|
|
updatedFamilies,
|
|
staleFamilies
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
var findHostInstancesForRefresh = function(root, families) {
|
|
{
|
|
var hostInstances = new Set();
|
|
var types = new Set(
|
|
families.map(function(family) {
|
|
return family.current;
|
|
})
|
|
);
|
|
findHostInstancesForMatchingFibersRecursively(
|
|
root.current,
|
|
types,
|
|
hostInstances
|
|
);
|
|
return hostInstances;
|
|
}
|
|
};
|
|
|
|
function findHostInstancesForMatchingFibersRecursively(
|
|
fiber,
|
|
types,
|
|
hostInstances
|
|
) {
|
|
{
|
|
var child = fiber.child,
|
|
sibling = fiber.sibling,
|
|
tag = fiber.tag,
|
|
type = fiber.type;
|
|
var candidateType = null;
|
|
|
|
switch (tag) {
|
|
case FunctionComponent:
|
|
case SimpleMemoComponent:
|
|
case ClassComponent:
|
|
candidateType = type;
|
|
break;
|
|
|
|
case ForwardRef:
|
|
candidateType = type.render;
|
|
break;
|
|
}
|
|
|
|
var didMatch = false;
|
|
|
|
if (candidateType !== null) {
|
|
if (types.has(candidateType)) {
|
|
didMatch = true;
|
|
}
|
|
}
|
|
|
|
if (didMatch) {
|
|
// We have a match. This only drills down to the closest host components.
|
|
// There's no need to search deeper because for the purpose of giving
|
|
// visual feedback, "flashing" outermost parent rectangles is sufficient.
|
|
findHostInstancesForFiberShallowly(fiber, hostInstances);
|
|
} else {
|
|
// If there's no match, maybe there will be one further down in the child tree.
|
|
if (child !== null) {
|
|
findHostInstancesForMatchingFibersRecursively(
|
|
child,
|
|
types,
|
|
hostInstances
|
|
);
|
|
}
|
|
}
|
|
|
|
if (sibling !== null) {
|
|
findHostInstancesForMatchingFibersRecursively(
|
|
sibling,
|
|
types,
|
|
hostInstances
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function findHostInstancesForFiberShallowly(fiber, hostInstances) {
|
|
{
|
|
var foundHostInstances = findChildHostInstancesForFiberShallowly(
|
|
fiber,
|
|
hostInstances
|
|
);
|
|
|
|
if (foundHostInstances) {
|
|
return;
|
|
} // If we didn't find any host children, fallback to closest host parent.
|
|
|
|
var node = fiber;
|
|
|
|
while (true) {
|
|
switch (node.tag) {
|
|
case HostSingleton:
|
|
case HostComponent:
|
|
hostInstances.add(node.stateNode);
|
|
return;
|
|
|
|
case HostPortal:
|
|
hostInstances.add(node.stateNode.containerInfo);
|
|
return;
|
|
|
|
case HostRoot:
|
|
hostInstances.add(node.stateNode.containerInfo);
|
|
return;
|
|
}
|
|
|
|
if (node.return === null) {
|
|
throw new Error("Expected to reach root first.");
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function findChildHostInstancesForFiberShallowly(fiber, hostInstances) {
|
|
{
|
|
var node = fiber;
|
|
var foundHostInstances = false;
|
|
|
|
while (true) {
|
|
if (
|
|
node.tag === HostComponent ||
|
|
node.tag === HostResource ||
|
|
node.tag === HostSingleton
|
|
) {
|
|
// We got a match.
|
|
foundHostInstances = true;
|
|
hostInstances.add(node.stateNode); // There may still be more, so keep searching.
|
|
} else if (node.child !== null) {
|
|
node.child.return = node;
|
|
node = node.child;
|
|
continue;
|
|
}
|
|
|
|
if (node === fiber) {
|
|
return foundHostInstances;
|
|
}
|
|
|
|
while (node.sibling === null) {
|
|
if (node.return === null || node.return === fiber) {
|
|
return foundHostInstances;
|
|
}
|
|
|
|
node = node.return;
|
|
}
|
|
|
|
node.sibling.return = node.return;
|
|
node = node.sibling;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
var hasBadMapPolyfill;
|
|
|
|
{
|
|
hasBadMapPolyfill = false;
|
|
|
|
try {
|
|
var nonExtensibleObject = Object.preventExtensions({});
|
|
/* eslint-disable no-new */
|
|
|
|
new Map([[nonExtensibleObject, null]]);
|
|
new Set([nonExtensibleObject]);
|
|
/* eslint-enable no-new */
|
|
} catch (e) {
|
|
// TODO: Consider warning about bad polyfills
|
|
hasBadMapPolyfill = true;
|
|
}
|
|
}
|
|
|
|
function FiberNode(tag, pendingProps, key, mode) {
|
|
// Instance
|
|
this.tag = tag;
|
|
this.key = key;
|
|
this.elementType = null;
|
|
this.type = null;
|
|
this.stateNode = null; // Fiber
|
|
|
|
this.return = null;
|
|
this.child = null;
|
|
this.sibling = null;
|
|
this.index = 0;
|
|
this.ref = null;
|
|
this.refCleanup = null;
|
|
this.pendingProps = pendingProps;
|
|
this.memoizedProps = null;
|
|
this.updateQueue = null;
|
|
this.memoizedState = null;
|
|
this.dependencies = null;
|
|
this.mode = mode; // Effects
|
|
|
|
this.flags = NoFlags;
|
|
this.subtreeFlags = NoFlags;
|
|
this.deletions = null;
|
|
this.lanes = NoLanes;
|
|
this.childLanes = NoLanes;
|
|
this.alternate = null;
|
|
|
|
{
|
|
// This isn't directly used but is handy for debugging internals:
|
|
this._debugSource = null;
|
|
this._debugOwner = null;
|
|
this._debugNeedsRemount = false;
|
|
this._debugHookTypes = null;
|
|
|
|
if (!hasBadMapPolyfill && typeof Object.preventExtensions === "function") {
|
|
Object.preventExtensions(this);
|
|
}
|
|
}
|
|
} // This is a constructor function, rather than a POJO constructor, still
|
|
// please ensure we do the following:
|
|
// 1) Nobody should add any instance methods on this. Instance methods can be
|
|
// more difficult to predict when they get optimized and they are almost
|
|
// never inlined properly in static compilers.
|
|
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
|
|
// always know when it is a fiber.
|
|
// 3) We might want to experiment with using numeric keys since they are easier
|
|
// to optimize in a non-JIT environment.
|
|
// 4) We can easily go from a constructor to a createFiber object literal if that
|
|
// is faster.
|
|
// 5) It should be easy to port this to a C struct and keep a C implementation
|
|
// compatible.
|
|
|
|
var createFiber = function(tag, pendingProps, key, mode) {
|
|
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
|
|
return new FiberNode(tag, pendingProps, key, mode);
|
|
};
|
|
|
|
function shouldConstruct$1(Component) {
|
|
var prototype = Component.prototype;
|
|
return !!(prototype && prototype.isReactComponent);
|
|
}
|
|
|
|
function isSimpleFunctionComponent(type) {
|
|
return (
|
|
typeof type === "function" &&
|
|
!shouldConstruct$1(type) &&
|
|
type.defaultProps === undefined
|
|
);
|
|
}
|
|
function resolveLazyComponentTag(Component) {
|
|
if (typeof Component === "function") {
|
|
return shouldConstruct$1(Component) ? ClassComponent : FunctionComponent;
|
|
} else if (Component !== undefined && Component !== null) {
|
|
var $$typeof = Component.$$typeof;
|
|
|
|
if ($$typeof === REACT_FORWARD_REF_TYPE) {
|
|
return ForwardRef;
|
|
}
|
|
|
|
if ($$typeof === REACT_MEMO_TYPE) {
|
|
return MemoComponent;
|
|
}
|
|
}
|
|
|
|
return IndeterminateComponent;
|
|
} // This is used to create an alternate fiber to do work on.
|
|
|
|
function createWorkInProgress(current, pendingProps) {
|
|
var workInProgress = current.alternate;
|
|
|
|
if (workInProgress === null) {
|
|
// We use a double buffering pooling technique because we know that we'll
|
|
// only ever need at most two versions of a tree. We pool the "other" unused
|
|
// node that we're free to reuse. This is lazily created to avoid allocating
|
|
// extra objects for things that are never updated. It also allow us to
|
|
// reclaim the extra memory if needed.
|
|
workInProgress = createFiber(
|
|
current.tag,
|
|
pendingProps,
|
|
current.key,
|
|
current.mode
|
|
);
|
|
workInProgress.elementType = current.elementType;
|
|
workInProgress.type = current.type;
|
|
workInProgress.stateNode = current.stateNode;
|
|
|
|
{
|
|
// DEV-only fields
|
|
workInProgress._debugSource = current._debugSource;
|
|
workInProgress._debugOwner = current._debugOwner;
|
|
workInProgress._debugHookTypes = current._debugHookTypes;
|
|
}
|
|
|
|
workInProgress.alternate = current;
|
|
current.alternate = workInProgress;
|
|
} else {
|
|
workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.
|
|
|
|
workInProgress.type = current.type; // We already have an alternate.
|
|
// Reset the effect tag.
|
|
|
|
workInProgress.flags = NoFlags; // The effects are no longer valid.
|
|
|
|
workInProgress.subtreeFlags = NoFlags;
|
|
workInProgress.deletions = null;
|
|
} // Reset all effects except static ones.
|
|
// Static effects are not specific to a render.
|
|
|
|
workInProgress.flags = current.flags & StaticMask;
|
|
workInProgress.childLanes = current.childLanes;
|
|
workInProgress.lanes = current.lanes;
|
|
workInProgress.child = current.child;
|
|
workInProgress.memoizedProps = current.memoizedProps;
|
|
workInProgress.memoizedState = current.memoizedState;
|
|
workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
|
|
// it cannot be shared with the current fiber.
|
|
|
|
var currentDependencies = current.dependencies;
|
|
workInProgress.dependencies =
|
|
currentDependencies === null
|
|
? null
|
|
: {
|
|
lanes: currentDependencies.lanes,
|
|
firstContext: currentDependencies.firstContext
|
|
}; // These will be overridden during the parent's reconciliation
|
|
|
|
workInProgress.sibling = current.sibling;
|
|
workInProgress.index = current.index;
|
|
workInProgress.ref = current.ref;
|
|
workInProgress.refCleanup = current.refCleanup;
|
|
|
|
{
|
|
workInProgress._debugNeedsRemount = current._debugNeedsRemount;
|
|
|
|
switch (workInProgress.tag) {
|
|
case IndeterminateComponent:
|
|
case FunctionComponent:
|
|
case SimpleMemoComponent:
|
|
workInProgress.type = resolveFunctionForHotReloading(current.type);
|
|
break;
|
|
|
|
case ClassComponent:
|
|
workInProgress.type = resolveClassForHotReloading(current.type);
|
|
break;
|
|
|
|
case ForwardRef:
|
|
workInProgress.type = resolveForwardRefForHotReloading(current.type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return workInProgress;
|
|
} // Used to reuse a Fiber for a second pass.
|
|
|
|
function resetWorkInProgress(workInProgress, renderLanes) {
|
|
// This resets the Fiber to what createFiber or createWorkInProgress would
|
|
// have set the values to before during the first pass. Ideally this wouldn't
|
|
// be necessary but unfortunately many code paths reads from the workInProgress
|
|
// when they should be reading from current and writing to workInProgress.
|
|
// We assume pendingProps, index, key, ref, return are still untouched to
|
|
// avoid doing another reconciliation.
|
|
// Reset the effect flags but keep any Placement tags, since that's something
|
|
// that child fiber is setting, not the reconciliation.
|
|
workInProgress.flags &= StaticMask | Placement; // The effects are no longer valid.
|
|
|
|
var current = workInProgress.alternate;
|
|
|
|
if (current === null) {
|
|
// Reset to createFiber's initial values.
|
|
workInProgress.childLanes = NoLanes;
|
|
workInProgress.lanes = renderLanes;
|
|
workInProgress.child = null;
|
|
workInProgress.subtreeFlags = NoFlags;
|
|
workInProgress.memoizedProps = null;
|
|
workInProgress.memoizedState = null;
|
|
workInProgress.updateQueue = null;
|
|
workInProgress.dependencies = null;
|
|
workInProgress.stateNode = null;
|
|
} else {
|
|
// Reset to the cloned values that createWorkInProgress would've.
|
|
workInProgress.childLanes = current.childLanes;
|
|
workInProgress.lanes = current.lanes;
|
|
workInProgress.child = current.child;
|
|
workInProgress.subtreeFlags = NoFlags;
|
|
workInProgress.deletions = null;
|
|
workInProgress.memoizedProps = current.memoizedProps;
|
|
workInProgress.memoizedState = current.memoizedState;
|
|
workInProgress.updateQueue = current.updateQueue; // Needed because Blocks store data on type.
|
|
|
|
workInProgress.type = current.type; // Clone the dependencies object. This is mutated during the render phase, so
|
|
// it cannot be shared with the current fiber.
|
|
|
|
var currentDependencies = current.dependencies;
|
|
workInProgress.dependencies =
|
|
currentDependencies === null
|
|
? null
|
|
: {
|
|
lanes: currentDependencies.lanes,
|
|
firstContext: currentDependencies.firstContext
|
|
};
|
|
}
|
|
|
|
return workInProgress;
|
|
}
|
|
function createHostRootFiber(
|
|
tag,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride
|
|
) {
|
|
var mode;
|
|
|
|
if (tag === ConcurrentRoot) {
|
|
mode = ConcurrentMode;
|
|
|
|
if (isStrictMode === true || createRootStrictEffectsByDefault) {
|
|
mode |= StrictLegacyMode | StrictEffectsMode;
|
|
}
|
|
|
|
if (
|
|
// We only use this flag for our repo tests to check both behaviors.
|
|
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
|
|
// Only for internal experiments.
|
|
concurrentUpdatesByDefaultOverride
|
|
) {
|
|
mode |= ConcurrentUpdatesByDefaultMode;
|
|
}
|
|
} else {
|
|
mode = NoMode;
|
|
}
|
|
|
|
return createFiber(HostRoot, null, null, mode);
|
|
}
|
|
function createFiberFromTypeAndProps(
|
|
type, // React$ElementType
|
|
key,
|
|
pendingProps,
|
|
owner,
|
|
mode,
|
|
lanes
|
|
) {
|
|
var fiberTag = IndeterminateComponent; // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
|
|
|
|
var resolvedType = type;
|
|
|
|
if (typeof type === "function") {
|
|
if (shouldConstruct$1(type)) {
|
|
fiberTag = ClassComponent;
|
|
|
|
{
|
|
resolvedType = resolveClassForHotReloading(resolvedType);
|
|
}
|
|
} else {
|
|
{
|
|
resolvedType = resolveFunctionForHotReloading(resolvedType);
|
|
}
|
|
}
|
|
} else if (typeof type === "string") {
|
|
{
|
|
var hostContext = getHostContext();
|
|
fiberTag = isHostResourceType(type, pendingProps, hostContext)
|
|
? HostResource
|
|
: isHostSingletonType(type)
|
|
? HostSingleton
|
|
: HostComponent;
|
|
}
|
|
} else {
|
|
getTag: switch (type) {
|
|
case REACT_FRAGMENT_TYPE:
|
|
return createFiberFromFragment(pendingProps.children, mode, lanes, key);
|
|
|
|
case REACT_STRICT_MODE_TYPE:
|
|
fiberTag = Mode;
|
|
mode |= StrictLegacyMode;
|
|
|
|
if ((mode & ConcurrentMode) !== NoMode) {
|
|
// Strict effects should never run on legacy roots
|
|
mode |= StrictEffectsMode;
|
|
}
|
|
|
|
break;
|
|
|
|
case REACT_PROFILER_TYPE:
|
|
return createFiberFromProfiler(pendingProps, mode, lanes, key);
|
|
|
|
case REACT_SUSPENSE_TYPE:
|
|
return createFiberFromSuspense(pendingProps, mode, lanes, key);
|
|
|
|
case REACT_SUSPENSE_LIST_TYPE:
|
|
return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
|
|
|
|
case REACT_OFFSCREEN_TYPE:
|
|
return createFiberFromOffscreen(pendingProps, mode, lanes, key);
|
|
|
|
case REACT_LEGACY_HIDDEN_TYPE:
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case REACT_SCOPE_TYPE: {
|
|
return createFiberFromScope(type, pendingProps, mode, lanes, key);
|
|
}
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case REACT_CACHE_TYPE: {
|
|
return createFiberFromCache(pendingProps, mode, lanes, key);
|
|
}
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case REACT_TRACING_MARKER_TYPE:
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case REACT_DEBUG_TRACING_MODE_TYPE:
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
default: {
|
|
if (typeof type === "object" && type !== null) {
|
|
switch (type.$$typeof) {
|
|
case REACT_PROVIDER_TYPE:
|
|
fiberTag = ContextProvider;
|
|
break getTag;
|
|
|
|
case REACT_CONTEXT_TYPE:
|
|
// This is a consumer
|
|
fiberTag = ContextConsumer;
|
|
break getTag;
|
|
|
|
case REACT_FORWARD_REF_TYPE:
|
|
fiberTag = ForwardRef;
|
|
|
|
{
|
|
resolvedType = resolveForwardRefForHotReloading(resolvedType);
|
|
}
|
|
|
|
break getTag;
|
|
|
|
case REACT_MEMO_TYPE:
|
|
fiberTag = MemoComponent;
|
|
break getTag;
|
|
|
|
case REACT_LAZY_TYPE:
|
|
fiberTag = LazyComponent;
|
|
resolvedType = null;
|
|
break getTag;
|
|
}
|
|
}
|
|
|
|
var info = "";
|
|
|
|
{
|
|
if (
|
|
type === undefined ||
|
|
(typeof type === "object" &&
|
|
type !== null &&
|
|
Object.keys(type).length === 0)
|
|
) {
|
|
info +=
|
|
" You likely forgot to export your component from the file " +
|
|
"it's defined in, or you might have mixed up default and " +
|
|
"named imports.";
|
|
}
|
|
|
|
var ownerName = owner ? getComponentNameFromFiber(owner) : null;
|
|
|
|
if (ownerName) {
|
|
info += "\n\nCheck the render method of `" + ownerName + "`.";
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
"Element type is invalid: expected a string (for built-in " +
|
|
"components) or a class/function (for composite components) " +
|
|
("but got: " + (type == null ? type : typeof type) + "." + info)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
var fiber = createFiber(fiberTag, pendingProps, key, mode);
|
|
fiber.elementType = type;
|
|
fiber.type = resolvedType;
|
|
fiber.lanes = lanes;
|
|
|
|
{
|
|
fiber._debugOwner = owner;
|
|
}
|
|
|
|
return fiber;
|
|
}
|
|
function createFiberFromElement(element, mode, lanes) {
|
|
var owner = null;
|
|
|
|
{
|
|
owner = element._owner;
|
|
}
|
|
|
|
var type = element.type;
|
|
var key = element.key;
|
|
var pendingProps = element.props;
|
|
var fiber = createFiberFromTypeAndProps(
|
|
type,
|
|
key,
|
|
pendingProps,
|
|
owner,
|
|
mode,
|
|
lanes
|
|
);
|
|
|
|
{
|
|
fiber._debugSource = element._source;
|
|
fiber._debugOwner = element._owner;
|
|
}
|
|
|
|
return fiber;
|
|
}
|
|
function createFiberFromFragment(elements, mode, lanes, key) {
|
|
var fiber = createFiber(Fragment, elements, key, mode);
|
|
fiber.lanes = lanes;
|
|
return fiber;
|
|
}
|
|
|
|
function createFiberFromScope(scope, pendingProps, mode, lanes, key) {
|
|
var fiber = createFiber(ScopeComponent, pendingProps, key, mode);
|
|
fiber.type = scope;
|
|
fiber.elementType = scope;
|
|
fiber.lanes = lanes;
|
|
return fiber;
|
|
}
|
|
|
|
function createFiberFromProfiler(pendingProps, mode, lanes, key) {
|
|
{
|
|
if (typeof pendingProps.id !== "string") {
|
|
error(
|
|
'Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.',
|
|
typeof pendingProps.id
|
|
);
|
|
}
|
|
}
|
|
|
|
var fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
|
|
fiber.elementType = REACT_PROFILER_TYPE;
|
|
fiber.lanes = lanes;
|
|
|
|
return fiber;
|
|
}
|
|
|
|
function createFiberFromSuspense(pendingProps, mode, lanes, key) {
|
|
var fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
|
|
fiber.elementType = REACT_SUSPENSE_TYPE;
|
|
fiber.lanes = lanes;
|
|
return fiber;
|
|
}
|
|
function createFiberFromSuspenseList(pendingProps, mode, lanes, key) {
|
|
var fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
|
|
fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
|
|
fiber.lanes = lanes;
|
|
return fiber;
|
|
}
|
|
function createFiberFromOffscreen(pendingProps, mode, lanes, key) {
|
|
var fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
|
|
fiber.elementType = REACT_OFFSCREEN_TYPE;
|
|
fiber.lanes = lanes;
|
|
var primaryChildInstance = {
|
|
_visibility: OffscreenVisible,
|
|
_pendingVisibility: OffscreenVisible,
|
|
_pendingMarkers: null,
|
|
_retryCache: null,
|
|
_transitions: null,
|
|
_current: null,
|
|
detach: function() {
|
|
return detachOffscreenInstance(primaryChildInstance);
|
|
},
|
|
attach: function() {
|
|
return attachOffscreenInstance(primaryChildInstance);
|
|
}
|
|
};
|
|
fiber.stateNode = primaryChildInstance;
|
|
return fiber;
|
|
}
|
|
function createFiberFromCache(pendingProps, mode, lanes, key) {
|
|
var fiber = createFiber(CacheComponent, pendingProps, key, mode);
|
|
fiber.elementType = REACT_CACHE_TYPE;
|
|
fiber.lanes = lanes;
|
|
return fiber;
|
|
}
|
|
function createFiberFromText(content, mode, lanes) {
|
|
var fiber = createFiber(HostText, content, null, mode);
|
|
fiber.lanes = lanes;
|
|
return fiber;
|
|
}
|
|
function createFiberFromHostInstanceForDeletion() {
|
|
var fiber = createFiber(HostComponent, null, null, NoMode);
|
|
fiber.elementType = "DELETED";
|
|
return fiber;
|
|
}
|
|
function createFiberFromDehydratedFragment(dehydratedNode) {
|
|
var fiber = createFiber(DehydratedFragment, null, null, NoMode);
|
|
fiber.stateNode = dehydratedNode;
|
|
return fiber;
|
|
}
|
|
function createFiberFromPortal(portal, mode, lanes) {
|
|
var pendingProps = portal.children !== null ? portal.children : [];
|
|
var fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
|
|
fiber.lanes = lanes;
|
|
fiber.stateNode = {
|
|
containerInfo: portal.containerInfo,
|
|
pendingChildren: null,
|
|
// Used by persistent updates
|
|
implementation: portal.implementation
|
|
};
|
|
return fiber;
|
|
} // Used for stashing WIP properties to replay failed work in DEV.
|
|
|
|
function FiberRootNode(
|
|
containerInfo, // $FlowFixMe[missing-local-annot]
|
|
tag,
|
|
hydrate,
|
|
identifierPrefix,
|
|
onRecoverableError
|
|
) {
|
|
this.tag = tag;
|
|
this.containerInfo = containerInfo;
|
|
this.pendingChildren = null;
|
|
this.current = null;
|
|
this.pingCache = null;
|
|
this.finishedWork = null;
|
|
this.timeoutHandle = noTimeout;
|
|
this.context = null;
|
|
this.pendingContext = null;
|
|
this.callbackNode = null;
|
|
this.callbackPriority = NoLane;
|
|
this.eventTimes = createLaneMap(NoLanes);
|
|
this.expirationTimes = createLaneMap(NoTimestamp);
|
|
this.pendingLanes = NoLanes;
|
|
this.suspendedLanes = NoLanes;
|
|
this.pingedLanes = NoLanes;
|
|
this.expiredLanes = NoLanes;
|
|
this.mutableReadLanes = NoLanes;
|
|
this.finishedLanes = NoLanes;
|
|
this.errorRecoveryDisabledLanes = NoLanes;
|
|
this.entangledLanes = NoLanes;
|
|
this.entanglements = createLaneMap(NoLanes);
|
|
this.hiddenUpdates = createLaneMap(null);
|
|
this.identifierPrefix = identifierPrefix;
|
|
this.onRecoverableError = onRecoverableError;
|
|
|
|
{
|
|
this.pooledCache = null;
|
|
this.pooledCacheLanes = NoLanes;
|
|
}
|
|
|
|
{
|
|
this.mutableSourceEagerHydrationData = null;
|
|
}
|
|
|
|
{
|
|
this.hydrationCallbacks = null;
|
|
}
|
|
|
|
this.incompleteTransitions = new Map();
|
|
|
|
{
|
|
switch (tag) {
|
|
case ConcurrentRoot:
|
|
this._debugRootType = hydrate ? "hydrateRoot()" : "createRoot()";
|
|
break;
|
|
|
|
case LegacyRoot:
|
|
this._debugRootType = hydrate ? "hydrate()" : "render()";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createFiberRoot(
|
|
containerInfo,
|
|
tag,
|
|
hydrate,
|
|
initialChildren,
|
|
hydrationCallbacks,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride, // TODO: We have several of these arguments that are conceptually part of the
|
|
// host config, but because they are passed in at runtime, we have to thread
|
|
// them through the root constructor. Perhaps we should put them all into a
|
|
// single type, like a DynamicHostConfig that is defined by the renderer.
|
|
identifierPrefix,
|
|
onRecoverableError,
|
|
transitionCallbacks
|
|
) {
|
|
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
var root = new FiberRootNode(
|
|
containerInfo,
|
|
tag,
|
|
hydrate,
|
|
identifierPrefix,
|
|
onRecoverableError
|
|
);
|
|
|
|
{
|
|
root.hydrationCallbacks = hydrationCallbacks;
|
|
}
|
|
// stateNode is any.
|
|
|
|
var uninitializedFiber = createHostRootFiber(
|
|
tag,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride
|
|
);
|
|
root.current = uninitializedFiber;
|
|
uninitializedFiber.stateNode = root;
|
|
|
|
{
|
|
var initialCache = createCache();
|
|
retainCache(initialCache); // The pooledCache is a fresh cache instance that is used temporarily
|
|
// for newly mounted boundaries during a render. In general, the
|
|
// pooledCache is always cleared from the root at the end of a render:
|
|
// it is either released when render commits, or moved to an Offscreen
|
|
// component if rendering suspends. Because the lifetime of the pooled
|
|
// cache is distinct from the main memoizedState.cache, it must be
|
|
// retained separately.
|
|
|
|
root.pooledCache = initialCache;
|
|
retainCache(initialCache);
|
|
var initialState = {
|
|
element: initialChildren,
|
|
isDehydrated: hydrate,
|
|
cache: initialCache
|
|
};
|
|
uninitializedFiber.memoizedState = initialState;
|
|
}
|
|
|
|
initializeUpdateQueue(uninitializedFiber);
|
|
return root;
|
|
}
|
|
|
|
var ReactVersion = "18.3.0-www-modern-379dd741e-20230113";
|
|
|
|
function createPortal(
|
|
children,
|
|
containerInfo, // TODO: figure out the API for cross-renderer implementation.
|
|
implementation
|
|
) {
|
|
var key =
|
|
arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
|
|
|
|
{
|
|
checkKeyStringCoercion(key);
|
|
}
|
|
|
|
return {
|
|
// This tag allow us to uniquely identify this as a React Portal
|
|
$$typeof: REACT_PORTAL_TYPE,
|
|
key: key == null ? null : "" + key,
|
|
children: children,
|
|
containerInfo: containerInfo,
|
|
implementation: implementation
|
|
};
|
|
}
|
|
|
|
// Might add PROFILE later.
|
|
|
|
var didWarnAboutNestedUpdates;
|
|
|
|
{
|
|
didWarnAboutNestedUpdates = false;
|
|
}
|
|
|
|
function getContextForSubtree(parentComponent) {
|
|
if (!parentComponent) {
|
|
return emptyContextObject;
|
|
}
|
|
|
|
var fiber = get(parentComponent);
|
|
var parentContext = findCurrentUnmaskedContext();
|
|
|
|
if (fiber.tag === ClassComponent) {
|
|
var Component = fiber.type;
|
|
|
|
if (isContextProvider()) {
|
|
return processChildContext(fiber, Component, parentContext);
|
|
}
|
|
}
|
|
|
|
return parentContext;
|
|
}
|
|
|
|
function createContainer(
|
|
containerInfo,
|
|
tag,
|
|
hydrationCallbacks,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride,
|
|
identifierPrefix,
|
|
onRecoverableError,
|
|
transitionCallbacks
|
|
) {
|
|
var hydrate = false;
|
|
var initialChildren = null;
|
|
return createFiberRoot(
|
|
containerInfo,
|
|
tag,
|
|
hydrate,
|
|
initialChildren,
|
|
hydrationCallbacks,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride,
|
|
identifierPrefix,
|
|
onRecoverableError
|
|
);
|
|
}
|
|
function createHydrationContainer(
|
|
initialChildren, // TODO: Remove `callback` when we delete legacy mode.
|
|
callback,
|
|
containerInfo,
|
|
tag,
|
|
hydrationCallbacks,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride,
|
|
identifierPrefix,
|
|
onRecoverableError,
|
|
transitionCallbacks
|
|
) {
|
|
var hydrate = true;
|
|
var root = createFiberRoot(
|
|
containerInfo,
|
|
tag,
|
|
hydrate,
|
|
initialChildren,
|
|
hydrationCallbacks,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride,
|
|
identifierPrefix,
|
|
onRecoverableError
|
|
); // TODO: Move this to FiberRoot constructor
|
|
|
|
root.context = getContextForSubtree(null); // Schedule the initial render. In a hydration root, this is different from
|
|
// a regular update because the initial render must match was was rendered
|
|
// on the server.
|
|
// NOTE: This update intentionally doesn't have a payload. We're only using
|
|
// the update to schedule work on the root fiber (and, for legacy roots, to
|
|
// enqueue the callback if one is provided).
|
|
|
|
var current = root.current;
|
|
var eventTime = requestEventTime();
|
|
var lane = requestUpdateLane(current);
|
|
var update = createUpdate(eventTime, lane);
|
|
update.callback =
|
|
callback !== undefined && callback !== null ? callback : null;
|
|
enqueueUpdate$1(current, update, lane);
|
|
scheduleInitialHydrationOnRoot(root, lane, eventTime);
|
|
return root;
|
|
}
|
|
function updateContainer(element, container, parentComponent, callback) {
|
|
{
|
|
onScheduleRoot(container, element);
|
|
}
|
|
|
|
var current$1 = container.current;
|
|
var eventTime = requestEventTime();
|
|
var lane = requestUpdateLane(current$1);
|
|
|
|
var context = getContextForSubtree(parentComponent);
|
|
|
|
if (container.context === null) {
|
|
container.context = context;
|
|
} else {
|
|
container.pendingContext = context;
|
|
}
|
|
|
|
{
|
|
if (isRendering && current !== null && !didWarnAboutNestedUpdates) {
|
|
didWarnAboutNestedUpdates = true;
|
|
|
|
error(
|
|
"Render methods should be a pure function of props and state; " +
|
|
"triggering nested component updates from render is not allowed. " +
|
|
"If necessary, trigger nested updates in componentDidUpdate.\n\n" +
|
|
"Check the render method of %s.",
|
|
getComponentNameFromFiber(current) || "Unknown"
|
|
);
|
|
}
|
|
}
|
|
|
|
var update = createUpdate(eventTime, lane); // Caution: React DevTools currently depends on this property
|
|
// being called "element".
|
|
|
|
update.payload = {
|
|
element: element
|
|
};
|
|
callback = callback === undefined ? null : callback;
|
|
|
|
if (callback !== null) {
|
|
{
|
|
if (typeof callback !== "function") {
|
|
error(
|
|
"render(...): Expected the last optional `callback` argument to be a " +
|
|
"function. Instead received: %s.",
|
|
callback
|
|
);
|
|
}
|
|
}
|
|
|
|
update.callback = callback;
|
|
}
|
|
|
|
var root = enqueueUpdate$1(current$1, update, lane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, current$1, lane, eventTime);
|
|
entangleTransitions(root, current$1, lane);
|
|
}
|
|
|
|
return lane;
|
|
}
|
|
function attemptSynchronousHydration$1(fiber) {
|
|
switch (fiber.tag) {
|
|
case HostRoot: {
|
|
var root = fiber.stateNode;
|
|
|
|
if (isRootDehydrated(root)) {
|
|
// Flush the first scheduled "update".
|
|
var lanes = getHighestPriorityPendingLanes(root);
|
|
flushRoot(root, lanes);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SuspenseComponent: {
|
|
flushSync(function() {
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
var eventTime = requestEventTime();
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, eventTime);
|
|
}
|
|
}); // If we're still blocked after this, we need to increase
|
|
// the priority of any promises resolving within this
|
|
// boundary so that they next attempt also has higher pri.
|
|
|
|
var retryLane = SyncLane;
|
|
markRetryLaneIfNotHydrated(fiber, retryLane);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function markRetryLaneImpl(fiber, retryLane) {
|
|
var suspenseState = fiber.memoizedState;
|
|
|
|
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
|
suspenseState.retryLane = higherPriorityLane(
|
|
suspenseState.retryLane,
|
|
retryLane
|
|
);
|
|
}
|
|
} // Increases the priority of thenables when they resolve within this boundary.
|
|
|
|
function markRetryLaneIfNotHydrated(fiber, retryLane) {
|
|
markRetryLaneImpl(fiber, retryLane);
|
|
var alternate = fiber.alternate;
|
|
|
|
if (alternate) {
|
|
markRetryLaneImpl(alternate, retryLane);
|
|
}
|
|
}
|
|
function attemptContinuousHydration$1(fiber) {
|
|
if (fiber.tag !== SuspenseComponent) {
|
|
// We ignore HostRoots here because we can't increase
|
|
// their priority and they should not suspend on I/O,
|
|
// since you have to wrap anything that might suspend in
|
|
// Suspense.
|
|
return;
|
|
}
|
|
|
|
var lane = SelectiveHydrationLane;
|
|
var root = enqueueConcurrentRenderForLane(fiber, lane);
|
|
|
|
if (root !== null) {
|
|
var eventTime = requestEventTime();
|
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
|
}
|
|
|
|
markRetryLaneIfNotHydrated(fiber, lane);
|
|
}
|
|
function attemptHydrationAtCurrentPriority$1(fiber) {
|
|
if (fiber.tag !== SuspenseComponent) {
|
|
// We ignore HostRoots here because we can't increase
|
|
// their priority other than synchronously flush it.
|
|
return;
|
|
}
|
|
|
|
var lane = requestUpdateLane(fiber);
|
|
var root = enqueueConcurrentRenderForLane(fiber, lane);
|
|
|
|
if (root !== null) {
|
|
var eventTime = requestEventTime();
|
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
|
}
|
|
|
|
markRetryLaneIfNotHydrated(fiber, lane);
|
|
}
|
|
|
|
var shouldErrorImpl = function(fiber) {
|
|
return null;
|
|
};
|
|
|
|
function shouldError(fiber) {
|
|
return shouldErrorImpl(fiber);
|
|
}
|
|
|
|
var shouldSuspendImpl = function(fiber) {
|
|
return false;
|
|
};
|
|
|
|
function shouldSuspend(fiber) {
|
|
return shouldSuspendImpl(fiber);
|
|
}
|
|
var overrideHookState = null;
|
|
var overrideHookStateDeletePath = null;
|
|
var overrideHookStateRenamePath = null;
|
|
var overrideProps = null;
|
|
var overridePropsDeletePath = null;
|
|
var overridePropsRenamePath = null;
|
|
var scheduleUpdate = null;
|
|
var setErrorHandler = null;
|
|
var setSuspenseHandler = null;
|
|
|
|
{
|
|
var copyWithDeleteImpl = function(obj, path, index) {
|
|
var key = path[index];
|
|
var updated = isArray(obj) ? obj.slice() : assign({}, obj);
|
|
|
|
if (index + 1 === path.length) {
|
|
if (isArray(updated)) {
|
|
updated.splice(key, 1);
|
|
} else {
|
|
delete updated[key];
|
|
}
|
|
|
|
return updated;
|
|
} // $FlowFixMe number or string is fine here
|
|
|
|
updated[key] = copyWithDeleteImpl(obj[key], path, index + 1);
|
|
return updated;
|
|
};
|
|
|
|
var copyWithDelete = function(obj, path) {
|
|
return copyWithDeleteImpl(obj, path, 0);
|
|
};
|
|
|
|
var copyWithRenameImpl = function(obj, oldPath, newPath, index) {
|
|
var oldKey = oldPath[index];
|
|
var updated = isArray(obj) ? obj.slice() : assign({}, obj);
|
|
|
|
if (index + 1 === oldPath.length) {
|
|
var newKey = newPath[index]; // $FlowFixMe number or string is fine here
|
|
|
|
updated[newKey] = updated[oldKey];
|
|
|
|
if (isArray(updated)) {
|
|
updated.splice(oldKey, 1);
|
|
} else {
|
|
delete updated[oldKey];
|
|
}
|
|
} else {
|
|
// $FlowFixMe number or string is fine here
|
|
updated[oldKey] = copyWithRenameImpl(
|
|
// $FlowFixMe number or string is fine here
|
|
obj[oldKey],
|
|
oldPath,
|
|
newPath,
|
|
index + 1
|
|
);
|
|
}
|
|
|
|
return updated;
|
|
};
|
|
|
|
var copyWithRename = function(obj, oldPath, newPath) {
|
|
if (oldPath.length !== newPath.length) {
|
|
warn("copyWithRename() expects paths of the same length");
|
|
|
|
return;
|
|
} else {
|
|
for (var i = 0; i < newPath.length - 1; i++) {
|
|
if (oldPath[i] !== newPath[i]) {
|
|
warn(
|
|
"copyWithRename() expects paths to be the same except for the deepest key"
|
|
);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
return copyWithRenameImpl(obj, oldPath, newPath, 0);
|
|
};
|
|
|
|
var copyWithSetImpl = function(obj, path, index, value) {
|
|
if (index >= path.length) {
|
|
return value;
|
|
}
|
|
|
|
var key = path[index];
|
|
var updated = isArray(obj) ? obj.slice() : assign({}, obj); // $FlowFixMe number or string is fine here
|
|
|
|
updated[key] = copyWithSetImpl(obj[key], path, index + 1, value);
|
|
return updated;
|
|
};
|
|
|
|
var copyWithSet = function(obj, path, value) {
|
|
return copyWithSetImpl(obj, path, 0, value);
|
|
};
|
|
|
|
var findHook = function(fiber, id) {
|
|
// For now, the "id" of stateful hooks is just the stateful hook index.
|
|
// This may change in the future with e.g. nested hooks.
|
|
var currentHook = fiber.memoizedState;
|
|
|
|
while (currentHook !== null && id > 0) {
|
|
currentHook = currentHook.next;
|
|
id--;
|
|
}
|
|
|
|
return currentHook;
|
|
}; // Support DevTools editable values for useState and useReducer.
|
|
|
|
overrideHookState = function(fiber, id, path, value) {
|
|
var hook = findHook(fiber, id);
|
|
|
|
if (hook !== null) {
|
|
var newState = copyWithSet(hook.memoizedState, path, value);
|
|
hook.memoizedState = newState;
|
|
hook.baseState = newState; // We aren't actually adding an update to the queue,
|
|
// because there is no update we can add for useReducer hooks that won't trigger an error.
|
|
// (There's no appropriate action type for DevTools overrides.)
|
|
// As a result though, React will see the scheduled update as a noop and bailout.
|
|
// Shallow cloning props works as a workaround for now to bypass the bailout check.
|
|
|
|
fiber.memoizedProps = assign({}, fiber.memoizedProps);
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
}
|
|
};
|
|
|
|
overrideHookStateDeletePath = function(fiber, id, path) {
|
|
var hook = findHook(fiber, id);
|
|
|
|
if (hook !== null) {
|
|
var newState = copyWithDelete(hook.memoizedState, path);
|
|
hook.memoizedState = newState;
|
|
hook.baseState = newState; // We aren't actually adding an update to the queue,
|
|
// because there is no update we can add for useReducer hooks that won't trigger an error.
|
|
// (There's no appropriate action type for DevTools overrides.)
|
|
// As a result though, React will see the scheduled update as a noop and bailout.
|
|
// Shallow cloning props works as a workaround for now to bypass the bailout check.
|
|
|
|
fiber.memoizedProps = assign({}, fiber.memoizedProps);
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
}
|
|
};
|
|
|
|
overrideHookStateRenamePath = function(fiber, id, oldPath, newPath) {
|
|
var hook = findHook(fiber, id);
|
|
|
|
if (hook !== null) {
|
|
var newState = copyWithRename(hook.memoizedState, oldPath, newPath);
|
|
hook.memoizedState = newState;
|
|
hook.baseState = newState; // We aren't actually adding an update to the queue,
|
|
// because there is no update we can add for useReducer hooks that won't trigger an error.
|
|
// (There's no appropriate action type for DevTools overrides.)
|
|
// As a result though, React will see the scheduled update as a noop and bailout.
|
|
// Shallow cloning props works as a workaround for now to bypass the bailout check.
|
|
|
|
fiber.memoizedProps = assign({}, fiber.memoizedProps);
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
}
|
|
}; // Support DevTools props for function components, forwardRef, memo, host components, etc.
|
|
|
|
overrideProps = function(fiber, path, value) {
|
|
fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
|
|
|
|
if (fiber.alternate) {
|
|
fiber.alternate.pendingProps = fiber.pendingProps;
|
|
}
|
|
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
};
|
|
|
|
overridePropsDeletePath = function(fiber, path) {
|
|
fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path);
|
|
|
|
if (fiber.alternate) {
|
|
fiber.alternate.pendingProps = fiber.pendingProps;
|
|
}
|
|
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
};
|
|
|
|
overridePropsRenamePath = function(fiber, oldPath, newPath) {
|
|
fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
|
|
|
|
if (fiber.alternate) {
|
|
fiber.alternate.pendingProps = fiber.pendingProps;
|
|
}
|
|
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
};
|
|
|
|
scheduleUpdate = function(fiber) {
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
|
|
if (root !== null) {
|
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
}
|
|
};
|
|
|
|
setErrorHandler = function(newShouldErrorImpl) {
|
|
shouldErrorImpl = newShouldErrorImpl;
|
|
};
|
|
|
|
setSuspenseHandler = function(newShouldSuspendImpl) {
|
|
shouldSuspendImpl = newShouldSuspendImpl;
|
|
};
|
|
}
|
|
|
|
function findHostInstanceByFiber(fiber) {
|
|
var hostFiber = findCurrentHostFiber(fiber);
|
|
|
|
if (hostFiber === null) {
|
|
return null;
|
|
}
|
|
|
|
return hostFiber.stateNode;
|
|
}
|
|
|
|
function emptyFindFiberByHostInstance(instance) {
|
|
return null;
|
|
}
|
|
|
|
function getCurrentFiberForDevTools() {
|
|
return current;
|
|
}
|
|
|
|
function injectIntoDevTools(devToolsConfig) {
|
|
var findFiberByHostInstance = devToolsConfig.findFiberByHostInstance;
|
|
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
|
return injectInternals({
|
|
bundleType: devToolsConfig.bundleType,
|
|
version: devToolsConfig.version,
|
|
rendererPackageName: devToolsConfig.rendererPackageName,
|
|
rendererConfig: devToolsConfig.rendererConfig,
|
|
overrideHookState: overrideHookState,
|
|
overrideHookStateDeletePath: overrideHookStateDeletePath,
|
|
overrideHookStateRenamePath: overrideHookStateRenamePath,
|
|
overrideProps: overrideProps,
|
|
overridePropsDeletePath: overridePropsDeletePath,
|
|
overridePropsRenamePath: overridePropsRenamePath,
|
|
setErrorHandler: setErrorHandler,
|
|
setSuspenseHandler: setSuspenseHandler,
|
|
scheduleUpdate: scheduleUpdate,
|
|
currentDispatcherRef: ReactCurrentDispatcher,
|
|
findHostInstanceByFiber: findHostInstanceByFiber,
|
|
findFiberByHostInstance:
|
|
findFiberByHostInstance || emptyFindFiberByHostInstance,
|
|
// React Refresh
|
|
findHostInstancesForRefresh: findHostInstancesForRefresh,
|
|
scheduleRefresh: scheduleRefresh,
|
|
scheduleRoot: scheduleRoot,
|
|
setRefreshHandler: setRefreshHandler,
|
|
// Enables DevTools to append owner stacks to error messages in DEV mode.
|
|
getCurrentFiber: getCurrentFiberForDevTools,
|
|
// Enables DevTools to detect reconciler version rather than renderer version
|
|
// which may not match for third party renderers.
|
|
reconcilerVersion: ReactVersion
|
|
});
|
|
}
|
|
|
|
var Dispatcher$1 = Internals.Dispatcher;
|
|
/* global reportError */
|
|
|
|
var defaultOnRecoverableError =
|
|
typeof reportError === "function" // In modern browsers, reportError will dispatch an error event,
|
|
? // emulating an uncaught JavaScript error.
|
|
reportError
|
|
: function(error) {
|
|
// In older browsers and test environments, fallback to console.error.
|
|
// eslint-disable-next-line react-internal/no-production-logging
|
|
console["error"](error);
|
|
}; // $FlowFixMe[missing-this-annot]
|
|
|
|
function ReactDOMRoot(internalRoot) {
|
|
this._internalRoot = internalRoot;
|
|
} // $FlowFixMe[prop-missing] found when upgrading Flow
|
|
// $FlowFixMe[missing-this-annot]
|
|
|
|
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function(
|
|
children
|
|
) {
|
|
var root = this._internalRoot;
|
|
|
|
if (root === null) {
|
|
throw new Error("Cannot update an unmounted root.");
|
|
}
|
|
|
|
{
|
|
if (typeof arguments[1] === "function") {
|
|
error(
|
|
"render(...): does not support the second callback argument. " +
|
|
"To execute a side effect after rendering, declare it in a component body with useEffect()."
|
|
);
|
|
} else if (isValidContainer(arguments[1])) {
|
|
error(
|
|
"You passed a container to the second argument of root.render(...). " +
|
|
"You don't need to pass it again since you already passed it to create the root."
|
|
);
|
|
} else if (typeof arguments[1] !== "undefined") {
|
|
error(
|
|
"You passed a second argument to root.render(...) but it only accepts " +
|
|
"one argument."
|
|
);
|
|
}
|
|
|
|
var container = root.containerInfo;
|
|
}
|
|
|
|
updateContainer(children, root, null, null);
|
|
}; // $FlowFixMe[prop-missing] found when upgrading Flow
|
|
// $FlowFixMe[missing-this-annot]
|
|
|
|
ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount = function() {
|
|
{
|
|
if (typeof arguments[0] === "function") {
|
|
error(
|
|
"unmount(...): does not support a callback argument. " +
|
|
"To execute a side effect after rendering, declare it in a component body with useEffect()."
|
|
);
|
|
}
|
|
}
|
|
|
|
var root = this._internalRoot;
|
|
|
|
if (root !== null) {
|
|
this._internalRoot = null;
|
|
var container = root.containerInfo;
|
|
|
|
{
|
|
if (isAlreadyRendering()) {
|
|
error(
|
|
"Attempted to synchronously unmount a root while React was already " +
|
|
"rendering. React cannot finish unmounting the root until the " +
|
|
"current render has completed, which may lead to a race condition."
|
|
);
|
|
}
|
|
}
|
|
|
|
flushSync(function() {
|
|
updateContainer(null, root, null, null);
|
|
});
|
|
unmarkContainerAsRoot(container);
|
|
}
|
|
};
|
|
|
|
function createRoot(container, options) {
|
|
if (!isValidContainer(container)) {
|
|
throw new Error("createRoot(...): Target container is not a DOM element.");
|
|
}
|
|
|
|
warnIfReactDOMContainerInDEV(container);
|
|
var isStrictMode = false;
|
|
var concurrentUpdatesByDefaultOverride = false;
|
|
var identifierPrefix = "";
|
|
var onRecoverableError = defaultOnRecoverableError;
|
|
var transitionCallbacks = null;
|
|
|
|
if (options !== null && options !== undefined) {
|
|
{
|
|
if (options.hydrate) {
|
|
warn(
|
|
"hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead."
|
|
);
|
|
} else {
|
|
if (
|
|
typeof options === "object" &&
|
|
options !== null &&
|
|
options.$$typeof === REACT_ELEMENT_TYPE
|
|
) {
|
|
error(
|
|
"You passed a JSX element to createRoot. You probably meant to " +
|
|
"call root.render instead. " +
|
|
"Example usage:\n\n" +
|
|
" let root = createRoot(domContainer);\n" +
|
|
" root.render(<App />);"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (options.unstable_strictMode === true) {
|
|
isStrictMode = true;
|
|
}
|
|
|
|
if (options.unstable_concurrentUpdatesByDefault === true) {
|
|
concurrentUpdatesByDefaultOverride = true;
|
|
}
|
|
|
|
if (options.identifierPrefix !== undefined) {
|
|
identifierPrefix = options.identifierPrefix;
|
|
}
|
|
|
|
if (options.onRecoverableError !== undefined) {
|
|
onRecoverableError = options.onRecoverableError;
|
|
}
|
|
|
|
if (options.unstable_transitionCallbacks !== undefined) {
|
|
transitionCallbacks = options.unstable_transitionCallbacks;
|
|
}
|
|
}
|
|
|
|
var root = createContainer(
|
|
container,
|
|
ConcurrentRoot,
|
|
null,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride,
|
|
identifierPrefix,
|
|
onRecoverableError
|
|
);
|
|
markContainerAsRoot(root.current, container);
|
|
|
|
{
|
|
// Set the default dispatcher to the client dispatcher
|
|
Dispatcher$1.current = ReactDOMClientDispatcher;
|
|
}
|
|
|
|
var rootContainerElement =
|
|
container.nodeType === COMMENT_NODE ? container.parentNode : container;
|
|
listenToAllSupportedEvents(rootContainerElement); // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
|
|
return new ReactDOMRoot(root);
|
|
} // $FlowFixMe[missing-this-annot]
|
|
|
|
function ReactDOMHydrationRoot(internalRoot) {
|
|
this._internalRoot = internalRoot;
|
|
}
|
|
|
|
function scheduleHydration(target) {
|
|
if (target) {
|
|
queueExplicitHydrationTarget(target);
|
|
}
|
|
} // $FlowFixMe[prop-missing] found when upgrading Flow
|
|
|
|
ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = scheduleHydration;
|
|
function hydrateRoot(container, initialChildren, options) {
|
|
if (!isValidContainer(container)) {
|
|
throw new Error("hydrateRoot(...): Target container is not a DOM element.");
|
|
}
|
|
|
|
warnIfReactDOMContainerInDEV(container);
|
|
|
|
{
|
|
if (initialChildren === undefined) {
|
|
error(
|
|
"Must provide initial children as second argument to hydrateRoot. " +
|
|
"Example usage: hydrateRoot(domContainer, <App />)"
|
|
);
|
|
}
|
|
} // For now we reuse the whole bag of options since they contain
|
|
// the hydration callbacks.
|
|
|
|
var hydrationCallbacks = options != null ? options : null; // TODO: Delete this option
|
|
|
|
var mutableSources = (options != null && options.hydratedSources) || null;
|
|
var isStrictMode = false;
|
|
var concurrentUpdatesByDefaultOverride = false;
|
|
var identifierPrefix = "";
|
|
var onRecoverableError = defaultOnRecoverableError;
|
|
var transitionCallbacks = null;
|
|
|
|
if (options !== null && options !== undefined) {
|
|
if (options.unstable_strictMode === true) {
|
|
isStrictMode = true;
|
|
}
|
|
|
|
if (options.unstable_concurrentUpdatesByDefault === true) {
|
|
concurrentUpdatesByDefaultOverride = true;
|
|
}
|
|
|
|
if (options.identifierPrefix !== undefined) {
|
|
identifierPrefix = options.identifierPrefix;
|
|
}
|
|
|
|
if (options.onRecoverableError !== undefined) {
|
|
onRecoverableError = options.onRecoverableError;
|
|
}
|
|
|
|
if (options.unstable_transitionCallbacks !== undefined) {
|
|
transitionCallbacks = options.unstable_transitionCallbacks;
|
|
}
|
|
}
|
|
|
|
var root = createHydrationContainer(
|
|
initialChildren,
|
|
null,
|
|
container,
|
|
ConcurrentRoot,
|
|
hydrationCallbacks,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride,
|
|
identifierPrefix,
|
|
onRecoverableError
|
|
);
|
|
markContainerAsRoot(root.current, container);
|
|
|
|
{
|
|
// Set the default dispatcher to the client dispatcher
|
|
Dispatcher$1.current = ReactDOMClientDispatcher;
|
|
} // This can't be a comment node since hydration doesn't work on comment nodes anyway.
|
|
|
|
listenToAllSupportedEvents(container);
|
|
|
|
if (mutableSources) {
|
|
for (var i = 0; i < mutableSources.length; i++) {
|
|
var mutableSource = mutableSources[i];
|
|
registerMutableSourceForHydration(root, mutableSource);
|
|
}
|
|
} // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
|
|
|
return new ReactDOMHydrationRoot(root);
|
|
}
|
|
function isValidContainer(node) {
|
|
return !!(
|
|
node &&
|
|
(node.nodeType === ELEMENT_NODE ||
|
|
node.nodeType === DOCUMENT_NODE ||
|
|
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
|
|
!disableCommentsAsDOMContainers)
|
|
);
|
|
} // TODO: Remove this function which also includes comment nodes.
|
|
|
|
function warnIfReactDOMContainerInDEV(container) {
|
|
{
|
|
if (
|
|
container.nodeType === ELEMENT_NODE &&
|
|
container.tagName &&
|
|
container.tagName.toUpperCase() === "BODY"
|
|
) {
|
|
error(
|
|
"createRoot(): Creating roots directly with document.body is " +
|
|
"discouraged, since its children are often manipulated by third-party " +
|
|
"scripts and browser extensions. This may lead to subtle " +
|
|
"reconciliation issues. Try using a container element created " +
|
|
"for your app."
|
|
);
|
|
}
|
|
|
|
if (isContainerMarkedAsRoot(container)) {
|
|
if (container._reactRootContainer) {
|
|
error(
|
|
"You are calling ReactDOMClient.createRoot() on a container that was previously " +
|
|
"passed to ReactDOM.render(). This is not supported."
|
|
);
|
|
} else {
|
|
error(
|
|
"You are calling ReactDOMClient.createRoot() on a container that " +
|
|
"has already been passed to createRoot() before. Instead, call " +
|
|
"root.render() on the existing root instead if you want to update it."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var ReactCurrentOwner$3 = ReactSharedInternals.ReactCurrentOwner;
|
|
|
|
function isValidEventTarget(target) {
|
|
return typeof target.addEventListener === "function";
|
|
}
|
|
|
|
function isReactScope(target) {
|
|
return typeof target.getChildContextValues === "function";
|
|
}
|
|
|
|
function createEventHandleListener(type, isCapturePhaseListener, callback) {
|
|
return {
|
|
callback: callback,
|
|
capture: isCapturePhaseListener,
|
|
type: type
|
|
};
|
|
}
|
|
|
|
function registerReactDOMEvent(target, domEventName, isCapturePhaseListener) {
|
|
if (target.nodeType === ELEMENT_NODE);
|
|
else if (isReactScope(target));
|
|
else if (isValidEventTarget(target)) {
|
|
var eventTarget = target; // These are valid event targets, but they are also
|
|
// non-managed React nodes.
|
|
|
|
listenToNativeEventForNonManagedEventTarget(
|
|
domEventName,
|
|
isCapturePhaseListener,
|
|
eventTarget
|
|
);
|
|
} else {
|
|
throw new Error(
|
|
"ReactDOM.createEventHandle: setter called on an invalid " +
|
|
"target. Provide a valid EventTarget or an element managed by React."
|
|
);
|
|
}
|
|
}
|
|
|
|
function createEventHandle(type, options) {
|
|
{
|
|
var domEventName = type; // We cannot support arbitrary native events with eager root listeners
|
|
// because the eager strategy relies on knowing the whole list ahead of time.
|
|
// If we wanted to support this, we'd have to add code to keep track
|
|
// (or search) for all portal and root containers, and lazily add listeners
|
|
// to them whenever we see a previously unknown event. This seems like a lot
|
|
// of complexity for something we don't even have a particular use case for.
|
|
// Unfortunately, the downside of this invariant is that *removing* a native
|
|
// event from the list of known events has now become a breaking change for
|
|
// any code relying on the createEventHandle API.
|
|
|
|
if (!allNativeEvents.has(domEventName)) {
|
|
throw new Error(
|
|
'Cannot call unstable_createEventHandle with "' +
|
|
domEventName +
|
|
'", as it is not an event known to React.'
|
|
);
|
|
}
|
|
|
|
var isCapturePhaseListener = false;
|
|
|
|
if (options != null) {
|
|
var optionsCapture = options.capture;
|
|
|
|
if (typeof optionsCapture === "boolean") {
|
|
isCapturePhaseListener = optionsCapture;
|
|
}
|
|
}
|
|
|
|
var eventHandle = function(target, callback) {
|
|
if (typeof callback !== "function") {
|
|
throw new Error(
|
|
"ReactDOM.createEventHandle: setter called with an invalid " +
|
|
"callback. The callback must be a function."
|
|
);
|
|
}
|
|
|
|
if (!doesTargetHaveEventHandle(target, eventHandle)) {
|
|
addEventHandleToTarget(target, eventHandle);
|
|
registerReactDOMEvent(target, domEventName, isCapturePhaseListener);
|
|
}
|
|
|
|
var listener = createEventHandleListener(
|
|
domEventName,
|
|
isCapturePhaseListener,
|
|
callback
|
|
);
|
|
var targetListeners = getEventHandlerListeners(target);
|
|
|
|
if (targetListeners === null) {
|
|
targetListeners = new Set();
|
|
setEventHandlerListeners(target, targetListeners);
|
|
}
|
|
|
|
targetListeners.add(listener);
|
|
return function() {
|
|
targetListeners.delete(listener);
|
|
};
|
|
};
|
|
|
|
return eventHandle;
|
|
}
|
|
}
|
|
|
|
function preinit$1() {
|
|
var dispatcher = ReactDOMSharedInternals.Dispatcher.current;
|
|
|
|
if (dispatcher) {
|
|
dispatcher.preinit.apply(this, arguments);
|
|
} // We don't error because preinit needs to be resilient to being called in a variety of scopes
|
|
// and the runtime may not be capable of responding. The function is optimistic and not critical
|
|
// so we favor silent bailout over warning or erroring.
|
|
}
|
|
function preload$1() {
|
|
var dispatcher = ReactDOMSharedInternals.Dispatcher.current;
|
|
|
|
if (dispatcher) {
|
|
dispatcher.preload.apply(this, arguments);
|
|
} // We don't error because preload needs to be resilient to being called in a variety of scopes
|
|
// and the runtime may not be capable of responding. The function is optimistic and not critical
|
|
// so we favor silent bailout over warning or erroring.
|
|
}
|
|
|
|
setAttemptSynchronousHydration(attemptSynchronousHydration$1);
|
|
setAttemptContinuousHydration(attemptContinuousHydration$1);
|
|
setAttemptHydrationAtCurrentPriority(attemptHydrationAtCurrentPriority$1);
|
|
setGetCurrentUpdatePriority(getCurrentUpdatePriority);
|
|
setAttemptHydrationAtPriority(runWithPriority);
|
|
|
|
{
|
|
if (
|
|
typeof Map !== "function" || // $FlowFixMe Flow incorrectly thinks Map has no prototype
|
|
Map.prototype == null ||
|
|
typeof Map.prototype.forEach !== "function" ||
|
|
typeof Set !== "function" || // $FlowFixMe Flow incorrectly thinks Set has no prototype
|
|
Set.prototype == null ||
|
|
typeof Set.prototype.clear !== "function" ||
|
|
typeof Set.prototype.forEach !== "function"
|
|
) {
|
|
error(
|
|
"React depends on Map and Set built-in types. Make sure that you load a " +
|
|
"polyfill in older browsers. https://reactjs.org/link/react-polyfills"
|
|
);
|
|
}
|
|
}
|
|
|
|
setRestoreImplementation(restoreControlledState$3);
|
|
setBatchingImplementation(batchedUpdates$1, discreteUpdates, flushSync);
|
|
|
|
function createPortal$1(children, container) {
|
|
var key =
|
|
arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
|
|
if (!isValidContainer(container)) {
|
|
throw new Error("Target container is not a DOM element.");
|
|
} // TODO: pass ReactDOM portal implementation as third argument
|
|
// $FlowFixMe The Flow type is opaque but there's no way to actually create it.
|
|
|
|
return createPortal(children, container, null, key);
|
|
}
|
|
|
|
function createRoot$1(container, options) {
|
|
{
|
|
if (!Internals.usingClientEntryPoint && !false) {
|
|
error(
|
|
'You are importing createRoot from "react-dom" which is not supported. ' +
|
|
'You should instead import it from "react-dom/client".'
|
|
);
|
|
}
|
|
}
|
|
|
|
return createRoot(container, options);
|
|
}
|
|
|
|
function hydrateRoot$1(container, initialChildren, options) {
|
|
{
|
|
if (!Internals.usingClientEntryPoint && !false) {
|
|
error(
|
|
'You are importing hydrateRoot from "react-dom" which is not supported. ' +
|
|
'You should instead import it from "react-dom/client".'
|
|
);
|
|
}
|
|
}
|
|
|
|
return hydrateRoot(container, initialChildren, options);
|
|
} // Overload the definition to the two valid signatures.
|
|
// Warning, this opts-out of checking the function body.
|
|
// eslint-disable-next-line no-redeclare
|
|
// eslint-disable-next-line no-redeclare
|
|
|
|
function flushSync$1(fn) {
|
|
{
|
|
if (isAlreadyRendering()) {
|
|
error(
|
|
"flushSync was called from inside a lifecycle method. React cannot " +
|
|
"flush when React is already rendering. Consider moving this call to " +
|
|
"a scheduler task or micro task."
|
|
);
|
|
}
|
|
}
|
|
|
|
return flushSync(fn);
|
|
}
|
|
// This is an array for better minification.
|
|
|
|
Internals.Events = [
|
|
getInstanceFromNode$1,
|
|
getNodeFromInstance,
|
|
getFiberCurrentPropsFromNode,
|
|
enqueueStateRestore,
|
|
restoreStateIfNeeded,
|
|
batchedUpdates$1
|
|
];
|
|
var foundDevTools = injectIntoDevTools({
|
|
findFiberByHostInstance: getClosestInstanceFromNode,
|
|
bundleType: 1,
|
|
version: ReactVersion,
|
|
rendererPackageName: "react-dom"
|
|
});
|
|
|
|
{
|
|
if (!foundDevTools && canUseDOM && window.top === window.self) {
|
|
// If we're in Chrome or Firefox, provide a download link if not installed.
|
|
if (
|
|
(navigator.userAgent.indexOf("Chrome") > -1 &&
|
|
navigator.userAgent.indexOf("Edge") === -1) ||
|
|
navigator.userAgent.indexOf("Firefox") > -1
|
|
) {
|
|
var protocol = window.location.protocol; // Don't warn in exotic cases like chrome-extension://.
|
|
|
|
if (/^(https?|file):$/.test(protocol)) {
|
|
// eslint-disable-next-line react-internal/no-production-logging
|
|
console.info(
|
|
"%cDownload the React DevTools " +
|
|
"for a better development experience: " +
|
|
"https://reactjs.org/link/react-devtools" +
|
|
(protocol === "file:"
|
|
? "\nYou might need to use a local HTTP server (instead of file://): " +
|
|
"https://reactjs.org/link/react-devtools-faq"
|
|
: ""),
|
|
"font-weight:bold"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = Internals;
|
|
exports.createComponentSelector = createComponentSelector;
|
|
exports.createHasPseudoClassSelector = createHasPseudoClassSelector;
|
|
exports.createPortal = createPortal$1;
|
|
exports.createRoleSelector = createRoleSelector;
|
|
exports.createRoot = createRoot$1;
|
|
exports.createTestNameSelector = createTestNameSelector;
|
|
exports.createTextSelector = createTextSelector;
|
|
exports.findAllNodes = findAllNodes;
|
|
exports.findBoundingRects = findBoundingRects;
|
|
exports.flushSync = flushSync$1;
|
|
exports.focusWithin = focusWithin;
|
|
exports.getFindAllNodesFailureDescription = getFindAllNodesFailureDescription;
|
|
exports.hydrateRoot = hydrateRoot$1;
|
|
exports.observeVisibleRects = observeVisibleRects;
|
|
exports.preinit = preinit$1;
|
|
exports.preload = preload$1;
|
|
exports.unstable_batchedUpdates = batchedUpdates$1;
|
|
exports.unstable_createEventHandle = createEventHandle;
|
|
exports.unstable_flushControlled = flushControlled;
|
|
exports.unstable_runWithPriority = runWithPriority;
|
|
exports.version = ReactVersion;
|
|
|
|
})();
|
|
}
|