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
Amp Ad Fast Fetch allow amp-image prefetch #11391
Changes from all commits
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 |
---|---|---|
|
@@ -40,7 +40,7 @@ import {isArray, isObject, isEnumValue} from '../../../src/types'; | |
import {utf8Decode} from '../../../src/utils/bytes'; | ||
import {getBinaryType, isExperimentOn} from '../../../src/experiments'; | ||
import {setStyle} from '../../../src/style'; | ||
import {assertHttpsUrl} from '../../../src/url'; | ||
import {assertHttpsUrl, isSecureUrl} from '../../../src/url'; | ||
import {parseJson} from '../../../src/json'; | ||
import {handleClick} from '../../../ads/alp/handler'; | ||
import { | ||
|
@@ -106,7 +106,8 @@ export let SizeInfoDef; | |
/** @typedef {{ | ||
minifiedCreative: string, | ||
customElementExtensions: !Array<string>, | ||
customStylesheets: !Array<{href: string}> | ||
customStylesheets: !Array<{href: string}>, | ||
images: (Array<string>|undefined), | ||
}} */ | ||
let CreativeMetaDataDef; | ||
|
||
|
@@ -733,6 +734,9 @@ export class AmpA4A extends AMP.BaseElement { | |
// Preload any fonts. | ||
(creativeMetaDataDef.customStylesheets || []).forEach(font => | ||
this.preconnect.preload(font.href)); | ||
// Preload any AMP images. | ||
(creativeMetaDataDef.images || []).forEach(image => | ||
isSecureUrl(image) && this.preconnect.preload(image)); | ||
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 should pass in an |
||
return creativeMetaDataDef; | ||
}) | ||
.catch(error => { | ||
|
@@ -1472,11 +1476,15 @@ export class AmpA4A extends AMP.BaseElement { | |
metaData.customStylesheets.forEach(stylesheet => { | ||
if (!isObject(stylesheet) || !stylesheet['href'] || | ||
typeof stylesheet['href'] !== 'string' || | ||
!/^https:\/\//i.test(stylesheet['href'])) { | ||
!isSecureUrl(stylesheet['href'])) { | ||
throw new Error(errorMsg); | ||
} | ||
}); | ||
} | ||
if (isArray(metaDataObj['images'])) { | ||
// Load maximum of 5 images. | ||
metaData.images = metaDataObj['images'].splice(0, 5); | ||
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 does not match the comment! 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. Actually, this will work since |
||
} | ||
// TODO(keithwrightbos): OK to assume ampRuntimeUtf16CharOffsets is before | ||
// metadata as its in the head? | ||
metaData.minifiedCreative = | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -152,7 +152,7 @@ describe('amp-a4a', () => { | |
|
||
function buildCreativeString(opt_additionalInfo) { | ||
const baseTestDoc = testFragments.minimalDocOneStyle; | ||
const offsets = opt_additionalInfo || {}; | ||
const offsets = Object.assign({}, opt_additionalInfo || {}); | ||
offsets.ampRuntimeUtf16CharOffsets = [ | ||
baseTestDoc.indexOf('<style amp4ads-boilerplate'), | ||
baseTestDoc.lastIndexOf('</script>') + '</script>'.length, | ||
|
@@ -941,6 +941,35 @@ describe('amp-a4a', () => { | |
}); | ||
}); | ||
}); | ||
// TODO (keithwrightbos) - move into above e2e once signed creative with | ||
// image within creative can be regenerated. | ||
it('should prefetch amp images', () => { | ||
return createIframePromise().then(fixture => { | ||
setupForAdTesting(fixture); | ||
fetchMock.getOnce( | ||
TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, | ||
{name: 'ad'}); | ||
const doc = fixture.doc; | ||
const a4aElement = createA4aElement(doc); | ||
const a4a = new MockA4AImpl(a4aElement); | ||
sandbox.stub(a4a, 'getAmpAdMetadata_', creative => { | ||
const metaData = AmpA4A.prototype.getAmpAdMetadata_(creative); | ||
metaData.images = ['https://prefetch.me.com?a=b', 'http://do.not.prefetch.me.com?c=d', | ||
'https://prefetch.metoo.com?e=f']; | ||
return metaData; | ||
}); | ||
a4a.buildCallback(); | ||
a4a.onLayoutMeasure(); | ||
return a4a.layoutCallback().then(() => { | ||
expect(doc.querySelector('link[rel=preload]' + | ||
'[href="https://prefetch.me.com?a=b"]')).to.be.ok; | ||
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. String continuation onto next line should be indented 4 spaces, correct? (I suspect this is a case of the indentation being wrong but the file is omitted by the linter) 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. It passes gulp lint and I am fairly positive it is enforced on test files. 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. Ok, nvm then |
||
expect(doc.querySelector('link[rel=preload]' + | ||
'[href="https://prefetch.metoo.com?e=f"]')).to.be.ok; | ||
expect(doc.querySelector('link[rel=preload]' + | ||
'[href="http://do.not.prefetch.me.com?c=d"]')).to.not.be.ok; | ||
}); | ||
}); | ||
}); | ||
it('must not be position:fixed', () => { | ||
return createIframePromise().then(fixture => { | ||
setupForAdTesting(fixture); | ||
|
@@ -1417,61 +1446,44 @@ describe('amp-a4a', () => { | |
|
||
describe('#getAmpAdMetadata_', () => { | ||
let a4a; | ||
let metaData; | ||
beforeEach(() => { | ||
metaData = { | ||
customElementExtensions: ['amp-vine', 'amp-vine', 'amp-vine'], | ||
customStylesheets: [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{href: 'https://fonts.com/css?helloworld'}, | ||
], | ||
images: ['https://some.image.com/a=b', 'https://other.image.com'], | ||
}; | ||
return createIframePromise().then(fixture => { | ||
setupForAdTesting(fixture); | ||
a4a = new MockA4AImpl(createA4aElement(fixture.doc)); | ||
return fixture; | ||
}); | ||
}); | ||
it('should parse metadata', () => { | ||
const actual = a4a.getAmpAdMetadata_(buildCreativeString({ | ||
customElementExtensions: ['amp-vine', 'amp-vine', 'amp-vine'], | ||
customStylesheets: [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{href: 'https://fonts.com/css?helloworld'}, | ||
], | ||
})); | ||
const expected = { | ||
const actual = a4a.getAmpAdMetadata_(buildCreativeString(metaData)); | ||
const expected = Object.assign(metaData, { | ||
minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, | ||
customElementExtensions: ['amp-vine', 'amp-vine', 'amp-vine'], | ||
customStylesheets: [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{href: 'https://fonts.com/css?helloworld'}, | ||
], | ||
}; | ||
}); | ||
expect(actual).to.deep.equal(expected); | ||
}); | ||
// TODO(levitzky) remove the following two tests after metadata bug is | ||
// fixed. | ||
it('should parse metadata with wrong opening tag', () => { | ||
const creative = buildCreativeString({ | ||
customElementExtensions: ['amp-vine', 'amp-vine', 'amp-vine'], | ||
customStylesheets: [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{href: 'https://fonts.com/css?helloworld'}, | ||
], | ||
}).replace('<script type="application/json" amp-ad-metadata>', | ||
const creative = buildCreativeString(metaData).replace( | ||
'<script type="application/json" amp-ad-metadata>', | ||
'<script type=application/json amp-ad-metadata>'); | ||
const actual = a4a.getAmpAdMetadata_(creative); | ||
const expected = { | ||
const expected = Object.assign({ | ||
minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, | ||
customElementExtensions: ['amp-vine', 'amp-vine', 'amp-vine'], | ||
customStylesheets: [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{href: 'https://fonts.com/css?helloworld'}, | ||
], | ||
}; | ||
}, metaData); | ||
expect(actual).to.deep.equal(expected); | ||
}); | ||
it('should return null if metadata opening tag is (truly) wrong', () => { | ||
const creative = buildCreativeString({ | ||
customElementExtensions: ['amp-vine', 'amp-vine', 'amp-vine'], | ||
customStylesheets: [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{href: 'https://fonts.com/css?helloworld'}, | ||
], | ||
}).replace('<script type="application/json" amp-ad-metadata>', | ||
const creative = buildCreativeString(metaData).replace( | ||
'<script type="application/json" amp-ad-metadata>', | ||
'<script type=application/json" amp-ad-metadata>'); | ||
expect(a4a.getAmpAdMetadata_(creative)).to.be.null; | ||
}); | ||
|
@@ -1485,28 +1497,44 @@ describe('amp-a4a', () => { | |
baseTestDoc.slice(splicePoint))).to.be.null; | ||
}); | ||
it('should return null if invalid extensions', () => { | ||
expect(a4a.getAmpAdMetadata_(buildCreativeString({ | ||
customElementExtensions: 'amp-vine', | ||
customStylesheets: [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{href: 'https://fonts.com/css?helloworld'}, | ||
], | ||
}))).to.be.null; | ||
metaData.customElementExtensions = 'amp-vine'; | ||
expect(a4a.getAmpAdMetadata_(buildCreativeString(metaData))).to.be.null; | ||
}); | ||
it('should return null if non-array stylesheets', () => { | ||
expect(a4a.getAmpAdMetadata_(buildCreativeString({ | ||
customElementExtensions: ['amp-vine', 'amp-vine', 'amp-vine'], | ||
customStylesheets: 'https://fonts.googleapis.com/css?foobar', | ||
}))).to.be.null; | ||
metaData.customStylesheets = 'https://fonts.googleapis.com/css?foobar'; | ||
expect(a4a.getAmpAdMetadata_(buildCreativeString(metaData))).to.be.null; | ||
}); | ||
it('should return null if invalid stylesheet object', () => { | ||
expect(a4a.getAmpAdMetadata_(buildCreativeString({ | ||
customElementExtensions: ['amp-vine', 'amp-vine', 'amp-vine'], | ||
customStylesheets: [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{foo: 'https://fonts.com/css?helloworld'}, | ||
], | ||
}))).to.be.null; | ||
metaData.customStylesheets = [ | ||
{href: 'https://fonts.googleapis.com/css?foobar'}, | ||
{foo: 'https://fonts.com/css?helloworld'}, | ||
]; | ||
expect(a4a.getAmpAdMetadata_(buildCreativeString(metaData))).to.be.null; | ||
}); | ||
it('should not include amp images if not an array', () => { | ||
metaData.images = 'https://foo.com'; | ||
const actual = a4a.getAmpAdMetadata_(buildCreativeString(metaData)); | ||
const expected = Object.assign({ | ||
minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, | ||
}, metaData); | ||
delete expected.images; | ||
expect(actual).to.deep.equal(expected); | ||
}); | ||
it('should tolerate missing images', () => { | ||
delete metaData.images; | ||
const actual = a4a.getAmpAdMetadata_(buildCreativeString(metaData)); | ||
const expected = Object.assign({ | ||
minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, | ||
}, metaData); | ||
delete expected.images; | ||
expect(actual).to.deep.equal(expected); | ||
}); | ||
it('should limit to 5 images', () => { | ||
while (metaData.images.length < 10) { | ||
metaData.images.push('https://another.image.com?abc=def'); | ||
} | ||
expect(a4a.getAmpAdMetadata_(buildCreativeString(metaData)).images.length) | ||
.to.equal(5); | ||
}); | ||
// FAILURE cases 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.
Who controls the origin of these images? How do we know we're not preconneting to a tracking service?