forked from ampproject/amphtml
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial draft amp-mraid implementation (ampproject#19531)
* initial draft amp-mraid implementation * responding to reviewer comments: * switched amp-mraid from a custom element to a host api * minor things * rework validation doc * move MraidService to its own file, minor tweaks * finish splitting out mraid service * rework error handling * document why impedance matching isn't needed * not_supported -> unsupported * make lint and type checker happy
- Loading branch information
1 parent
16b53a7
commit f1e30f2
Showing
9 changed files
with
486 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<!doctype html> | ||
<html ⚡4ads> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> | ||
<style amp4ads-boilerplate>body{visibility:hidden}</style> | ||
<meta name="amp4ads-id" content="vendor=doubleclick,type=impression-id,value=12345"> | ||
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script> | ||
<script async host-api="amp-mraid" src="https://cdn.ampproject.org/v0/amp-mraid-0.1.js"></script> | ||
<script async src="https://cdn.ampproject.org/v0.js"></script> | ||
<!-- <script async src="https://cdn.ampproject.org/amp4ads-v0.js"></script> --> | ||
<style amp-custom> | ||
.left { | ||
text-align: left; | ||
} | ||
.right { | ||
text-align: right; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div class="left"> | ||
<amp-img id="img-1" width="150" height="100" | ||
src="https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-Rf78IofLb9QjS5_0mqsY1zEFc=w300-h50-no"> | ||
</amp-img> | ||
</div> | ||
<div class="right"> | ||
<a href="https://google.com" target="_blank"> | ||
<amp-img id="img-2" width="150" height="100" | ||
src="https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-Rf78IofLb9QjS5_0mqsY1zEFc=w300-h80-no"> | ||
</amp-img> | ||
</a> | ||
</div> | ||
<amp-pixel src="https://www.google.com/?cid=CLIENT_ID(a)"></amp-pixel> | ||
<amp-analytics> | ||
<script type="application/json"> | ||
{ | ||
"transport": {"beacon": false, "xhrpost": false}, | ||
"requests": { | ||
"visibility": "/${type}?cid=CLIENT_ID(a)&elementX=${elementX}&elementY=${elementY}&elementWidth=${elementWidth}&elementHeight=${elementHeight}&totalTime=${totalTime}&totalVisibleTime=${totalVisibleTime}&maxContinuousVisibleTime=${maxContinuousVisibleTime}&loadTimeVisibility=${loadTimeVisibility}&backgrounded=${backgrounded}&backgroundedAtStart=${backgroundedAtStart}&firstSeenTime=${firstSeenTime}&lastSeenTime=${lastSeenTime}&firstVisibleTime=${firstVisibleTime}&lastVisibleTime=${lastVisibleTime}&minVisiblePercentage=${minVisiblePercentage}&maxVisiblePercentage=${maxVisiblePercentage}&intersectionRatio=${intersectionRatio}" | ||
}, | ||
"triggers": { | ||
"visible": { | ||
"on": "visible", | ||
"request": "visibility", | ||
"vars": { "type": "visible" } | ||
}, | ||
"rootVisible": { | ||
"on": "visible", | ||
"request": "visibility", | ||
"visibilitySpec": { | ||
"selector": ":root", | ||
"visiblePercentageMin": 0 | ||
}, | ||
"vars": { "type": "rootVisible" } | ||
}, | ||
"imgVisible": { | ||
"on": "visible", | ||
"request": "visibility", | ||
"visibilitySpec": { | ||
"selector": "#img-2", | ||
"selectionMethod": "scope", | ||
"visiblePercentageMin": 0 | ||
}, | ||
"vars": { "type": "img2Visible" } | ||
} | ||
} | ||
} | ||
</script> | ||
</amp-analytics> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Fake mraid.js that lets us pretend to be in a mobile app environment. | ||
window.mraid = {}; | ||
window.mraid.getState = function() { | ||
return Math.random() < 0.5 ? 'ready' : 'loading'; | ||
}; | ||
window.mraid.addEventListener = function(event, callback) { | ||
if (event === 'ready') { | ||
window.setTimeout(1000, callback); | ||
} else if (event === 'exposureChange') { | ||
window.setTimeout(1000, function() { | ||
callback(.7, null, null); | ||
}); | ||
} else { | ||
console.log('unknown event ' + event); | ||
} | ||
}; | ||
window.mraid.close = function() { | ||
console.log('close'); | ||
}; | ||
window.mraid.open = function (url) { | ||
console.log('open ' + url); | ||
}; | ||
window.mraid.expand = function() { | ||
console.log('expand'); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
/** | ||
* Copyright 2018 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. | ||
*/ | ||
|
||
/** | ||
* @fileoverview Connects AMP Host Services to MRAID to allow invoking native | ||
* APIs from mobile app ad webviews. | ||
* | ||
* Example: | ||
* <code> | ||
* <script async host-api="amp-mraid" | ||
* fallback-on="mismatch"></script> | ||
* </code> | ||
* | ||
*/ | ||
|
||
import { | ||
HostServiceError, | ||
HostServices, | ||
} from '../../../src/inabox/host-services'; | ||
import {MraidService} from './mraid-service'; | ||
import {dev} from '../../../src/log'; | ||
import {getMode} from '../../../src/mode'; | ||
|
||
const TAG = 'amp-mraid'; | ||
const FALLBACK_ON = 'fallback-on'; | ||
|
||
/** | ||
* String representations of the HostServicesErrors that can be used in the | ||
* 'fallback-on' attribute. | ||
* | ||
* @const @enum {number} | ||
*/ | ||
const FallbackErrorNames = { | ||
'mismatch': HostServiceError.MISMATCH, | ||
'unsupported': HostServiceError.UNSUPPORTED, | ||
}; | ||
|
||
/** | ||
* Loads mraid.js if available, and once it's loaded looks good, configures an | ||
* MraidService to handle visibility, fullscreen, and exit. | ||
*/ | ||
export class MraidInitializer { | ||
/** | ||
* @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc | ||
*/ | ||
constructor(ampdoc) { | ||
/** @private {!../../../src/service/ampdoc-impl.AmpDoc} */ | ||
this.ampdoc_ = ampdoc; | ||
|
||
/** @private {!Array<number>} */ | ||
this.fallbackOn_ = []; | ||
|
||
/** @private {boolean} */ | ||
this.registeredWithHostServices_ = false; | ||
|
||
/** @private */ | ||
this.mraid_ = null; | ||
|
||
const ampMraidScripts = this.ampdoc_.getHeadNode().querySelectorAll( | ||
'script[host-api="amp-mraid"]'); | ||
if (ampMraidScripts.length > 1) { | ||
dev().error(TAG, 'Multiple amp-mraid scripts.'); | ||
return; | ||
} else if (ampMraidScripts.length < 1) { | ||
dev().error(TAG, 'Missing amp-mraid scripts.'); | ||
return; | ||
} | ||
const element = ampMraidScripts[0]; | ||
|
||
if (getMode().runtime !== 'inabox') { | ||
dev().error(TAG, 'Only supported with Inabox'); | ||
return; | ||
} | ||
|
||
this.fallbackOn_ = []; | ||
const fallbackOnErrorNames = | ||
(element.getAttribute(FALLBACK_ON) || '').split(' '); | ||
for (let i = 0; i < fallbackOnErrorNames.length; i++) { | ||
const errorName = fallbackOnErrorNames[i]; | ||
if (errorName) { | ||
if (!(errorName in FallbackErrorNames)) { | ||
dev().error(TAG, `Unknown ${FALLBACK_ON} "${errorName}"`); | ||
return; | ||
} | ||
this.fallbackOn_.push(FallbackErrorNames[errorName]); | ||
} | ||
} | ||
|
||
// It looks like we're initiating a network load for mraid from a relative | ||
// url, but this will actually be intercepted by the mobile app SDK and | ||
// handled locally. | ||
const mraidJs = document.createElement('script'); | ||
mraidJs.setAttribute('type', 'text/javascript'); | ||
mraidJs.setAttribute('src', 'mraid.js'); | ||
mraidJs.addEventListener('load', () => { | ||
this.mraidLoadSuccess_(); | ||
}); | ||
mraidJs.addEventListener('error', () => { | ||
this.handleError_(HostServiceError.MISMATCH); | ||
}); | ||
const head = document.getElementsByTagName('head').item(0); | ||
head.appendChild(mraidJs); | ||
} | ||
|
||
/** | ||
* @param {number} hostServiceError | ||
*/ | ||
handleError_(hostServiceError) { | ||
if (!this.registeredWithHostServices_ && | ||
this.fallbackOn_.includes(hostServiceError)) { | ||
this.declineService_(); | ||
} | ||
// TODO: send error ping | ||
} | ||
|
||
/** | ||
* Runs when MRAID reports that it is ready. | ||
*/ | ||
mraidReady_() { | ||
const mraidService = new MraidService(this.mraid_); | ||
|
||
HostServices.installVisibilityServiceForDoc( | ||
this.ampdoc_, () => mraidService); | ||
HostServices.installFullscreenServiceForDoc( | ||
this.ampdoc_, () => mraidService); | ||
HostServices.installExitServiceForDoc( | ||
this.ampdoc_, () => mraidService); | ||
|
||
this.registeredWithHostServices_ = true; | ||
} | ||
|
||
/** | ||
* Runs if mraid.js was loaded successfully. | ||
*/ | ||
mraidLoadSuccess_() { | ||
const mraid = window['mraid']; | ||
if (!mraid || !mraid.getState || !mraid.addEventListener | ||
|| !mraid.close || !mraid.open || !mraid.expand) { | ||
this.handleError_(HostServiceError.UNSUPPORTED); | ||
return; | ||
} | ||
this.mraid_ = mraid; | ||
if (mraid.getState() === 'loading') { | ||
mraid.addEventListener('ready', () => { | ||
this.mraidReady_(); | ||
}); | ||
} else { | ||
this.mraidReady_(); | ||
} | ||
} | ||
|
||
/** | ||
* Stub for handling the case when we want to allow fallback to the standard | ||
* web way of doing things. | ||
*/ | ||
declineService_() { | ||
// Needs API change | ||
} | ||
} | ||
|
||
AMP.extension(TAG, '0.1', AMP => { | ||
AMP.registerServiceForDoc(TAG, MraidInitializer); | ||
}); |
Oops, something went wrong.