-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Fix Measuring Child AMP Elements #9279
Changes from all commits
848f2ea
2215d32
0166ed3
219428c
1652bfe
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 | ||
---|---|---|---|---|
|
@@ -19,9 +19,10 @@ import { | |||
layoutRectsOverlap, | ||||
moveLayoutRect, | ||||
} from '../layout-rect'; | ||||
import {Layout} from '../layout'; | ||||
import {dev} from '../log'; | ||||
import {startsWith} from '../string'; | ||||
import {toggle, computedStyle} from '../style'; | ||||
import {isAmpElement} from '../dom'; | ||||
|
||||
const TAG = 'Resource'; | ||||
const RESOURCE_PROP_ = '__AMP__RESOURCE'; | ||||
|
@@ -147,6 +148,9 @@ export class Resource { | |||
/** @private {!AmpElement|undefined|null} */ | ||||
this.owner_ = undefined; | ||||
|
||||
/** @private {!AmpElement|undefined|null} */ | ||||
this.ampAncestor_ = undefined; | ||||
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. This needs to be discussed a bit more. But CSS changes seem rather clear to me. Can we merge them first and split this part into another PR? I'd like to get a bit deeper on this. 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. Done. |
||||
|
||||
/** @private {!ResourceState} */ | ||||
this.state_ = element.isBuilt() ? ResourceState.NOT_LAID_OUT : | ||||
ResourceState.NOT_BUILT; | ||||
|
@@ -215,21 +219,41 @@ export class Resource { | |||
this.owner_ = owner; | ||||
} | ||||
|
||||
/** | ||||
* Returns an ancestor amp element or null. | ||||
* @return {?AmpElement} | ||||
*/ | ||||
getAmpAncestor() { | ||||
if (this.ampAncestor_ === undefined) { | ||||
let ancestor = null; | ||||
for (let n = this.element.parentElement; n; n = n.parentElement) { | ||||
if (isAmpElement(n)) { | ||||
ancestor = n; | ||||
break; | ||||
} | ||||
} | ||||
this.ampAncestor_ = ancestor; | ||||
} | ||||
return this.ampAncestor_; | ||||
} | ||||
|
||||
/** | ||||
* Returns an owner element or null. | ||||
* @return {?AmpElement} | ||||
*/ | ||||
getOwner() { | ||||
if (this.owner_ === undefined) { | ||||
for (let n = this.element; n; n = n.parentElement) { | ||||
if (n[OWNER_PROP_]) { | ||||
this.owner_ = n[OWNER_PROP_]; | ||||
break; | ||||
const ancestor = this.getAmpAncestor(); | ||||
let owner = null; | ||||
if (ancestor) { | ||||
for (let n = this.element; n !== ancestor; n = n.parentElement) { | ||||
if (n[OWNER_PROP_]) { | ||||
owner = n[OWNER_PROP_]; | ||||
break; | ||||
} | ||||
} | ||||
} | ||||
if (this.owner_ === undefined) { | ||||
this.owner_ = null; | ||||
} | ||||
this.owner_ = owner; | ||||
} | ||||
return this.owner_; | ||||
} | ||||
|
@@ -365,19 +389,31 @@ export class Resource { | |||
* transitioned to the "ready for layout" state. | ||||
*/ | ||||
measure() { | ||||
let current = this; | ||||
let ancestor = this.getAmpAncestor(); | ||||
// Check if the element is ready to be measured. | ||||
// Placeholders are special. They are technically "owned" by parent AMP | ||||
// elements, sized by parents, but laid out independently. This means | ||||
// that placeholders need to at least wait until the parent element | ||||
// has been stubbed. We can tell whether the parent has been stubbed | ||||
// by whether a resource has been attached to it. | ||||
if (this.isPlaceholder_ && | ||||
this.element.parentElement && | ||||
// Use prefix to recognize AMP element. This is necessary because stub | ||||
// may not be attached yet. | ||||
startsWith(this.element.parentElement.tagName, 'AMP-') && | ||||
!(RESOURCE_PROP_ in this.element.parentElement)) { | ||||
return; | ||||
while (ancestor) { | ||||
const resource = Resource.forElementOptional(ancestor); | ||||
// If there's an AMP ancestor that's still not stubbed (has a Resource), | ||||
// we don't know what to do yet. | ||||
if (!resource) { | ||||
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 believe, unfortunately, we do have a very few 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 don't understand. 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.e.
This code is probably benign, since no one ads stuff into this element from what I can tell. But others might not be so lucky. 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. @zhouyx: Is that supposed to be publicly stylable? 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. yes 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. Reviewed, #9283. Will update so those aren't considered. |
||||
return; | ||||
} | ||||
// If the current resource (not the ancestor) is a placeholder, allow it to | ||||
// measure. This is because placeholders are laid out independently of | ||||
// the ancestor element (even if they are sized and "owned" by it). | ||||
if (current.isPlaceholder_) { | ||||
break; | ||||
} | ||||
// If this ancestor isn't built yet, that means we don't know what kind | ||||
// of styles will be applied to this element. We must wait. | ||||
// Unless the ancestor is a container, in which case the element will | ||||
// define its own sizing. | ||||
if (!ancestor.isBuilt() && ancestor.getLayout() !== Layout.CONTAINER) { | ||||
return; | ||||
} | ||||
current = resource; | ||||
ancestor = current.getAmpAncestor(); | ||||
} | ||||
|
||||
this.isMeasureRequested_ = false; | ||||
|
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.
why is checking element.classList.contain('i-amphtml-element') not sufficient?
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.
From the original comment
That means we have to be able to tell regardless of where the element is in the CustomElement lifecycle. And an element has to have been stubbed (
#connectedCallback
, the first step) to have thei-amphtml-element
class.