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
Basic support for lightboxes in inabox #9646
Changes from 4 commits
3c7779b
c6372da
33f1bc5
ef83f47
200bf55
9d07afd
71bce3b
65c1629
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 |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/** | ||
* Copyright 2017 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
import {fakeVsync, timer} from './util'; | ||
import { | ||
centerFrameUnderVsyncMutate, | ||
expandFrameUnderVsyncMutate, | ||
collapseFrameUnderVsyncMutate, | ||
} from '../../src/full-overlay-frame-helper'; | ||
|
||
|
||
const CENTER_TRANSITION_TIME_MS = 500; | ||
const CENTER_TRANSITION_END_WAIT_TIME_MS = 200; | ||
|
||
|
||
/** | ||
* Places the child frame in full overlay mode. | ||
* @param {!Window} win Host window. | ||
* @param {!HTMLIFrameElement} iframe | ||
* @param {!Function} onFinish | ||
* @private | ||
*/ | ||
const expandFrameImpl = function(win, iframe, onFinish) { | ||
fakeVsync(win, { | ||
measure(state) { | ||
state.viewportSize = { | ||
width: win./*OK*/innerWidth, | ||
height: win./*OK*/innerHeight, | ||
}; | ||
state.rect = iframe./*OK*/getBoundingClientRect(); | ||
}, | ||
mutate(state) { | ||
centerFrameUnderVsyncMutate(iframe, state.rect, state.viewportSize, | ||
CENTER_TRANSITION_TIME_MS); | ||
|
||
timer(() => { | ||
fakeVsync(win, { | ||
mutate() { | ||
expandFrameUnderVsyncMutate(iframe, state.viewportSize); | ||
onFinish(); | ||
}, | ||
}); | ||
}, CENTER_TRANSITION_TIME_MS + CENTER_TRANSITION_END_WAIT_TIME_MS); | ||
}, | ||
}, {}); | ||
}; | ||
|
||
|
||
/** | ||
* Resets the frame from full overlay mode. | ||
* @param {!Window} win Host window. | ||
* @param {!HTMLIFrameElement} iframe | ||
* @param {!Function} onFinish | ||
* @private | ||
*/ | ||
const collapseFrameImpl = function(win, iframe, onFinish) { | ||
fakeVsync(win, { | ||
mutate() { | ||
collapseFrameUnderVsyncMutate(iframe); | ||
onFinish(); | ||
}, | ||
}); | ||
}; | ||
|
||
|
||
/** | ||
* Places the child frame in full overlay mode. | ||
* @param {!Window} win Host window. | ||
* @param {!HTMLIFrameElement} iframe | ||
* @param {!Function} onFinish | ||
*/ | ||
export let expandFrame = expandFrameImpl; | ||
|
||
|
||
/** | ||
* @param {!Function} implFn | ||
* @visibleForTesting | ||
*/ | ||
export function stubExpandFrameForTesting(implFn) { | ||
expandFrame = implFn; | ||
} | ||
|
||
|
||
/** | ||
* @visibleForTesting | ||
*/ | ||
export function resetExpandFrameForTesting() { | ||
expandFrame = expandFrameImpl; | ||
} | ||
|
||
|
||
/** | ||
* Places the child frame in full overlay mode. | ||
* @param {!Window} win Host window. | ||
* @param {!HTMLIFrameElement} iframe | ||
* @param {!Function} onFinish | ||
*/ | ||
export let collapseFrame = collapseFrameImpl; | ||
|
||
|
||
/** | ||
* @param {!Function} implFn | ||
* @visibleForTesting | ||
*/ | ||
export function stubCollapseFrameForTesting(implFn) { | ||
collapseFrame = implFn; | ||
} | ||
|
||
|
||
/** | ||
* @visibleForTesting | ||
*/ | ||
export function resetCollapseFrameForTesting() { | ||
collapseFrame = collapseFrameImpl; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,10 +21,33 @@ import { | |
MessageType, | ||
} from '../../src/3p-frame-messaging'; | ||
import {dev} from '../../src/log'; | ||
import {expandFrame, collapseFrame} from './frame-overlay-helper'; | ||
|
||
/** @const */ | ||
const TAG = 'InaboxMessagingHost'; | ||
|
||
|
||
class NamedObservable { | ||
constructor() { | ||
this.map_ = {}; | ||
} | ||
|
||
listen(key, callback) { | ||
if (key in this.map_) { | ||
dev().fine(TAG, `Overriding message callback [${key}]`); | ||
} | ||
this.map_[key] = callback; | ||
} | ||
|
||
fire(key, thisArg, args) { | ||
if (key in this.map_) { | ||
return this.map_[key].apply(thisArg, args); | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
|
||
export class InaboxMessagingHost { | ||
|
||
constructor(win, iframes) { | ||
|
@@ -33,6 +56,16 @@ export class InaboxMessagingHost { | |
this.iframeMap_ = Object.create(null); | ||
this.registeredIframeSentinels_ = Object.create(null); | ||
this.positionObserver_ = new PositionObserver(win); | ||
this.msgObservable_ = new NamedObservable(); | ||
|
||
this.msgObservable_.listen( | ||
MessageType.SEND_POSITIONS, this.handleSendPositions_); | ||
|
||
this.msgObservable_.listen( | ||
MessageType.FULL_OVERLAY_FRAME, this.handleEnterFullOverlay_); | ||
|
||
this.msgObservable_.listen( | ||
MessageType.RESET_FULL_OVERLAY_FRAME, this.handleResetFullOverlay_); | ||
} | ||
|
||
/** | ||
|
@@ -60,23 +93,78 @@ export class InaboxMessagingHost { | |
return false; | ||
} | ||
|
||
if (request.type == MessageType.SEND_POSITIONS) { | ||
// To prevent double tracking for the same requester. | ||
if (this.registeredIframeSentinels_[request.sentinel]) { | ||
return false; | ||
} | ||
this.registeredIframeSentinels_[request.sentinel] = true; | ||
this.positionObserver_.observe(iframe, data => { | ||
dev().fine(TAG, `Sent position data to [${request.sentinel}]`, data); | ||
message.source./*OK*/postMessage( | ||
serializeMessage(MessageType.POSITION, request.sentinel, data), | ||
message.origin); | ||
}); | ||
return true; | ||
} else { | ||
if (!this.msgObservable_.fire(request.type, this, | ||
[iframe, request, message.source, message.origin])) { | ||
dev().warn(TAG, 'Unprocessed AMP message:', message); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* @param {!HTMLIFrameElement} iframe | ||
* @param {!Object} request | ||
* @param {!Window} source | ||
* @param {string} origin | ||
* @return {boolean} | ||
*/ | ||
handleSendPositions_(iframe, request, source, origin) { | ||
// To prevent double tracking for the same requester. | ||
if (this.registeredIframeSentinels_[request.sentinel]) { | ||
return false; | ||
} | ||
this.registeredIframeSentinels_[request.sentinel] = true; | ||
this.positionObserver_.observe(iframe, data => { | ||
dev().fine(TAG, `Sent position data to [${request.sentinel}]`, data); | ||
source./*OK*/postMessage( | ||
serializeMessage(MessageType.POSITION, request.sentinel, data), | ||
origin); | ||
}); | ||
return true; | ||
} | ||
|
||
/** | ||
* @param {!HTMLIFrameElement} iframe | ||
* @param {!Object} request | ||
* @param {!Window} source | ||
* @param {string} origin | ||
* @return {boolean} | ||
*/ | ||
// TODO(alanorozco): | ||
// 1. Reject request if frame is out of focus | ||
// 2. Disable zoom and scroll on parent doc | ||
handleEnterFullOverlay_(iframe, request, source, origin) { | ||
expandFrame(this.win_, iframe, () => { | ||
source./*OK*/postMessage( | ||
serializeMessage( | ||
MessageType.FULL_OVERLAY_FRAME_RESPONSE, | ||
request.sentinel, | ||
{content: {accept: true}}), | ||
origin); | ||
}); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* @param {!HTMLIFrameElement} iframe | ||
* @param {!Object} request | ||
* @param {!Window} source | ||
* @param {string} origin | ||
* @return {boolean} | ||
*/ | ||
handleResetFullOverlay_(iframe, request, source, origin) { | ||
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. handleExitFullOverlay 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. Changed to |
||
collapseFrame(this.win_, iframe, () => { | ||
source./*OK*/postMessage( | ||
serializeMessage( | ||
MessageType.RESET_FULL_OVERLAY_FRAME_RESPONSE, | ||
request.sentinel, | ||
{content: {accept: true}}), | ||
origin); | ||
}); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* Copyright 2017 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
|
||
/** | ||
* Executes a "fake vsync" read/write. | ||
* This function exists mainly since the vsync service is not available for the | ||
* inabox host script. | ||
* It also helps with maintainability. Since the passed tasks have to define | ||
* measure and mutate callbacks, it makes it harder for the calling code to be | ||
* changed in a way that screws up the read-write order. | ||
* Please note that this is NOT real vsync. Concurrent reads and writes ARE NOT | ||
* BATCHED. This means that using this can still cause layout thrashing if it's | ||
* being called more than once within the same frame. Use with caution. | ||
* @param {!Window} win | ||
* @param {{measure: (Function|undefined), mutate: !Function}} task | ||
* @param {!Object=} opt_state | ||
* @visibleForTesting | ||
*/ | ||
// TODO(alanorozco): Figure out a longer-term solution | ||
export function fakeVsync(win, task, opt_state) { | ||
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. call it something else. 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. Changed to |
||
win.requestAnimationFrame(() => { | ||
if (task.measure) { | ||
task.measure(opt_state); | ||
} | ||
task.mutate(opt_state); | ||
}); | ||
} | ||
|
||
|
||
/** | ||
* Executes a function after a certain time. | ||
* The timer service is not available for the inabox host script, hence this | ||
* function. | ||
* Not using setTimeout directly allows us to execute the callback directly on | ||
* tests. | ||
* @param {!Function} callback | ||
* @param {number} timeMs | ||
* @visibleForTesting | ||
*/ | ||
export function timer(callback, timeMs) { | ||
setTimeout(callback, timeMs); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,12 +36,17 @@ export const MessageType = { | |
EMBED_SIZE_DENIED: 'embed-size-denied', | ||
NO_CONTENT: 'no-content', | ||
|
||
// For the frame to be placed in full overlay mode for lightboxes | ||
FULL_OVERLAY_FRAME: 'full-overlay-frame', | ||
FULL_OVERLAY_FRAME_RESPONSE: 'full-overlay-frame-response', | ||
RESET_FULL_OVERLAY_FRAME: 'reset-full-overlay-frame', | ||
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. Naming suggestion: RESET -> CANCEL, FULL-> FULLSCREEN, and remove FRAME (given the context) FULLSCREEN_OVERLAY: 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 like "fullscreen" specifically because it's not really fullscreen. It fills the entire viewport, but the browser chrome may still be visible. Changed reset to cancel. |
||
RESET_FULL_OVERLAY_FRAME_RESPONSE: 'reset-full-overlay-frame-response', | ||
|
||
// For amp-inabox | ||
SEND_POSITIONS: 'send-positions', | ||
POSITION: 'position', | ||
}; | ||
|
||
|
||
/** | ||
* Listens for the specified event on the element. | ||
* @param {!EventTarget} element | ||
|
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 consistency with resize API ->
success: true
.also, why nest the data in
content
?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 nested the content since
serializeMessage
adds top-level members and that's a little weird :/Renaming to top-level
success
is still alright I guess. It's a simple API. Changed.