-
Notifications
You must be signed in to change notification settings - Fork 384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
1094: Transform CSS selectors according to sanitizer HTML element to AMP component conversions #1175
1094: Transform CSS selectors according to sanitizer HTML element to AMP component conversions #1175
Changes from 7 commits
e530045
f2395ee
958fd48
488ebd2
077a094
ed1cc03
6091533
7a218aa
fcc0b44
d815891
12c9ff8
91e3eef
04f6b85
19db2c1
1b8405a
592aca7
a564410
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ abstract class AMP_Base_Sanitizer { | |
* Value for <amp-image-lightbox> ID. | ||
* | ||
* @since 1.0 | ||
* @todo Move to AMP_Img_Sanitizer? | ||
* | ||
* @const string | ||
*/ | ||
|
@@ -121,11 +122,41 @@ public function __construct( $dom, $args = array() ) { | |
} | ||
} | ||
|
||
/** | ||
* Get mapping of HTML selectors to the AMP component selectors which they may be converted into. | ||
* | ||
* @return array Mapping. | ||
*/ | ||
public function get_selector_conversion_mapping() { | ||
return array(); | ||
} | ||
|
||
/** | ||
* Run logic before any sanitizers are run. | ||
* | ||
* After the sanitizers are instantiated but before calling sanitize on each of them, this | ||
* method is called with list of all the instantiated sanitizers. | ||
* | ||
* @todo Call this init? | ||
* | ||
* @param AMP_Base_Sanitizer[] $sanitizers Sanitizers. | ||
*/ | ||
public function before_sanitize( $sanitizers ) {} | ||
|
||
/** | ||
* Sanitize the HTML contained in the DOMDocument received by the constructor | ||
*/ | ||
abstract public function sanitize(); | ||
|
||
/** | ||
* Run logic after all sanitizers are finished. | ||
* | ||
* @todo Do we even need this? | ||
* | ||
* @param AMP_Base_Sanitizer[] $sanitizers Sanitizers. | ||
*/ | ||
public function after_sanitize( $sanitizers ) {} | ||
|
||
/** | ||
* Return array of values that would be valid as an HTML `script` element. | ||
* | ||
|
@@ -444,6 +475,8 @@ public function get_data_amp_attributes( $node ) { | |
if ( isset( $parent_attributes['data-amp-noloading'] ) && true === filter_var( $parent_attributes['data-amp-noloading'], FILTER_VALIDATE_BOOLEAN ) ) { | ||
$attributes['noloading'] = $parent_attributes['data-amp-noloading']; | ||
} | ||
|
||
// @todo Move to AMP_Img_Sanitizer subclassed method? | ||
if ( isset( $parent_attributes['data-amp-lightbox'] ) && true === filter_var( $parent_attributes['data-amp-lightbox'], FILTER_VALIDATE_BOOLEAN ) ) { | ||
$attributes['lightbox'] = true; | ||
} | ||
|
@@ -466,6 +499,7 @@ public function filter_data_amp_attributes( $attributes, $amp_data ) { | |
if ( isset( $amp_data['noloading'] ) ) { | ||
$attributes['data-amp-noloading'] = ''; | ||
} | ||
// @todo Move lightbox condition to subclassed method of AMP_Img_Sanitizer. | ||
if ( isset( $amp_data['lightbox'] ) ) { | ||
$attributes['data-amp-lightbox'] = ''; | ||
$attributes['on'] = 'tap:' . self::AMP_IMAGE_LIGHTBOX_ID; | ||
|
@@ -514,6 +548,8 @@ public function filter_attachment_layout_attributes( $node, $new_attributes, $la | |
|
||
/** | ||
* Add <amp-image-lightbox> element to body tag if it doesn't exist yet. | ||
* | ||
* @todo Move this to AMP_Img_Sanitizer? | ||
*/ | ||
public function maybe_add_amp_image_lightbox_node() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this is also used by |
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -182,6 +182,13 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer { | |
*/ | ||
private $processed_imported_stylesheet_urls = array(); | ||
|
||
/** | ||
* Mapping of HTML element selectors to AMP selector elements. | ||
* | ||
* @var array | ||
*/ | ||
private $selector_mappings = array(); | ||
|
||
/** | ||
* Get error codes that can be raised during parsing of CSS. | ||
* | ||
|
@@ -308,6 +315,30 @@ private function get_used_tag_names() { | |
return $this->used_tag_names; | ||
} | ||
|
||
/** | ||
* Run logic before any sanitizers are run. | ||
* | ||
* After the sanitizers are instantiated but before calling sanitize on each of them, this | ||
* method is called with list of all the instantiated sanitizers. | ||
* | ||
* @param AMP_Base_Sanitizer[] $sanitizers Sanitizers. | ||
*/ | ||
public function before_sanitize( $sanitizers ) { | ||
parent::before_sanitize( $sanitizers ); | ||
|
||
foreach ( $sanitizers as $sanitizer ) { | ||
foreach ( $sanitizer->get_selector_conversion_mapping() as $html_selectors => $amp_selectors ) { | ||
if ( ! isset( $this->selector_mappings[ $html_selectors ] ) ) { | ||
$this->selector_mappings[ $html_selectors ] = $amp_selectors; | ||
} else { | ||
$this->selector_mappings[ $html_selectors ] = array_unique( | ||
array_merge( $this->selector_mappings[ $html_selectors ], $amp_selectors ) | ||
); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Sanitize CSS styles within the HTML contained in this instance's DOMDocument. | ||
* | ||
|
@@ -647,7 +678,7 @@ private function fetch_external_stylesheet( $url ) { | |
private function process_stylesheet( $stylesheet, $options = array() ) { | ||
$parsed = null; | ||
$cache_key = null; | ||
$cache_group = 'amp-parsed-stylesheet-v6'; | ||
$cache_group = 'amp-parsed-stylesheet-v7'; | ||
|
||
$cache_impacting_options = array_merge( | ||
wp_array_slice_assoc( | ||
|
@@ -1236,6 +1267,10 @@ private function real_path_urls( $urls, $stylesheet_url ) { | |
private function process_css_declaration_block( RuleSet $ruleset, CSSList $css_list, $options ) { | ||
$results = array(); | ||
|
||
if ( $ruleset instanceof DeclarationBlock ) { | ||
$this->ampify_ruleset_selectors( $ruleset ); | ||
} | ||
|
||
// Remove disallowed properties. | ||
if ( ! empty( $options['property_whitelist'] ) ) { | ||
$properties = $ruleset->getRules(); | ||
|
@@ -1754,6 +1789,40 @@ private function finalize_styles() { | |
} | ||
} | ||
|
||
/** | ||
* Convert CSS selectors. | ||
* | ||
* @param DeclarationBlock $ruleset Ruleset. | ||
*/ | ||
private function ampify_ruleset_selectors( $ruleset ) { | ||
$selectors = array(); | ||
$replacements = 0; | ||
foreach ( $ruleset->getSelectors() as $old_selector ) { | ||
$edited_selectors = array( $old_selector->getSelector() ); | ||
foreach ( $this->selector_mappings as $html_selector => $amp_selectors ) { // Note: The $selector_mappings array contains ~6 items. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These notes are here because 4 level of loop nesting is normally something that is a big red flag for performance. |
||
$html_pattern = '/(?<=^|[^a-z0-9_-])' . preg_quote( $html_selector ) . '(?=$|[^a-z0-9_-])/i'; | ||
foreach ( $edited_selectors as &$edited_selector ) { // Note: The $edited_selectors array contains only item in the normal case. | ||
$original_selector = $edited_selector; | ||
$amp_selector = array_shift( $amp_selectors ); | ||
$edited_selector = preg_replace( $html_pattern, $amp_selector, $edited_selector, -1, $count ); | ||
if ( ! $count ) { | ||
continue; | ||
} | ||
$replacements += $count; | ||
while ( ! empty( $amp_selectors ) ) { // Note: This array contains only a couple items. | ||
$amp_selector = array_shift( $amp_selectors ); | ||
$edited_selectors[] = preg_replace( $html_pattern, $amp_selector, $original_selector, -1, $count ); | ||
} | ||
} | ||
} | ||
$selectors = array_merge( $selectors, $edited_selectors ); | ||
} | ||
|
||
if ( $replacements > 0 ) { | ||
$ruleset->setSelectors( $selectors ); | ||
} | ||
} | ||
|
||
/** | ||
* Finalize a stylesheet set (amp-custom or amp-keyframes). | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,8 +63,16 @@ public static function sanitize_document( &$dom, $sanitizer_classes, $args ) { | |
|
||
$return_styles = ! empty( $args['return_styles'] ); | ||
unset( $args['return_styles'] ); | ||
|
||
/** | ||
* Sanitizers. | ||
* | ||
* @var AMP_Base_Sanitizer[] $sanitizers | ||
*/ | ||
$sanitizers = array(); | ||
|
||
// Instantiate the sanitizers. | ||
foreach ( $sanitizer_classes as $sanitizer_class => $sanitizer_args ) { | ||
$sanitize_class_start = microtime( true ); | ||
if ( ! class_exists( $sanitizer_class ) ) { | ||
/* translators: %s is sanitizer class */ | ||
_doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Sanitizer (%s) class does not exist', 'amp' ), esc_html( $sanitizer_class ) ), '0.4.1' ); | ||
|
@@ -84,6 +92,18 @@ public static function sanitize_document( &$dom, $sanitizer_classes, $args ) { | |
continue; | ||
} | ||
|
||
$sanitizers[ $sanitizer_class ] = $sanitizer; | ||
} | ||
|
||
// Let the sanitizers know about each other prior to sanitizing. | ||
foreach ( $sanitizers as $sanitizer ) { | ||
$sanitizer->before_sanitize( $sanitizers ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do think we should rename this to |
||
} | ||
|
||
// Sanitize. | ||
foreach ( $sanitizers as $sanitizer ) { | ||
$sanitize_class_start = microtime( true ); | ||
|
||
$sanitizer->sanitize(); | ||
|
||
$scripts = array_merge( $scripts, $sanitizer->get_scripts() ); | ||
|
@@ -96,6 +116,11 @@ public static function sanitize_document( &$dom, $sanitizer_classes, $args ) { | |
AMP_Response_Headers::send_server_timing( 'amp_sanitize', -$sanitize_class_start, $sanitizer_class ); | ||
} | ||
|
||
// Let the sanitizers. | ||
foreach ( $sanitizers as $sanitizer ) { | ||
$sanitizer->after_sanitize( $sanitizers ); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're not using this, I think we should just kill |
||
|
||
return compact( 'scripts', 'styles', 'stylesheets' ); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is illegal in AMP to use
i-amphtml-replaced-content
class names. See:https://www.ampproject.org/docs/fundamentals/spec#classes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, think I even saw a todo note recently somewhere as well to remove these from CSS within style sanitizer and realized that it's illegal. Will look into which selector to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since tree shaking isn't done for attribute values, I suggest the way to fix this is to use an attribute selector like
[src]
.