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

Type check amp-access and make type checking mandatory for extensions. #10231

Merged
merged 3 commits into from
Jul 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion ads/yieldmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function yieldmo(global, data) {
const ymElem = global.document.createElement('div');
ymElem.id = 'ym_' + data.ymid;
ymElem.className = 'ym';
ymElem.dataset.ampEnabled = true;
ymElem.dataset['ampEnabled'] = true;
global.document.getElementById('c').appendChild(ymElem);

const ymJs = 'https://static.yieldmo.com/ym.amp1.js';
Expand Down
6 changes: 6 additions & 0 deletions build-system/amp.extern.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
*/
function JsonObject() {}

/**
* Force the dataset property to be handled as a JsonObject.
* @type {!JsonObject}
*/
Element.prototype.dataset;

/**
* - n is the name.
* - f is the function body of the extension.
Expand Down
4 changes: 4 additions & 0 deletions build-system/tasks/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ function compile(entryModuleFilenames, outputDir,
'extensions/amp-bind/**/*.js',
// Needed to access form impl from other extensions
'extensions/amp-form/**/*.js',
// Needed for AccessService
'extensions/amp-access/**/*.js',
// Needed to access UserNotificationManager from other extensions
'extensions/amp-user-notification/**/*.js',
'src/*.js',
Expand Down Expand Up @@ -283,6 +285,8 @@ function compile(entryModuleFilenames, outputDir,
'build/patched-module/',
// Can't seem to suppress `(0, win.eval)` suspicious code warning
'3p/environment.js',
// Generated code.
'extensions/amp-access/0.1/access-expr-impl.js',
],
jscomp_error: [],
}
Expand Down
127 changes: 65 additions & 62 deletions extensions/amp-access-laterpay/0.1/laterpay-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {dev, user} from '../../../src/log';
import {installStyles} from '../../../src/style-installer';
import {installStylesForShadowRoot} from '../../../src/shadow-embed';
import {getMode} from '../../../src/mode';
import {dict} from '../../../src/utils/object';
import {listen} from '../../../src/event-helper';
import {removeChildren} from '../../../src/dom';
import {timerFor} from '../../../src/services';
Expand Down Expand Up @@ -46,13 +47,13 @@ const DEFAULT_MESSAGES = {

/**
* @typedef {{
* articleTitleSelector: !string,
* configUrl: string=,
* articleId: string=,
* scrollToTopAfterAuth: boolean=,
* locale: string=,
* localeMessages: object=,
* sandbox: boolean=,
* articleTitleSelector: string,
* configUrl: (string|undefined),
* articleId: (string|undefined),
* scrollToTopAfterAuth: (boolean|undefined),
* locale: (string|undefined),
* localeMessages: (Object|undefined),
* sandbox: (boolean|undefined),
* }}
*/
let LaterpayConfigDef;
Expand All @@ -76,35 +77,35 @@ let PurchaseOptionDef;
* access: boolean,
* apl: string,
* premiumcontent: !PurchaseOptionDef,
* timepasses: Array<PurchaseOptionDef>=,
* subscriptions: Array<PurchaseOptionDef>=
* timepasses: (!Array<PurchaseOptionDef>|undefined),
* subscriptions: (!Array<PurchaseOptionDef>|undefined)
* }}
*/
let PurchaseConfigDef;


/**
* @implements {AccessVendor}
* @implements {../../amp-access/0.1/access-vendor.AccessVendor}
*/
export class LaterpayVendor {

/**
* @param {!AccessService} accessService
* @param {!../../amp-access/0.1/amp-access.AccessService} accessService
*/
constructor(accessService) {
/** @const */
this.ampdoc = accessService.ampdoc;

/** @const @private {!AccessService} */
/** @const @private {!../../amp-access/0.1/amp-access.AccessService} */
this.accessService_ = accessService;

/** @private @const {!Viewport} */
/** @private @const {!../../../src/service/viewport-impl.Viewport} */
this.viewport_ = viewportForDoc(this.ampdoc);

/** @const @private {!LaterpayConfigDef} */
/** @const @private {!JsonObject} For shape see LaterpayConfigDef */
this.laterpayConfig_ = this.accessService_.getAdapterConfig();

/** @private {?PurchaseConfigDef} */
/** @private {?JsonObject} For shape see PurchaseConfigDef */
this.purchaseConfig_ = null;

/** @private {?Function} */
Expand All @@ -113,7 +114,7 @@ export class LaterpayVendor {
/** @private {?Function} */
this.alreadyPurchasedListener_ = null;

/** @const @private {!Array<function(!Event)>} */
/** @const @private {!Array<function()>} */
this.purchaseOptionListeners_ = [];

/** @private {!boolean} */
Expand All @@ -129,27 +130,27 @@ export class LaterpayVendor {
this.purchaseButton_ = null;

/** @private {string} */
this.currentLocale_ = this.laterpayConfig_.locale || 'en';
this.currentLocale_ = this.laterpayConfig_['locale'] || 'en';

/** @private {Object} */
this.i18n_ = Object.assign({}, DEFAULT_MESSAGES,
this.laterpayConfig_.localeMessages || {});
/** @private {!JsonObject} */
this.i18n_ = /** @type {!JsonObject} */ (Object.assign(dict(),
DEFAULT_MESSAGES, this.laterpayConfig_['localeMessages'] || dict()));

/** @private {string} */
this.purchaseConfigBaseUrl_ = this.getConfigUrl_() + CONFIG_BASE_PATH;
const articleId = this.laterpayConfig_.articleId;
const articleId = this.laterpayConfig_['articleId'];
if (articleId) {
this.purchaseConfigBaseUrl_ +=
'&article_id=' + encodeURIComponent(articleId);
}

/** @const @private {!Timer} */
/** @const @private {!../../../src/service/timer-impl.Timer} */
this.timer_ = timerFor(this.ampdoc.win);

/** @const @private {!Vsync} */
/** @const @private {!../../../src/service/vsync-impl.Vsync} */
this.vsync_ = vsyncFor(this.ampdoc.win);

/** @const @private {!Xhr} */
/** @const @private {!../../../src/service/xhr-impl.Xhr} */
this.xhr_ = xhrFor(this.ampdoc.win);

// Install styles.
Expand All @@ -169,10 +170,10 @@ export class LaterpayVendor {
getConfigUrl_() {
if (
(getMode().localDev || getMode().development) &&
this.laterpayConfig_.configUrl
this.laterpayConfig_['configUrl']
) {
return this.laterpayConfig_.configUrl;
} else if (this.laterpayConfig_.sandbox) {
return this.laterpayConfig_['configUrl'];
} else if (this.laterpayConfig_['sandbox']) {
return SANDBOX_CONFIG_URL;
} else {
return CONFIG_URL;
Expand All @@ -191,7 +192,7 @@ export class LaterpayVendor {
'article, or no paid content configurations are setup.');
}

if (this.laterpayConfig_.scrollToTopAfterAuth) {
if (this.laterpayConfig_['scrollToTopAfterAuth']) {
this.vsync_.mutate(() => this.viewport_.setScrollTop(0));
}
this.emptyContainer_();
Expand Down Expand Up @@ -250,23 +251,23 @@ export class LaterpayVendor {
*/
getArticleTitle_() {
const title = this.ampdoc.getRootNode().querySelector(
this.laterpayConfig_.articleTitleSelector);
this.laterpayConfig_['articleTitleSelector']);
user().assert(
title, 'No article title element found with selector %s',
this.laterpayConfig_.articleTitleSelector);
this.laterpayConfig_['articleTitleSelector']);
return title.textContent.trim();
}

/**
* @return {!Node}
* @return {!Element}
* @private
*/
getContainer_() {
const id = TAG + '-dialog';
const dialogContainer = this.ampdoc.getElementById(id);
return user().assert(
return user().assertElement(
dialogContainer,
'No element found with id %s', id
'No element found with id ' + id
);
}

Expand Down Expand Up @@ -305,46 +306,48 @@ export class LaterpayVendor {
const dialogContainer = this.getContainer_();
this.innerContainer_ = this.createElement_('div');
this.innerContainer_.className = TAG + '-container';
if (this.laterpayConfig_.sandbox) {
if (this.laterpayConfig_['sandbox']) {
this.renderTextBlock_('sandbox');
}
this.renderTextBlock_('header');
const listContainer = this.createElement_('ul');
this.purchaseConfig_.premiumcontent['tp_title'] =
this.i18n_.premiumContentTitle;
this.purchaseConfig_.premiumcontent.description = this.getArticleTitle_();
this.purchaseConfig_['premiumcontent']['tp_title'] =
this.i18n_['premiumContentTitle'];
this.purchaseConfig_['premiumcontent']['description'] =
this.getArticleTitle_();
listContainer.appendChild(
this.createPurchaseOption_(this.purchaseConfig_.premiumcontent)
this.createPurchaseOption_(this.purchaseConfig_['premiumcontent'])
);
this.purchaseConfig_.timepasses.forEach(timepass => {
this.purchaseConfig_['timepasses'].forEach(timepass => {
listContainer.appendChild(this.createPurchaseOption_(timepass));
});
this.purchaseConfig_.subscriptions.forEach(subscription => {
this.purchaseConfig_['subscriptions'].forEach(subscription => {
listContainer.appendChild(this.createPurchaseOption_(subscription));
});
const purchaseButton = this.createElement_('button');
purchaseButton.className = TAG + '-purchase-button';
purchaseButton.textContent = this.i18n_.defaultButton;
purchaseButton.textContent = this.i18n_['defaultButton'];
this.purchaseButton_ = purchaseButton;
this.purchaseButtonListener_ = listen(purchaseButton, 'click', ev => {
const value = this.selectedPurchaseOption_.value;
const purchaseType = this.selectedPurchaseOption_.dataset.purchaseType;
const purchaseType = this.selectedPurchaseOption_.dataset['purchaseType'];
this.handlePurchase_(ev, value, purchaseType);
});
this.innerContainer_.appendChild(listContainer);
this.innerContainer_.appendChild(purchaseButton);
this.innerContainer_.appendChild(
this.createAlreadyPurchasedLink_(this.purchaseConfig_.apl));
this.createAlreadyPurchasedLink_(this.purchaseConfig_['apl']));
this.renderTextBlock_('footer');
dialogContainer.appendChild(this.innerContainer_);
dialogContainer.appendChild(this.createLaterpayBadge_());
this.containerEmpty_ = false;
this.preselectFirstOption_(listContainer.firstElementChild);
this.preselectFirstOption_(
dev().assertElement(listContainer.firstElementChild));
}

/**
* @private
* @param {!Node} firstOption
* @param {!Element} firstOption
*/
preselectFirstOption_(firstOption) {
const firstInput = firstOption.querySelector('input[type="radio"]');
Expand All @@ -367,7 +370,7 @@ export class LaterpayVendor {

/**
* @private
* @return {!Node}
* @return {!Element}
*/
createLaterpayBadge_() {
const a = this.createElement_('a');
Expand All @@ -382,42 +385,42 @@ export class LaterpayVendor {
}

/**
* @param {!PurchaseOptionDef} option
* @return {!Node}
* @param {!JsonObject} option Shape: PurchaseOptionDef
* @return {!Element}
* @private
*/
createPurchaseOption_(option) {
const li = this.createElement_('li');
const control = this.createElement_('label');
control.for = option.tp_title;
control.for = option['tp_title'];
control.appendChild(this.createRadioControl_(option));
const metadataContainer = this.createElement_('div');
metadataContainer.className = TAG + '-metadata';
const title = this.createElement_('span');
title.className = TAG + '-title';
title.textContent = option.tp_title;
title.textContent = option['tp_title'];
metadataContainer.appendChild(title);
const description = this.createElement_('p');
description.className = TAG + '-description';
description.textContent = option.description;
description.textContent = option['description'];
metadataContainer.appendChild(description);
control.appendChild(metadataContainer);
li.appendChild(control);
li.appendChild(this.createPrice_(option.price));
li.appendChild(this.createPrice_(option['price']));
return li;
}

/**
* @param {!PurchaseOptionDef} option
* @return {!Node}
* @param {!JsonObject} option Shape: PurchaseOptionDef
* @return {!Element}
* @private
*/
createRadioControl_(option) {
const radio = this.createElement_('input');
radio.name = 'purchaseOption';
radio.type = 'radio';
radio.id = option.tp_title;
radio.value = option.purchase_url;
radio.id = option['tp_title'];
radio.value = option['purchase_url'];
const purchaseType = option['purchase_type'] === 'ppu' ?
'payLater' :
'payNow';
Expand All @@ -432,7 +435,7 @@ export class LaterpayVendor {

/**
* @param {!Object<string, number>} price
* @return {!Node}
* @return {!Element}
* @private
*/
createPrice_(price) {
Expand Down Expand Up @@ -467,14 +470,14 @@ export class LaterpayVendor {

/**
* @param {!string} href
* @return {!Node}
* @return {!Element}
*/
createAlreadyPurchasedLink_(href) {
const p = this.createElement_('p');
p.className = TAG + '-already-purchased-link-container';
const a = this.createElement_('a');
a.href = href;
a.textContent = this.i18n_.alreadyPurchasedLink;
a.textContent = this.i18n_['alreadyPurchasedLink'];
this.alreadyPurchasedListener_ = listen(a, 'click', ev => {
this.handlePurchase_(ev, href, 'alreadyPurchased');
});
Expand All @@ -488,17 +491,17 @@ export class LaterpayVendor {
*/
handlePurchaseOptionSelection_(ev) {
ev.preventDefault();
this.selectPurchaseOption_(ev.target);
this.selectPurchaseOption_(dev().assertElement(ev.target));
}

/**
* @param {!Node} target
* @param {!Element} target
* @private
*/
selectPurchaseOption_(target) {
const selectedOptionClassname = TAG + '-selected';
const prevPurchaseOption = this.selectedPurchaseOption_;
const purchaseActionLabel = target.dataset.purchaseActionLabel;
const purchaseActionLabel = target.dataset['purchaseActionLabel'];
if (prevPurchaseOption &&
prevPurchaseOption.classList.contains(selectedOptionClassname)) {
prevPurchaseOption.classList.remove(selectedOptionClassname);
Expand Down
Loading