diff --git a/includes/sanitizers/class-amp-style-sanitizer.php b/includes/sanitizers/class-amp-style-sanitizer.php index 56c1560b149..91198e09edf 100644 --- a/includes/sanitizers/class-amp-style-sanitizer.php +++ b/includes/sanitizers/class-amp-style-sanitizer.php @@ -661,6 +661,12 @@ private function has_used_class_name( $class_names ) { } continue 2; } + } elseif ( ctype_upper( $class_name[0] ) && $this->has_used_tag_names( [ 'amp-date-picker' ] ) && $this->is_class_allowed_in_amp_date_picker( $class_name ) ) { + // If the document has an amp-date-picker tag, check if this class is an allowed child of it. + // That component's child classes won't be present yet in the document, so prevent tree-shaking valid classes. + // The ctype_upper() check is an optimization since we know up front that all class names in React Dates are + // in CamelCase form, thus we can short-circut if the first character of the class name is not upper-case. + continue; } if ( ! isset( $this->used_class_names[ $class_name ] ) ) { @@ -756,6 +762,32 @@ private function has_used_attributes( $attribute_names ) { return true; } + /** + * Whether a given class is allowed to be styled in . + * + * That component has child classes that won't be present in the document yet. + * So get whether a class is an allowed child. + * + * @since 1.5.0 + * @link https://github.com/airbnb/react-dates/tree/05356/src/components + * + * @param string $class The name of the class to evaluate. + * @return bool Whether the class is allowed as a child of . + */ + private function is_class_allowed_in_amp_date_picker( $class ) { + static $class_prefixes = [ + 'CalendarDay', + 'CalendarMonth', + 'CalendarMonthGrid', + 'DayPicker', + 'DayPickerKeyboardShortcuts', + 'DayPickerNavigation', + 'KeyboardShortcutRow', + ]; + + return in_array( strtok( $class, '_' ), $class_prefixes, true ); + } + /** * Run logic before any sanitizers are run. * diff --git a/tests/php/test-amp-style-sanitizer.php b/tests/php/test-amp-style-sanitizer.php index 319f1a51f08..75259f86142 100644 --- a/tests/php/test-amp-style-sanitizer.php +++ b/tests/php/test-amp-style-sanitizer.php @@ -657,6 +657,46 @@ static function( $preempt, $request, $url ) { */ public function get_amp_selector_data() { return [ + 'amp-date-picker-allowed-child-class-not-tree-shaken' => [ + '
', + 'amp-date-picker .CalendarMonth_caption{border-bottom: 10px} div amp-date-picker .amp-date-picker-selecting{margin-right:10px}', + 'amp-date-picker .CalendarMonth_caption{border-bottom:10px}div amp-date-picker .amp-date-picker-selecting{margin-right:10px}', // This class is an allowed child, so it shouldn't be removed. + ], + 'amp-date-picker-single-allowed-child-class-not-tree-shaken' => [ + '
', + '.DayPicker_weekHeaders {border-bottom: 10px}', + '.DayPicker_weekHeaders{border-bottom:10px}', + ], + 'amp-date-picker-allowed-container-child-class-not-tree-shaken' => [ + '
', + 'amp-date-picker .amp-date-picker-calendar-container{border-bottom: 10px} div amp-date-picker .amp-date-picker-selecting{margin-right:10px}', + 'amp-date-picker .amp-date-picker-calendar-container{border-bottom:10px}div amp-date-picker .amp-date-picker-selecting{margin-right:10px}', + ], + 'valid-class-wrapping-amp-date-picker-not-tree-shaken' => [ + '
', + '.foo-baz amp-date-picker .CalendarMonth_caption{border-bottom: 10px} div amp-date-picker .amp-date-picker-selecting{margin-right:10px}', + '.foo-baz amp-date-picker .CalendarMonth_caption{border-bottom:10px}div amp-date-picker .amp-date-picker-selecting{margin-right:10px}', + ], + 'amp-date-picker-valid-child-tree-shaken-if-component-not-in-document' => [ + '
AMP Date Picker Not Present
', + 'amp-date-picker .CalendarMonth_caption{border-bottom: 10px}', + '', + ], + 'amp-date-picker-similar-disallowed-child-class-tree-shaken' => [ + '
', + 'amp-date-picker .CalendarFoo {border-bottom: 10px}', + '', + ], + 'amp-date-picker-disallowed-child-class-tree-shaken' => [ + '
', + 'amp-date-picker .random-class{border-bottom: 10px}', + '', + ], + 'amp-date-picker-non-children-tree-shaken' => [ + '
', + 'amp-date-picker .CalendarMonth_caption{border-bottom: 10px} .unrelated{color:#fff}', + 'amp-date-picker .CalendarMonth_caption{border-bottom:10px}', // Non-children of the amp-date-picker should still be tree-shaken if they're not in the DOM. + ], 'img' => [ sprintf( '
', admin_url( 'images/wordpress-logo.png' ) ), 'div img.logo{border:solid 1px red}',