-
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
Make inabox-viewport measure the top level window directly if same domain #20459
Changes from all commits
d6612a9
f03abfb
ea78dc5
4fbd385
f4b0d21
4655d6a
5947bfb
37d2b15
5b0627c
2dc3ce5
a15e80f
48771c6
18fcc29
f226409
a862336
ad8ca83
1d59138
bdbab11
d57a414
5710e6f
46ef8d3
e36c257
1553d9c
bf4dc5a
ee941d6
7f15bbb
7780a9d
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 |
---|---|---|
|
@@ -16,9 +16,11 @@ | |
|
||
import {MessageType} from '../../src/3p-frame-messaging'; | ||
import {Observable} from '../observable'; | ||
import {PositionObserver} from '../../ads/inabox/position-observer'; | ||
import {Services} from '../services'; | ||
import {Viewport} from '../service/viewport/viewport-impl'; | ||
import {ViewportBindingDef} from '../service/viewport/viewport-binding-def'; | ||
import {canInspectWindow} from '../iframe-helper'; | ||
import {dev, devAssert} from '../log'; | ||
import {iframeMessagingClientFor} from './inabox-iframe-messaging-client'; | ||
import {isExperimentOn} from '../experiments'; | ||
|
@@ -149,35 +151,74 @@ export class ViewportBindingInabox { | |
/** @private @const {boolean} */ | ||
this.useLayers_ = isExperimentOn(this.win, 'layers'); | ||
|
||
/** @private {?../../ads/inabox/position-observer.PositionObserver} */ | ||
this.topWindowPositionObserver_ = null; | ||
|
||
/** @private {?UnlistenDef} */ | ||
this.unobserveFunction_ = null; | ||
|
||
dev().fine(TAG, 'initialized inabox viewport'); | ||
} | ||
|
||
/** @override */ | ||
connect() { | ||
this.listenForPosition_(); | ||
if (isExperimentOn(this.win, 'inabox-viewport-friendly') && | ||
canInspectWindow(this.win.top)) { | ||
this.listenForPositionSameDomain(); | ||
} else { | ||
this.listenForPosition_(); | ||
} | ||
} | ||
|
||
/** @private */ | ||
listenForPosition_() { | ||
|
||
this.iframeClient_.makeRequest( | ||
MessageType.SEND_POSITIONS, MessageType.POSITION, | ||
data => { | ||
dev().fine(TAG, 'Position changed: ', data); | ||
const oldViewportRect = this.viewportRect_; | ||
this.viewportRect_ = data['viewportRect']; | ||
|
||
this.updateBoxRect_(data['targetRect']); | ||
this.updateLayoutRects_(data['viewportRect'], data['targetRect']); | ||
}); | ||
} | ||
|
||
if (isResized(this.viewportRect_, oldViewportRect)) { | ||
this.resizeObservable_.fire(); | ||
} | ||
if (isMoved(this.viewportRect_, oldViewportRect)) { | ||
this.fireScrollThrottle_(); | ||
} | ||
/** @visibleForTesting */ | ||
listenForPositionSameDomain() { | ||
// Set up listener but only after the resources service is properly | ||
// registered (since it's registered after the inabox services so it won't | ||
// be available immediately). | ||
// TODO(lannka): Investigate why this is the case. | ||
if (this.topWindowPositionObserver_) { | ||
return Promise.resolve(); | ||
} | ||
return Services.resourcesPromiseForDoc(this.win.document.documentElement) | ||
.then(() => { | ||
this.topWindowPositionObserver_ = new PositionObserver(this.win.top); | ||
this.unobserveFunction_ = this.topWindowPositionObserver_.observe( | ||
/** @type {!HTMLIFrameElement} */(this.win.frameElement), | ||
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. shall we sanity check that win.top is win.parent? otherwise I don't know if the positionObserver still works as intended. /cc @zhouyx 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 thought that the inabox iframe does not have to necessarily be the direct child of the top level window? #20599 was created specifically to handle situations where this is not the case. |
||
data => { | ||
this.updateLayoutRects_( | ||
data['viewportRect'], | ||
data['targetRect']); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {!../layout-rect.LayoutRectDef} viewportRect | ||
* @param {!../layout-rect.LayoutRectDef} targetRect | ||
*/ | ||
updateLayoutRects_(viewportRect, targetRect) { | ||
const oldViewportRect = this.viewportRect_; | ||
this.viewportRect_ = viewportRect; | ||
this.updateBoxRect_(targetRect); | ||
if (isResized(this.viewportRect_, oldViewportRect)) { | ||
this.resizeObservable_.fire(); | ||
} | ||
if (isMoved(this.viewportRect_, oldViewportRect)) { | ||
this.fireScrollThrottle_(); | ||
} | ||
} | ||
|
||
/** @override */ | ||
getLayoutRect(el) { | ||
const b = el./*OK*/getBoundingClientRect(); | ||
|
@@ -283,6 +324,13 @@ export class ViewportBindingInabox { | |
|
||
/** @override */ | ||
getRootClientRectAsync() { | ||
if (isExperimentOn(this.win, 'inabox-viewport-friendly') && | ||
canInspectWindow(this.win.top)) { | ||
// Set up the listener if we haven't already. | ||
return this.listenForPositionSameDomain().then(() => | ||
this.topWindowPositionObserver_.getTargetRect( | ||
/** @type {!HTMLIFrameElement} */(this.win.frameElement))); | ||
} | ||
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 gated by the experiment too, right? 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. |
||
if (!this.requestPositionPromise_) { | ||
this.requestPositionPromise_ = new Promise(resolve => { | ||
this.iframeClient_.requestOnce( | ||
|
@@ -378,7 +426,13 @@ export class ViewportBindingInabox { | |
return dev().assertElement(this.win.document.body); | ||
} | ||
|
||
/** @override */ disconnect() {/* no-op */} | ||
/** @override */ | ||
disconnect() { | ||
if (this.unobserveFunction_) { | ||
this.unobserveFunction_(); | ||
} | ||
} | ||
|
||
/** @override */ updatePaddingTop() {/* no-op */} | ||
/** @override */ hideViewerHeader() {/* no-op */} | ||
/** @override */ showViewerHeader() {/* no-op */} | ||
|
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 quite understand why this is needed? Is there a race condition you try to avoid? Can you explain?
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 inabox-viewport requires the Resources service to remeasure child elements. It's registered after the viewport service is set up, so when we setup the native listener it will try to call getChildResources() immediately and will throw since it can't find the service. I did manual testing within a browser; without this guard the console will be swarmed by thrown errors.
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 might missed something, but my intuition is that a native listener should not need anything particular from resource service, because the parent window is not even AMP. is that a wrong dependency?
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 Resources service comes from the AMP Inabox window itself.
Though I admit that I haven't looked very deep into what it actually does.
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.
Could you dig deep into it? I'm afraid something is wrong 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.
So I think the Resource service is responsible for positioning and sizing the child elements within an AMP document. As part of its regular flow the inabox viewport calls remeasureAllElements_() when the iframe size/scroll changes, which triggers this service.
In the cross domain case the postMessage from the host window usually comes back after the Resources service is set up, but in the same domain case added by this PR the listener is set up and updates positioning immediately, which will happen before the Resources service is installed due to their order of install in amp-inabox.js. If I'm correct then this race condition has always existed within the cross domain case.