15
} from '../events/EventRegistry';
16
17
import {canUseDOM} from 'shared/ExecutionEnvironment';
18
-import hasOwnProperty from 'shared/hasOwnProperty';
18
import {checkHtmlStringCoercion} from 'shared/CheckStringCoercion';
19
20
import {
58
} from './CSSPropertyOperations';
59
import {HTML_NAMESPACE, getIntrinsicNamespace} from './DOMNamespaces';
60
import {getPropertyInfo} from '../shared/DOMProperty';
62
-import {DOCUMENT_NODE} from './HTMLNodeType';
63
-import isCustomComponent from '../shared/isCustomComponent';
61
+import isCustomElement from '../shared/isCustomElement';
62
import possibleStandardNames from '../shared/possibleStandardNames';
63
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
64
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
65
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
66
67
import {
70
- enableTrustedTypesIntegration,
68
enableCustomElementPropertySupport,
69
enableClientRenderFallbackOnTextMismatch,
70
enableHostSingletons,
76
} from '../events/DOMPluginEventSystem';
77
78
let didWarnInvalidHydration = false;
82
-let didWarnScriptTags = false;
83
-
84
-let warnedUnknownTags: {
85
- [key: string]: boolean,
86
-};
79
let canDiffStyleForHydrationWarning;
88
-
80
if (__DEV__) {
90
- warnedUnknownTags = {
91
- // There are working polyfills for <dialog>. Let people use it.
92
- dialog: true,
93
- // Electron ships a custom <webview> tag to display external web content in
94
- // an isolated frame and process.
95
- // This tag is not present in non Electron environments such as JSDom which
96
- // is often used for testing purposes.
97
- // @see https://electronjs.org/docs/api/webview-tag
98
- webview: true,
99
- };
100
-
81
// IE 11 parses & normalizes the style attribute as opposed to other
82
// browsers. It adds spaces and sorts the properties in some
83
// non-alphabetical order. Handling that would require sorting CSS
244
}
245
}
246
267
-export function getOwnerDocumentFromRootContainer(
268
- rootContainerElement: Element | Document | DocumentFragment,
269
-): Document {
270
- return rootContainerElement.nodeType === DOCUMENT_NODE
271
- ? (rootContainerElement: any)
272
- : rootContainerElement.ownerDocument;
273
-}
274
-
247
function noop() {}
248
249
export function trapClickOnNonInteractiveElement(node: HTMLElement) {
264
tag: string,
265
key: string,
266
value: mixed,
295
- isCustomComponentTag: boolean,
267
+ isCustomElementTag: boolean,
268
props: any,
269
): void {
270
switch (key) {
390
warnForInvalidEventListener(key, value);
391
}
392
} else {
421
- if (isCustomComponentTag) {
422
- setValueForPropertyOnCustomComponent(domElement, key, value);
393
+ if (isCustomElementTag) {
394
+ if (enableCustomElementPropertySupport) {
395
+ setValueForPropertyOnCustomComponent(domElement, key, value);
396
+ } else {
397
+ if (typeof value === 'boolean') {
398
+ // Special case before the new flag is on
399
+ value = '' + (value: any);
400
+ }
401
+ setValueForAttribute(domElement, key, value);
402
+ }
403
} else {
404
if (
405
// shouldIgnoreAttribute
423
}
424
}
425
446
-// creates a script element that won't execute
447
-export function createPotentiallyInlineScriptElement(
448
- ownerDocument: Document,
449
-): Element {
450
- // Create the script via .innerHTML so its "parser-inserted" flag is
451
- // set to true and it does not execute
452
- const div = ownerDocument.createElement('div');
453
- if (__DEV__) {
454
- if (enableTrustedTypesIntegration && !didWarnScriptTags) {
455
- console.error(
456
- 'Encountered a script tag while rendering React component. ' +
457
- 'Scripts inside React components are never executed when rendering ' +
458
- 'on the client. Consider using template tag instead ' +
459
- '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).',
460
- );
461
- didWarnScriptTags = true;
462
- }
463
- }
464
- div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
465
- // This is guaranteed to yield a script element.
466
- const firstChild = ((div.firstChild: any): HTMLScriptElement);
467
- const element = div.removeChild(firstChild);
468
- return element;
469
-}
470
-
471
-export function createSelectElement(
472
- props: Object,
473
- ownerDocument: Document,
474
-): Element {
475
- let element;
476
- if (typeof props.is === 'string') {
477
- element = ownerDocument.createElement('select', {is: props.is});
478
- } else {
479
- // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
480
- // See discussion in https://github.com/facebook/react/pull/6896
481
- // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
482
- element = ownerDocument.createElement('select');
483
- }
484
- if (props.multiple) {
485
- element.multiple = true;
486
- } else if (props.size) {
487
- // Setting a size greater than 1 causes a select to behave like `multiple=true`, where
488
- // it is possible that no option is selected.
489
- //
490
- // This is only necessary when a select in "single selection mode".
491
- element.size = props.size;
492
- }
493
- return element;
494
-}
495
-
496
-// Creates elements in the HTML namesapce
497
-export function createHTMLElement(
498
- type: string,
499
- props: Object,
500
- ownerDocument: Document,
501
-): Element {
502
- if (__DEV__) {
503
- switch (type) {
504
- case 'script':
505
- case 'select':
506
- console.error(
507
- 'createHTMLElement was called with a "%s" type. This type has special creation logic in React and should use the create function implemented specifically for it. This is a bug in React.',
508
- type,
509
- );
510
- break;
511
- case 'svg':
512
- case 'math':
513
- console.error(
514
- 'createHTMLElement was called with a "%s" type. This type must be created with Document.createElementNS which this method does not implement. This is a bug in React.',
515
- type,
516
- );
517
- }
518
- }
519
-
520
- let isCustomComponentTag;
521
-
522
- let element: Element;
523
- if (__DEV__) {
524
- isCustomComponentTag = isCustomComponent(type, props);
525
- // Should this check be gated by parent namespace? Not sure we want to
526
- // allow <SVG> or <mATH>.
527
- if (!isCustomComponentTag && type !== type.toLowerCase()) {
528
- console.error(
529
- '<%s /> is using incorrect casing. ' +
530
- 'Use PascalCase for React components, ' +
531
- 'or lowercase for HTML elements.',
532
- type,
533
- );
534
- }
535
- }
536
-
537
- if (typeof props.is === 'string') {
538
- element = ownerDocument.createElement(type, {is: props.is});
539
- } else {
540
- // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
541
- // See discussion in https://github.com/facebook/react/pull/6896
542
- // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
543
- element = ownerDocument.createElement(type);
544
- }
545
-
546
- if (__DEV__) {
547
- if (
548
- !isCustomComponentTag &&
549
- // $FlowFixMe[method-unbinding]
550
- Object.prototype.toString.call(element) ===
551
- '[object HTMLUnknownElement]' &&
552
- !hasOwnProperty.call(warnedUnknownTags, type)
553
- ) {
554
- warnedUnknownTags[type] = true;
555
- console.error(
556
- 'The tag <%s> is unrecognized in this browser. ' +
557
- 'If you meant to render a React component, start its name with ' +
558
- 'an uppercase letter.',
559
- type,
560
- );
561
- }
562
- }
563
-
564
- return element;
565
-}
566
-
567
-export function createTextNode(
568
- text: string,
569
- rootContainerElement: Element | Document | DocumentFragment,
570
-): Text {
571
- return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
572
- text,
573
- );
574
-}
575
-
426
export function setInitialProperties(
427
domElement: Element,
428
tag: string,
657
}
658
// defaultChecked and defaultValue are ignored by setProp
659
default: {
810
- // TODO: If the `is` prop is specified, this should go through the isCustomComponentTag flow.
660
setProp(domElement, tag, propKey, propValue, false, props);
661
}
662
}
665
}
666
}
667
819
- const isCustomComponentTag = isCustomComponent(tag, props);
668
+ const isCustomElementTag = isCustomElement(tag, props);
669
for (const propKey in props) {
670
if (!props.hasOwnProperty(propKey)) {
671
continue;
674
if (propValue == null) {
675
continue;
676
}
828
- setProp(domElement, tag, propKey, propValue, isCustomComponentTag, props);
677
+ setProp(domElement, tag, propKey, propValue, isCustomElementTag, props);
678
}
679
}
680
769
}
770
break;
771
}
772
+ case 'is':
773
+ if (__DEV__) {
774
+ console.error(
775
+ 'Cannot update the "is" prop after it has been initialized.',
776
+ );
777
+ }
778
+ // eslint-disable-next-line no-fallthrough
779
default: {
780
(updatePayload = updatePayload || []).push(propKey, nextProp);
781
}
951
}
952
// defaultChecked and defaultValue are ignored by setProp
953
default: {
1098
- // TODO: If the `is` prop is specified, this should go through the isCustomComponentTag flow.
954
setProp(domElement, tag, propKey, propValue, false, nextProps);
955
}
956
}
959
}
960
}
961
1107
- // TODO: Handle wasCustomComponentTag. Changing "is" isn't valid.
1108
- // const wasCustomComponentTag = isCustomComponent(tag, lastProps);
1109
- const isCustomComponentTag = isCustomComponent(tag, nextProps);
962
+ const isCustomElementTag = isCustomElement(tag, nextProps);
963
// Apply the diff.
964
for (let i = 0; i < updatePayload.length; i += 2) {
965
const propKey = updatePayload[i];
966
const propValue = updatePayload[i + 1];
1114
- setProp(
1115
- domElement,
1116
- tag,
1117
- propKey,
1118
- propValue,
1119
- isCustomComponentTag,
1120
- nextProps,
1121
- );
967
+ setProp(domElement, tag, propKey, propValue, isCustomElementTag, nextProps);
968
}
969
}
970
1064
continue;
1065
}
1066
// eslint-disable-next-line no-fallthrough
1067
+ case 'className':
1068
+ if (enableCustomElementPropertySupport) {
1069
+ // className is a special cased property on the server to render as an attribute.
1070
+ extraAttributeNames.delete('class');
1071
+ const serverValue = getValueForAttributeOnCustomComponent(
1072
+ domElement,
1073
+ 'class',
1074
+ nextProp,
1075
+ );
1076
+ if (nextProp !== serverValue) {
1077
+ warnForPropDifference('className', serverValue, nextProp);
1078
+ }
1079
+ continue;
1080
+ }
1081
+ // eslint-disable-next-line no-fallthrough
1082
default: {
1083
let ownNamespaceDev = parentNamespaceDev;
1084
if (ownNamespaceDev === HTML_NAMESPACE) {
1365
extraAttributeNames.add(attributes[i].name);
1366
}
1367
}
1507
- if (isCustomComponent(tag, props)) {
1368
+ if (isCustomElement(tag, props)) {
1369
diffHydratedCustomComponent(
1370
domElement,
1371
tag,