Skip to content

Commit

Permalink
Type check amp-access and make type checking mandatory for extensions. (
Browse files Browse the repository at this point in the history
#10231)

Tightens type for `Element.prototype.dataset` to be an `@dict` object that requires property access.

Part of #9876
  • Loading branch information
cramforce authored Jul 5, 2017
1 parent c3d6308 commit 1209e7f
Show file tree
Hide file tree
Showing 19 changed files with 235 additions and 210 deletions.
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

0 comments on commit 1209e7f

Please sign in to comment.