-
Notifications
You must be signed in to change notification settings - Fork 4k
Rewrite PositionObserver for better sharing #10813
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
Conversation
adae0fa to
f636fd1
Compare
c81b1bd to
bea519d
Compare
|
PTAL |
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.
Awesome refactoring! few small requests.
ads/inabox/position-observer.js
Outdated
| } | ||
|
|
||
| connect() { | ||
| //console.log('connect but do nothing! should fail'); |
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.
remove
|
|
||
| import {Services} from '../../src/services'; | ||
| import {registerServiceBuilderForDoc} from '../../src/service'; | ||
| import { |
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.
nit: one line for single imports
| * @param {function(?)} unusedCallback | ||
| * @return {function()} | ||
| */ | ||
| onHostMessage(unusedCallback) { |
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.
nit {}
ads/inabox/position-observer.js
Outdated
|
|
||
|
|
||
| /** | ||
| * @implements {PosObViewportInfoDef} |
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.
naming suggestions:
PositionObserverHostInterface
PositionObserverNonAmpHost
etc...
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.
Haha I am really bad at naming.
changed to PosObHostInterfaceDef, then PosObAmpdocHostInterface PosObInaboxHostInterface PosObNonAmpHostInterface
| // If not receive iframe position from host, or if iframe is outside vp | ||
| return null; | ||
| } | ||
| if (!element.inIframePositionRect) { |
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.
can this really happen? like getLayoutRect be called before inIframePositionRect is set for InABoxCase?
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, but not because getLayoutRect is called before.
It happens when the old inIframePositionRect is outdated, and we clear the stored value. For example, if the inabox iframe gets resized, we would want to refresh the inIframePositionRect value.
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.
I see. So to make sure I understand this right: I was expecting another AmpDocPositionObserver in here monitoring the element but I guess in-a-box is not scrollable (which will likely stay true for forseeable future) so the only real event that can invalidate this is a resize change and the host handles that. Correct?
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.
Correct!
| if (prePos | ||
| && layoutRectEquals(prePos.positionRect, position.positionRect) | ||
| && layoutRectEquals(prePos.viewportRect, position.viewportRect)) { | ||
| // position doesn't change, do nothing. |
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.
didn't
|
|
||
| /** | ||
| * This should always be called in vsync. | ||
| * @param {boolean=} force |
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.
this is opt too right?
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. force can be used to support fidelity. Viewport resize event for example would require we force update all entries, while viewport scroll doesn't
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.
ok opt_force then?
| * @param {!Element} element | ||
| */ | ||
| unobserve(element) { | ||
| for (let i = 0; i < this.entries_.length; i++) { |
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.
filterSplice in array.js can do this
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.
Not exactly. filterSplice splice every array item that filter false. Here we can use some optimization to only splice the first item that filter false.
Do you think it is worth to create a new function for this?
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.
I see. Neither the short-circuitingnot not using filterSplice here are a big deal here. Up to you.
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.
Yup, #filterSplice is meant only for multi-removals.
| /** @private {function()} */ | ||
| this.boundStopScroll_ = debounce(this.win_, () => { | ||
| this.inScroll_ = false; | ||
| }, 500); |
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.
const for 500
| // Need to notify that element gets outside viewport | ||
| // NOTE: This is required for inabox position observer. | ||
| this.position = null; | ||
| position.positionRect = null; |
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 are we mutating an object we don't own?
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.
Because we don't want to expose the element position when it gets out of viewport. So set its positionRect to null.
| /** @private {!Window} */ | ||
| this.win_ = win; | ||
|
|
||
| /** @private {!Array<!Object>} */ |
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.
Update the typedef.
| unobserve(element) { | ||
| for (let i = 0; i < this.entries_.length; i++) { | ||
| if (this.entries_[i].element == element) { | ||
| this.entries_[i].handler = null; |
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.
This shouldn't be necessary.
| for (let i = 0; i < this.entries_.length; i++) { | ||
| const entry = this.entries_[i]; | ||
| if (opt_remeasure) { | ||
| entry.element.inIframePositionRect = null; |
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 does an Element have a #inIframePositionRect ?
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.
😢 I don't like this either. But in the case of inabox, I know an element's relative pos to the inabox window is rarely changed. So I store the value in elemenet['inIframePositionRect'] (need avoid using .) to avoid unnecessary getBoundingClientRect() call. However in the case of inabox window resize I'll need to clear the stored value. That's why I set it back to null here.
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.
I realized it's really not good to store the info to element. I moved it to entry.inInframePositionRect.
| continue; | ||
| } | ||
| // Reset entry.turn value. | ||
| if (!force) { |
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.
Nit: join this with the previous if statement.
| * @param {!Event} event | ||
| * @private | ||
| */ | ||
| onHostMessageHandler_(event) { |
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 does a standard position observer care about this?
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.
Two types of PositionObserver
- Top doc level PosOb where the doc viewport is the top level viewport. (not needed)
- Iframe PosOb where it requires position message from host doc (need this handler)
I am trying to let the PositionObserver to work in all scenarios, and that's why I have this #onHostMessageHandler_.
| this.viewport_.storeIframePosition(iframePosition); | ||
|
|
||
| // do this in vsyn.measure | ||
| this.vsync_.measure(() => { |
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.
Call #schedulePass_.
| */ | ||
| onResizeHandler_() { | ||
| this.needRefreshOnMessage_ = true; | ||
| this.vsync_.measure(() => { |
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.
Call #schedulePass_.
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.
But in my current code, #schedulePass_ actively remeasure element position in every animation frame until scroll stops. I can modify the #schedulePass_ function, let me know if you like that better, would become #schedulePass_(isOneTime)
ebb29ab to
9272904
Compare
|
See #10995. We will need the inabox position api anyway. Sync with @aghassemi and @lannka offline. Somehow decided to switch from Solution B to solution A. Parent window inform iframe window to start observe position through postMessage (‘start’, ‘end’)
CORS iframe register the iframe.
|
9272904 to
7314b7e
Compare
| } from '../../../src/layout'; | ||
| import { | ||
| installPositionObserverServiceForDoc, | ||
| } from '../../../src/service/position-observer/position-observer-impl'; |
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 the position-observer-impl.js position-observer-entry.js are much cleaner after the cleanup, I am OK if we want to move them to one single file. So that we don't need to import from different files or whitelist multiple files.
Ideas?
| // This code is here for demo purposes and enables the required experiments | ||
| // automatically for this demo so that visitors do not need to enable them manually. | ||
| var experiments = ['amp-animation', 'amp-position-observer']; | ||
| var experiments = ['amp-animation', 'amp-position-observer', 'inabox-position-api']; |
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.
@aghassemi Is the scrollbound animation already supported. Basically with the new refractor we need to toggle on inabox-position-api to enable position observer in inabox. Let me know if I should remove the experiment flag in this PR
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.
scrollbound is still under an experiment flag but will be removed soon. if you are okay with removing inabox-position-api experiment, go for it. otherwise mention it in #10875 so they get removed at the same time
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.
I'll mention there : )
| * @typedef {{ | ||
| * positionRect: ?../../layout-rect.LayoutRectDef, | ||
| * viewportRect: !../../layout-rect.LayoutRectDef, | ||
| * relativePos: string, |
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.
let's make this of type RelativePositions
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.
I added a TODO here. Since relativePos is being used in several files. I would rather make this change a following PR.
| */ | ||
| trigger(position) { | ||
|
|
||
| const prePos = this.prevPosition_ ; |
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.
prevPos
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.
done
|
|
||
| // Add the relative position of the element to its viewport | ||
| position.relativePos = layoutRectsRelativePos( | ||
| /** @type {!../../layout-rect.LayoutRectDef} */ (position.positionRect), |
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.
if position.positionRect can't be null, do an assert and assign to a constant so type casting happens only once.
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.
done
| try { | ||
| this.handler_(position); | ||
| } catch (err) { | ||
| // TODO(@zhouyx, #9208) Throw error. |
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 catch if rethrowing anyway?
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.
hmmm you're right! remove try catch block
| PositionObserverFidelity, | ||
| LOW_FIDELITY_FRAME_COUNT, | ||
| } from './position-observer-fidelity'; | ||
| /** @const @private */ |
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.
nit: new line above
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.
fixed
|
|
||
| this.entries_.push(entry); | ||
|
|
||
| if (this.entries_.length == 1) { |
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.
this.entries_.length == 1 was confusing to me initially had to think to see why. Can we track through another variable? like callbackStarted_?
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.
added
src/service/viewport-impl.js
Outdated
| const bPromise = this.binding_.getLayoutRectAsync(el, 0, 0); | ||
| const cPromise = this.binding_.getLayoutRectAsync( | ||
| frameElement, scrollLeft, scrollTop); | ||
| const cPromise = this.binding_.getLayoutRectAsync(frameElement); |
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.
/cc @dvoytenko for the not passing scroll values anymore as I am not sure what the effects of it would be.
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.
I found if scrollTop/scrollLeft are not passed in, binding will calculate latest scrollTop/scrollLeft. That's what I want.
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.
ok, please ping @dvoytenko for the implications of this before merging
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.
Hmm. This sounds odd. A big change from the original change. Please clarify.
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.
I added two new methods to Viewport class:
#getLayoutRectAsync() and #getBoundingRectAsync(). The idea is that in most cases where an element wants to know its layout rect, #getLayoutRectAsync() should be used instead of #getLayoutRect(). And #getLayoutRectAsync() always return the latest rect in certain animation frame.
To implement this, I added a #getLayoutRectAsync() function to ViewportBindingDef class. Here I am trying to implement Viewport #getLayoutRectAsync() with ViewportBindingDef #getLayoutRectAsync() method. I am not passing scroll values here. Because I want to get the latest layout rect, and don't want to use any cached value here. (I just realized this may not work in ViewportBindingNaturalIosEmbed_ case, I'll fix that)
WDYT?
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.
This should definitely be using the cached values, they should always be correct.
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 what I found they are not always correct when we try to calculate rect at a higher rate than browser 'scroll' event. See https://github.com/ampproject/amphtml/blob/master/src/service/viewport-impl.js#L258 that Viewport class use the cached scrollTop_ value when calling #getScrollTop(). But viewport.scrollTop_ is only updated on browser scroll event (throttled already). https://github.com/ampproject/amphtml/blob/master/src/service/viewport-impl.js#L889.
To get the latest layout rect in each animationFrame, I can't use the cached values.
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.
@jridgewell @dvoytenko Maybe we can talk about this offline? My plan to to have Viewport provide functions to get latest rect info at every animationFrame.
| // This code is here for demo purposes and enables the required experiments | ||
| // automatically for this demo so that visitors do not need to enable them manually. | ||
| var experiments = ['amp-animation', 'amp-position-observer']; | ||
| var experiments = ['amp-animation', 'amp-position-observer', 'inabox-position-api']; |
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.
scrollbound is still under an experiment flag but will be removed soon. if you are okay with removing inabox-position-api experiment, go for it. otherwise mention it in #10875 so they get removed at the same time
|
comment addressed |
|
@jridgewell PTAL : ) |
| for (let i = 0; i < this.entries_.length; i++) { | ||
| const entry = this.entries_[i]; | ||
| if (entry.element == element) { | ||
| entry.fidelity = fidelity; |
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.
This if statement should be a method on Entry.
| this.startCallback_(); | ||
| } | ||
|
|
||
| this.callbackStarted_ = true; |
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.
Nit: move into if statement.
| * @param {!Element} element | ||
| */ | ||
| unobserve(element) { | ||
| for (let i = 0; i < this.entries_.length; i++) { |
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.
Yup, #filterSplice is meant only for multi-removals.
| */ | ||
| updateAllEntries(opt_force) { | ||
| for (let i = 0; i < this.entries_.length; i++) { | ||
| const entry = this.entries_[i]; |
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.
This body statement should be a method on Entry.
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.
Still belongs as a method on Entry.
| // P2: do passes on onDomMutation (if available using MutationObserver | ||
| // mostly for in-a-box host, since most DOM mutations are constraint to the | ||
| // AMP elements). | ||
| this.updateAllEntries(); |
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.
Shouldn't this wait for a measure?
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.
#updateAllEntries() call #updateSingleEntries() then call #Viewport.getBoundingRectAsync(). which returns rect in next vsync measure.
| if (!this.inScroll_) { | ||
| // Stop measure if viewport is no longer scrolling | ||
| this.measure_ = false; | ||
| return; |
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.
The positions could still change by this time (without firing a new scroll event). We should probably remeasure again, but not issue another vsync.
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.
But I call updateAllEntries() before checking this.inScroll_. I think that address your comment here.
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.
Lol, ignore me.
src/service/viewport-impl.js
Outdated
| } | ||
|
|
||
| getRectAsync() { | ||
| return this.vsync_.measurePromise(() => { |
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.
This should return a memoized promise until the vsync executes (it'll save a lot of memory).
src/service/viewport-impl.js
Outdated
| const bPromise = this.binding_.getLayoutRectAsync(el, 0, 0); | ||
| const cPromise = this.binding_.getLayoutRectAsync( | ||
| frameElement, scrollLeft, scrollTop); | ||
| const cPromise = this.binding_.getLayoutRectAsync(frameElement); |
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.
This should definitely be using the cached values, they should always be correct.
4c32542 to
1134c19
Compare
1134c19 to
8899bc9
Compare
|
I think #11148 unblock the work here. Changed the PR. PTAL : ) |
| * @param {!PositionInViewportEntryDef} position | ||
| */ | ||
| trigger(position) { | ||
|
|
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.
Nit: no newline.
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.
deleted
| return; | ||
| } | ||
|
|
||
| dev().assert(position.positionRect); |
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.
Nit: add a message.
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.
added
|
|
||
| dev().assert(position.positionRect); | ||
| const positionRect = | ||
| /** @type {!../../layout-rect.LayoutRectDef} */ (position.positionRect); |
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.
This would be cleaner if the position we passed to trigger weren't nullable, and the one we pass to handler was. Or, we just pass two objects to the handler.
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.
position is always NOT nullable. position.postionRect is be nullable.
The problem is I want to pass position.positionRect = null to handler for the first time. After that I don't want to invoke handler with another positionRect null.
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.
position.postionRect is be nullable.
That's what I meant.
The problem is I want to pass position.positionRect = null to handler for the first time.
Don't you mean when there's no intersection? It can still happen that way by using a second type, or passing two arguments to the handler function.
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.
emmm. I sorry I kind of get lost here.
So what you want is have position.positionRect NOT nullable when passed to #trigger(), but have it nullable when passed to #handler()? This is what I have now.
Yes I can definitely change the arguments to the handler function. But now handler function expects single object, and I think our code is already depending on that return type. Dima proposed some more general object type that we might migrate to. But that's following work then.
| this.entries_.splice(i, 1); | ||
| if (this.entries_.length == 0) { | ||
| this.stopCallback_(); | ||
| this.callbackStarted_ = 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.
Nit: this can be moved into start and stop callbacks.
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.
good idea! done
|
|
||
| if (!this.callbackStarted_) { | ||
| this.startCallback_(); | ||
| this.callbackStarted_ = true; |
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.
Nit: this can be moved into start and stop callbacks.
| */ | ||
| updateAllEntries(opt_force) { | ||
| for (let i = 0; i < this.entries_.length; i++) { | ||
| const entry = this.entries_[i]; |
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.
Still belongs as a method on Entry.
| const positionRect = | ||
| /** @type {!../../layout-rect.LayoutRectDef} */ (position.positionRect); | ||
| // Add the relative position of the element to its viewport | ||
| position.relativePos = layoutRectsRelativePos(positionRect, |
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 this being calculated here?
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.
Hmmm that's how we define (at least currently) the return value of PositionObserverCallback
{
viewportRect,
positionRect,
relativePos,
}
Because we will be setting positionRect to null when element gets out of viewport, we need to calculate its relativePosition to viewport (above/below) here, handler won't have enough data to calculate it.
| for (let i = 0; i < this.entries_.length; i++) { | ||
| const entry = this.entries_[i]; | ||
|
|
||
| if (opt_force) { |
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.
These two if statements belong in Entry#update as well.
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.
Ah! 👍
|
|
||
| dev().assert(position.positionRect); | ||
| const positionRect = | ||
| /** @type {!../../layout-rect.LayoutRectDef} */ (position.positionRect); |
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.
position.postionRect is be nullable.
That's what I meant.
The problem is I want to pass position.positionRect = null to handler for the first time.
Don't you mean when there's no intersection? It can still happen that way by using a second type, or passing two arguments to the handler function.
| LOW_FIDELITY_FRAME_COUNT, | ||
| } from './position-observer-fidelity'; | ||
| PositionObserverEntry, | ||
| /* eslint no-unused-vars: 0 */ PositionObserverFidelity, |
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.
Nit: Appending Def to the name will skip the lint error.
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.
but the value is an enum not a Def 😕 Should i do that.
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.
Oh, this is just being used as a typedef. Just use the ./position-observer-entry.PositionObserverFidelity types instead.
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.
That's the first thing I try.
Get
Bad type annotation. Unknown type module$src$service$position_observer$position_observer_entry.PositionObserverFidelity
Strange that ./position-observer-entry.PositionInViewportEntryDef works but ./position-observer-entry.PositionObserverFidelity doesn't.
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.
That's likely during the extension compiling. What's the full output?
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.
src/service/position-observer/position-observer-impl.js:73: ERROR - Bad type annotation. Unknown type module$src$service$position_observer$position_observer_entry.PositionObserverFidelity
* @param {!./position-observer-entry.PositionObserverFidelity} fidelity
get this running gulp check-types
| this.turn--; | ||
| return; | ||
| } | ||
| this.turn = (this.fidelity == PositionObserverFidelity.LOW) ? |
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.
This is slightly different than the old code. Now, when a force update comes in, all low fidelity Entries will be synchronized to the same frame turn. Using two nested if statements cleans it up:
if (!opt_force) {
if (this.turn > 0) {
this.turn--;
return;
}
// An if statement just seems nicer than the assignment conditional
if (this.fidelity == LOW) {
this.turn = 4;
}
}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.
Good point. Talked offline, will move back to old behavior. But we may need future discussion on whether we should randomize entry.turn or sync all low fidelity elements' measurement
| */ | ||
| export let PositionInViewportEntryDef; | ||
|
|
||
| export class PositionObserverEntry { |
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.
-> xxxWorker
| * @param {!Element} element | ||
| * @param {PositionObserverFidelity} fidelity | ||
| */ | ||
| changeFidelity(element, fidelity) { |
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.
can we remove?
bb18853 to
5a4370e
Compare
|
fix #8755 |


Instead of extending from an abstract position observer class, the difference between inabox or ampdoc are handled in
Viewportclass. Thus the PositionObserver should be identical in both cases.