Skip to content
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

✨ Render close button for lightbox ads in inabox #16886

Merged
merged 24 commits into from
Jul 24, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ads/inabox/frame-overlay-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class FrameOverlayManager {
this.isExpanded_ = true;
this.viewportChangedSinceExpand_ = false;
this.collapsedRect_ = collapsedRect;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

callback(expandedRect);
});
}
Expand Down
69 changes: 60 additions & 9 deletions extensions/amp-lightbox/0.1/amp-lightbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ import {Gestures} from '../../../src/gesture';
import {KeyCodes} from '../../../src/utils/key-codes';
import {Services} from '../../../src/services';
import {SwipeXYRecognizer} from '../../../src/gesture-recognizers';
import {computedStyle, setImportantStyles} from '../../../src/style';
import {createCustomEvent} from '../../../src/event-helper';
import {computedStyle, px, setImportantStyles} from '../../../src/style';
import {createCustomEvent, listenOnce} from '../../../src/event-helper';
import {debounce} from '../../../src/utils/rate-limit';
import {dev, user} from '../../../src/log';
import {dict, hasOwn} from '../../../src/utils/object';
import {getMode} from '../../../src/mode';
import {installStylesForDoc} from '../../../src/style-installer';
import {isInFie} from '../../../src/friendly-iframe-embed';
import {cssText as lightboxAdCss} from '../../../build/lightbox-ad.css.js';
import {removeElement, tryFocus} from '../../../src/dom';
import {renderCloseButtonHeader} from '../../../src/full-overlay-frame-helper';
import {toArray} from '../../../src/types';
import {tryFocus} from '../../../src/dom';

/** @const {string} */
const TAG = 'amp-lightbox';
Expand Down Expand Up @@ -277,6 +280,7 @@ class AmpLightbox extends AMP.BaseElement {
});

this.handleAutofocus_();
this.maybeRenderCloseButtonHeader_();

// TODO (jridgewell): expose an API accomodating this per PR #14676
this.mutateElement(() => {
Expand Down Expand Up @@ -305,6 +309,40 @@ class AmpLightbox extends AMP.BaseElement {
this.active_ = true;
}

/** @private */
maybeRenderCloseButtonHeader_() {
if (!this.isInabox_()) {
return;
}

const header = renderCloseButtonHeader(this.element);

listenOnce(header, 'click', () => {
removeElement(header);
this.close();
});

installStylesForDoc(this.getAmpDoc(), lightboxAdCss, () => {
this.element.insertBefore(header, this.container_);

// Done in callback in order to apply transition.
header.classList.add('amp-ad-close-header');

let headerHeight;

this.measureMutateElement(() => {
headerHeight = header./*OK*/getBoundingClientRect().height;
}, () => {
setImportantStyles(dev().assertElement(this.container_), {
'margin-top': px(headerHeight),
'min-height': `calc(100vh - ${px(headerHeight)})`,
});
});
},
/* opt_isRuntimeCss */ false,
/* opt_ext */ 'amp-lightbox');
}

/**
* @private
* @return {!AnimationPresetDef}
Expand Down Expand Up @@ -343,22 +381,27 @@ class AmpLightbox extends AMP.BaseElement {
*/
finalizeClose_() {
const {element} = this;

st.setStyles(element, this.getAnimationPresetDef_().closedStyle);

const event = ++this.eventCounter_;

const collapseAndReschedule = () => {
// Don't collapse on transitionend if there was a previous event.
// Don't collapse on transitionend if there was a subsequent event.
if (event != this.eventCounter_) {
return;
}
this./*OK*/collapse();
this.boundReschedule_();
};

element.addEventListener('transitionend', collapseAndReschedule);
element.addEventListener('animationend', collapseAndReschedule);
// Disable transition for inabox since the frame gets immediately collapsed.
if (this.isInabox_()) {
st.resetStyles(element, ['transition']);
collapseAndReschedule();
} else {
element.addEventListener('transitionend', collapseAndReschedule);
element.addEventListener('animationend', collapseAndReschedule);
}

st.setStyles(element, this.getAnimationPresetDef_().closedStyle);

if (this.historyId_ != -1) {
this.getHistory_().pop(this.historyId_);
Expand All @@ -371,6 +414,14 @@ class AmpLightbox extends AMP.BaseElement {
this.triggerEvent_(LightboxEvents.CLOSE);
}

/**
* @return {boolean}
* @private
*/
isInabox_() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think we make the close button a feature generic to amp-lightbox in both FIE or regular AMP. The feature is enabled by having element attribute "close-button".

We then let the A4A validator to ensure its existence in ads.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great idea, and decreases complexity! Done.

return getMode(this.win).runtime == 'inabox';
}

/**
* Handles scroll on the amp-lightbox.
* The scroll throttling and visibility calculation is similar to
Expand Down
85 changes: 57 additions & 28 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,29 @@ function css() {
return compileCss();
}

const cssEntryPoints = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
path: 'amp.css',
outJs: 'css.js',
outCss: 'v0.css',
},
{
path: 'lightbox-ad.css',
outJs: 'lightbox-ad.css.js',
outCss: 'lightbox-ad.css',
},
{
path: 'video-docking.css',
outJs: 'video-docking.css.js',
outCss: 'video-docking.css',
},
{
path: 'video-autoplay.css',
outJs: 'video-autoplay.css.js',
outCss: 'video-autoplay.css',
},
];

/**
* Compile all the css and drop in the build folder
* @param {boolean} watch
Expand Down Expand Up @@ -432,34 +455,37 @@ function compileCss(watch, opt_compileAll) {
}));
}

/**
* @param {string} path
* @param {string} outJs
* @param {string} outCss
*/
function writeCssEntryPoint(path, outJs, outCss) {
const startTime = Date.now();

return jsifyCssAsync(`css/${path}`)
.then(css => writeCss(css, path, outJs, outCss))
.then(() => {
endBuildStep('Recompiled CSS in', path, startTime);
});
}

// Used by `gulp test --local-changes` to map CSS files to JS files.
fs.writeFileSync('EXTENSIONS_CSS_MAP', JSON.stringify(extensions));

const startTime = Date.now();
return jsifyCssAsync('css/amp.css')
.then(css => writeCss(css, 'amp.css', 'css.js', 'v0.css'))
.then(() => {
endBuildStep('Recompiled CSS in', 'amp.css', startTime);
})
.then(() => jsifyCssAsync('css/video-docking.css'))
.then(css => writeCss(css,
'video-docking.css', 'video-docking.css.js', 'video-docking.css'))
.then(() => {
endBuildStep('Recompiled CSS in', 'video-docking.css', startTime);
})
.then(() => jsifyCssAsync('css/video-autoplay.css'))
.then(css => writeCss(css,
'video-autoplay.css', 'video-autoplay.css.js', 'video-autoplay.css'))
.then(() => {
endBuildStep('Recompiled CSS in', 'video-autoplay.css', startTime);
})
.then(() => {
return buildExtensions({
bundleOnlyIfListedInFiles: false,
compileOnlyCss: true,
compileAll: opt_compileAll,
});
});

let promise = Promise.resolve();

cssEntryPoints.forEach(entryPoint => {
const {path, outJs, outCss} = entryPoint;
promise = promise.then(() => writeCssEntryPoint(path, outJs, outCss));
});

return promise.then(() => buildExtensions({
bundleOnlyIfListedInFiles: false,
compileOnlyCss: true,
compileAll: opt_compileAll,
}));
}

/**
Expand All @@ -468,12 +494,15 @@ function compileCss(watch, opt_compileAll) {
*/
function copyCss() {
const startTime = Date.now();
fs.copySync('build/css/v0.css', 'dist/v0.css');
fs.copySync('build/css/video-docking.css', 'dist/video-docking.css');

cssEntryPoints.forEach(({outCss}) => {
fs.copySync(`build/css/${outCss}`, `dist/${outCss}`);
});

return toPromise(gulp.src('build/css/amp-*.css')
.pipe(gulp.dest('dist/v0')))
.then(() => {
endBuildStep('Copied', 'build/css/v0.css to dist/v0.css', startTime);
endBuildStep('Copied', 'build/css/*.css to dist/*.css', startTime);
});
}

Expand Down
16 changes: 7 additions & 9 deletions src/friendly-iframe-embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {closestBySelector, escapeHtml, removeElement} from './dom';
import {createCustomEvent, listenOnce, loadPromise} from './event-helper';
import {dev, rethrowAsync, user} from './log';
import {disposeServicesForEmbed, getTopWindow} from './service';
import {htmlFor} from './static-template';
import {isDocumentReady} from './document-ready';
import {layoutRectLtwh} from './layout-rect';
import {
Expand All @@ -33,6 +32,7 @@ import {
setStyle,
setStyles,
} from './style';
import {renderCloseButtonHeader} from './full-overlay-frame-helper';
import {toWin} from './types';


Expand Down Expand Up @@ -523,7 +523,8 @@ export class FriendlyIframeEmbed {
'Only <amp-ad> is allowed to enter lightbox mode.');

const header =
renderCloseButtonHeader(this.win, ampAdParent, requestingElement);
renderClickableCloseButtonHeader(
this.win, ampAdParent, requestingElement);

ampAdParent.appendChild(header);

Expand Down Expand Up @@ -635,13 +636,10 @@ export class FriendlyIframeEmbed {
* @param {!Element} ampLightbox
* @visibleForTesting
*/
export function renderCloseButtonHeader(win, ampAdParent, ampLightbox) {
const el = htmlFor(ampAdParent)`
<i-amphtml-ad-close-header role=button tabindex=0 aria-label="Close Ad">
<div>Ad</div>
<i-amphtml-ad-close-button class="amp-ad-close-button">
</i-amphtml-ad-close-button>
</i-amphtml-ad-close-header>`;
export function renderClickableCloseButtonHeader(
win, ampAdParent, ampLightbox) {

const el = renderCloseButtonHeader(/* ctx */ ampAdParent);

listenOnce(el, 'click', () => {
triggerLightboxClose(win, ampLightbox, /* caller */ ampAdParent);
Expand Down
24 changes: 20 additions & 4 deletions src/full-overlay-frame-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {htmlFor} from './static-template';
import {px, resetStyles, setStyles, translate} from './style';


Expand All @@ -27,11 +28,12 @@ import {px, resetStyles, setStyles, translate} from './style';
*/
export function centerFrameUnderVsyncMutate(
iframe, iframeRect, viewportSize, transitionTimeMs) {

const translateX = px(
(viewportSize.width / 2 - iframeRect.width / 2) - iframeRect.left);
(viewportSize.width / 2) - (iframeRect.width / 2) - iframeRect.left);

const translateY = px(
(viewportSize.height / 2 - iframeRect.height / 2) - iframeRect.top);
(viewportSize.height / 2) - (iframeRect.height / 2) - iframeRect.top);

setStyles(iframe, {
'position': 'fixed',
Expand All @@ -41,8 +43,7 @@ export function centerFrameUnderVsyncMutate(
'bottom': px(viewportSize.height - (iframeRect.top + iframeRect.height)),
'height': px(iframeRect.height),
'width': px(iframeRect.width),
'transition':
`transform ${(transitionTimeMs / 1000)}s ease`,
'transition': `transform ${transitionTimeMs}ms ease`,
'transform': translate(translateX, translateY),
});
}
Expand Down Expand Up @@ -92,3 +93,18 @@ export function collapseFrameUnderVsyncMutate(iframe) {
'border',
]);
}


/**
* @param {!Element} ctx
* @return {!Element}
* @visibleForTesting
*/
export function renderCloseButtonHeader(ctx) {
return htmlFor(ctx)`
<i-amphtml-ad-close-header role=button tabindex=0 aria-label="Close Ad">
<div>Ad</div>
<i-amphtml-ad-close-button class="amp-ad-close-button">
</i-amphtml-ad-close-button>
</i-amphtml-ad-close-header>`;
}
10 changes: 6 additions & 4 deletions test/functional/test-friendly-iframe-embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
getFriendlyIframeEmbedOptional,
installFriendlyIframeEmbed,
mergeHtmlForTesting,
renderCloseButtonHeader,
renderClickableCloseButtonHeader,
setFriendlyIframeEmbedVisible,
setSrcdocSupportedForTesting,
whenContentIniLoad,
Expand Down Expand Up @@ -824,15 +824,16 @@ describe('friendly-iframe-embed', () => {
});
});

describe('#renderCloseButtonHeader', () => {
describe('#renderClickableCloseButtonHeader', () => {

const win = document.defaultView;

it('renders', () => {
const ampAdParentMock = document.createElement('div');
const ampLightboxMock = document.createElement('div');

const el = renderCloseButtonHeader(win, ampAdParentMock, ampLightboxMock);
const el = renderClickableCloseButtonHeader(
win, ampAdParentMock, ampLightboxMock);

expect(el.tagName.toLowerCase()).to.equal('i-amphtml-ad-close-header');
expect(el.getAttribute('role')).to.equal('button');
Expand All @@ -853,7 +854,8 @@ describe('friendly-iframe-embed', () => {
const ampAdParentMock = document.createElement('div');
const ampLightboxMock = document.createElement('div');

const el = renderCloseButtonHeader(win, ampAdParentMock, ampLightboxMock);
const el = renderClickableCloseButtonHeader(
win, ampAdParentMock, ampLightboxMock);

const execute = sandbox.spy();
const actionServiceMock = {execute};
Expand Down