diff --git a/bin/amphtml-update.py b/bin/amphtml-update.py
index f9ddb389312..87f8aaad937 100755
--- a/bin/amphtml-update.py
+++ b/bin/amphtml-update.py
@@ -799,6 +799,9 @@ def GetTagRules(tag_spec):
if tag_spec.HasField('unique_warning'):
tag_rules['unique_warning'] = tag_spec.unique_warning
+ if tag_spec.HasField('siblings_disallowed'):
+ tag_rules['siblings_disallowed'] = tag_spec.siblings_disallowed
+
if tag_spec.HasField('child_tags'):
child_tags = collections.defaultdict( lambda: [] )
for field in tag_spec.child_tags.ListFields():
diff --git a/bin/latest-extension-versions.json b/bin/latest-extension-versions.json
index 3a00f2a877b..991e2b07a28 100644
--- a/bin/latest-extension-versions.json
+++ b/bin/latest-extension-versions.json
@@ -115,6 +115,7 @@
"amp-sticky-ad" : "1.0",
"amp-story" : "1.0",
"amp-story-360" : "0.1",
+ "amp-story-audio-sticker" : "0.1",
"amp-story-auto-ads" : "0.1",
"amp-story-auto-analytics" : "0.1",
"amp-story-captions" : "0.1",
diff --git a/includes/sanitizers/class-amp-allowed-tags-generated.php b/includes/sanitizers/class-amp-allowed-tags-generated.php
index 45b87352fb9..22c9f4c4a64 100644
--- a/includes/sanitizers/class-amp-allowed-tags-generated.php
+++ b/includes/sanitizers/class-amp-allowed-tags-generated.php
@@ -249,6 +249,7 @@ class AMP_Allowed_Tags_Generated {
'address',
'amp-analytics',
'amp-audio',
+ 'amp-bodymovin-animation',
'amp-date-countdown',
'amp-date-display',
'amp-experiment',
@@ -271,6 +272,7 @@ class AMP_Allowed_Tags_Generated {
'amp-story-interactive-poll',
'amp-story-interactive-quiz',
'amp-story-interactive-results',
+ 'amp-story-interactive-slider',
'amp-story-panning-media',
'amp-story-shopping-tag',
'amp-timeago',
@@ -607,6 +609,14 @@ class AMP_Allowed_Tags_Generated {
'attributionreportto' => array(),
'attributionsourceeventid' => array(),
'attributionsourceid' => array(),
+ 'attributionsrc' => array(
+ 'value_url' => array(
+ 'allow_empty' => true,
+ 'protocol' => array(
+ 'https',
+ ),
+ ),
+ ),
'border' => array(),
'conversiondestination' => array(),
'data-amp-bind-href' => array(),
@@ -5238,6 +5248,14 @@ class AMP_Allowed_Tags_Generated {
array(
'attr_spec_list' => array(
'allow-ssr-img' => array(),
+ 'attributionsrc' => array(
+ 'value_url' => array(
+ 'allow_empty' => true,
+ 'protocol' => array(
+ 'https',
+ ),
+ ),
+ ),
'media' => array(),
'noloading' => array(
'value' => array(
@@ -6102,6 +6120,9 @@ class AMP_Allowed_Tags_Generated {
),
),
),
+ 'desktop-aspect-ratio' => array(
+ 'value_regex' => '\\d+:\\d+',
+ ),
'entity' => array(),
'entity-logo-src' => array(
'value_url' => array(
@@ -6194,6 +6215,7 @@ class AMP_Allowed_Tags_Generated {
'amp-story-bookend',
'amp-story-page',
'amp-story-social-share',
+ 'amp-story-subscriptions',
),
'mandatory_min_num_child_tags' => 1,
),
@@ -6201,6 +6223,7 @@ class AMP_Allowed_Tags_Generated {
'requires_extension' => array(
'amp-story',
),
+ 'siblings_disallowed' => true,
),
),
),
@@ -6306,7 +6329,15 @@ class AMP_Allowed_Tags_Generated {
),
'amp-story-auto-ads' => array(
array(
- 'attr_spec_list' => array(),
+ 'attr_spec_list' => array(
+ 'src' => array(
+ 'value_url' => array(
+ 'protocol' => array(
+ 'https',
+ ),
+ ),
+ ),
+ ),
'tag_spec' => array(
'mandatory_parent' => 'amp-story',
'requires_extension' => array(
@@ -6921,6 +6952,55 @@ class AMP_Allowed_Tags_Generated {
),
),
),
+ 'amp-story-interactive-slider' => array(
+ array(
+ 'attr_spec_list' => array(
+ 'chip-style' => array(
+ 'value' => array(
+ 'shadow',
+ 'flat',
+ 'transparent',
+ ),
+ ),
+ 'endpoint' => array(
+ 'mandatory' => true,
+ 'value_url' => array(
+ 'allow_empty' => false,
+ 'allow_relative' => false,
+ 'protocol' => array(
+ 'https',
+ ),
+ ),
+ ),
+ 'id' => array(
+ 'mandatory' => true,
+ ),
+ 'option-1-text' => array(
+ 'mandatory' => false,
+ ),
+ 'prompt-size' => array(
+ 'value' => array(
+ 'small',
+ 'medium',
+ 'large',
+ ),
+ ),
+ 'prompt-text' => array(),
+ 'theme' => array(
+ 'value' => array(
+ 'light',
+ 'dark',
+ ),
+ ),
+ ),
+ 'tag_spec' => array(
+ 'mandatory_ancestor' => 'amp-story-grid-layer',
+ 'requires_extension' => array(
+ 'amp-story-interactive',
+ ),
+ ),
+ ),
+ ),
'amp-story-page' => array(
array(
'attr_spec_list' => array(
@@ -7226,12 +7306,21 @@ class AMP_Allowed_Tags_Generated {
'amp-story-subscriptions' => array(
array(
'attr_spec_list' => array(
+ 'additional-description' => array(),
+ 'description' => array(
+ 'mandatory' => true,
+ ),
+ 'headline' => array(),
'media' => array(),
'noloading' => array(
'value' => array(
'',
),
),
+ 'price' => array(
+ 'mandatory' => true,
+ ),
+ 'subscriptions-page-index' => array(),
),
'tag_spec' => array(
'amp_layout' => array(
@@ -8004,6 +8093,11 @@ class AMP_Allowed_Tags_Generated {
'mandatory' => true,
'value_regex' => '[0-9]+',
),
+ 'do-not-track' => array(
+ 'value' => array(
+ '',
+ ),
+ ),
'media' => array(),
'noloading' => array(
'value' => array(
@@ -11031,6 +11125,7 @@ class AMP_Allowed_Tags_Generated {
),
),
),
+ 'xssi-prefix' => array(),
),
'tag_spec' => array(
'disallowed_ancestor' => array(
@@ -15127,10 +15222,10 @@ class AMP_Allowed_Tags_Generated {
),
'nonce' => array(),
'src' => array(
- 'dispatch_key' => 2,
'mandatory' => true,
'value' => array(
'https://cdn.ampproject.org/v0.js',
+ 'https://ampjs.org/v0.js',
),
),
'type' => array(
@@ -15173,6 +15268,7 @@ class AMP_Allowed_Tags_Generated {
'mandatory' => true,
'value' => array(
'https://cdn.ampproject.org/lts/v0.js',
+ 'https://ampjs.org/lts/v0.js',
),
),
'type' => array(
@@ -15721,6 +15817,7 @@ class AMP_Allowed_Tags_Generated {
'requires_extension' => array(
'amp-story',
),
+ 'siblings_disallowed' => true,
'spec_name' => 'amp-story-bookend extension .json script',
'unique' => true,
),
@@ -15741,6 +15838,7 @@ class AMP_Allowed_Tags_Generated {
'requires_extension' => array(
'amp-story',
),
+ 'siblings_disallowed' => true,
'spec_name' => 'amp-story-social-share extension .json script',
'unique' => true,
),
@@ -22766,6 +22864,7 @@ class AMP_Allowed_Tags_Generated {
'unique' => false,
),
),
+ 'siblings_disallowed' => true,
'spec_name' => 'AMP-MEGA-MENU > NAV',
),
),
diff --git a/includes/sanitizers/class-amp-rule-spec.php b/includes/sanitizers/class-amp-rule-spec.php
index 3df2e0d667e..644b42bc9c3 100644
--- a/includes/sanitizers/class-amp-rule-spec.php
+++ b/includes/sanitizers/class-amp-rule-spec.php
@@ -37,6 +37,7 @@ abstract class AMP_Rule_Spec {
const MANDATORY_PARENT = 'mandatory_parent';
const DESCENDANT_TAG_LIST = 'descendant_tag_list';
const CHILD_TAGS = 'child_tags';
+ const SIBLINGS_DISALLOWED = 'siblings_disallowed';
/*
* HTML Element Attribute rule names
diff --git a/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php b/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php
index 47069101e68..aac823f191a 100644
--- a/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php
+++ b/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php
@@ -45,6 +45,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
const DISALLOWED_CHILD_TAG = 'DISALLOWED_CHILD_TAG';
const DISALLOWED_DESCENDANT_TAG = 'DISALLOWED_DESCENDANT_TAG';
const DISALLOWED_FIRST_CHILD_TAG = 'DISALLOWED_FIRST_CHILD_TAG';
+ const DISALLOWED_SIBLING_TAG = 'DISALLOWED_SIBLING_TAG';
const DISALLOWED_PROCESSING_INSTRUCTION = 'DISALLOWED_PROCESSING_INSTRUCTION';
const DISALLOWED_PROPERTY_IN_ATTR_VALUE = 'DISALLOWED_PROPERTY_IN_ATTR_VALUE';
const DISALLOWED_RELATIVE_URL = 'DISALLOWED_RELATIVE_URL';
@@ -749,6 +750,11 @@ static function ( $validation_error ) {
}
}
+ // If the node disallows any siblings, remove them.
+ if ( ! empty( $tag_spec[ AMP_Rule_Spec::SIBLINGS_DISALLOWED ] ) ) {
+ $this->remove_disallowed_siblings( $node, $this->get_spec_name( $node, $tag_spec ) );
+ }
+
// After attributes have been sanitized (and potentially removed), if mandatory attribute(s) are missing, remove the element.
$missing_mandatory_attributes = $this->get_missing_mandatory_attributes( $attr_spec_list, $node );
if ( ! empty( $missing_mandatory_attributes ) ) {
@@ -2453,6 +2459,46 @@ private function remove_disallowed_descendants( DOMElement $node, $allowed_desce
}
}
+ /**
+ * Loop through node's siblings and remove them.
+ *
+ * @param DOMElement $node Node.
+ * @param string $spec_name Spec name.
+ */
+ private function remove_disallowed_siblings( DOMElement $node, $spec_name ) {
+ $prev_sibling = $node->previousSibling;
+ while ( null !== $prev_sibling ) {
+ $prev_prev_sibling = $prev_sibling->previousSibling;
+ if ( $prev_sibling instanceof Element ) {
+ $this->remove_invalid_child(
+ $prev_sibling,
+ [
+ 'code' => self::DISALLOWED_SIBLING_TAG,
+ 'sibling' => $node->nodeName,
+ 'spec_name' => $spec_name,
+ ]
+ );
+ }
+ $prev_sibling = $prev_prev_sibling;
+ }
+
+ $next_sibling = $node->nextSibling;
+ while ( null !== $next_sibling ) {
+ $next_next_sibling = $next_sibling->nextSibling;
+ if ( $next_sibling instanceof Element ) {
+ $this->remove_invalid_child(
+ $next_sibling,
+ [
+ 'code' => self::DISALLOWED_SIBLING_TAG,
+ 'sibling' => $node->nodeName,
+ 'spec_name' => $spec_name,
+ ]
+ );
+ }
+ $next_sibling = $next_next_sibling;
+ }
+ }
+
/**
* Check whether the node validates the constraints for children.
*
diff --git a/tests/php/test-tag-and-attribute-sanitizer.php b/tests/php/test-tag-and-attribute-sanitizer.php
index 29d83ad9308..52adaf8359f 100644
--- a/tests/php/test-tag-and-attribute-sanitizer.php
+++ b/tests/php/test-tag-and-attribute-sanitizer.php
@@ -1397,6 +1397,48 @@ static function() {
[ 'amp-sidebar', 'amp-story' ],
],
+ 'amp_story_with_disallowed_sibling' => [
+ str_replace(
+ [ "\n", "\t" ],
+ '',
+ '
+
+
+ This is the cover page of this story.Hello World
+
This is the cover page of this story.
+