From d69857ad69cd3695a9534ab6a0b06d69f5ecab05 Mon Sep 17 00:00:00 2001 From: Ryan Cebulko Date: Wed, 5 May 2021 19:13:58 -0400 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Enable=20type-checking=20s?= =?UTF-8?q?rc/polyfills=20in=20CI=20(#34239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Clean up directory globs * Move src/core srcs+externs to const arrays * Exclude fetch/get-bounding-client-rect for now * Provide extern for promise-pjs polyfill * Fix types and add comments in abort-controller * Fix types and add comments in intersection-observer-stub * Remove @suppress {checkTypes} from promise * Fix @this type for document.contains() polyfill * Remove unneeded typecast to unknown * Fix types and add comments in resize-observer-stub * Fix types and add comments in custom-elements * Add ! * Fix types in get-bounding-client-rect * Add more ! * Lint fixes * Allow custom-element.externs.js to use window * Revert no-op JSDoc changes * Remove connected callback externs and typecast instead * Clean up typedefs for observer stubs * Fix typo * Dedupe typedef --- build-system/tasks/check-types.js | 49 ++++++---- src/.eslintrc.js | 5 +- src/polyfills/abort-controller.js | 27 +++--- src/polyfills/custom-elements.extern.js | 20 ++++ src/polyfills/custom-elements.js | 88 +++++++----------- src/polyfills/document-contains.js | 2 +- src/polyfills/get-bounding-client-rect.js | 2 + src/polyfills/promise.extern.js | 23 +++++ src/polyfills/promise.js | 3 +- src/polyfills/shame.extern.js | 59 ++++++++++++ .../stubs/intersection-observer-stub.js | 92 +++++++++---------- src/polyfills/stubs/resize-observer-stub.js | 47 ++++------ 12 files changed, 248 insertions(+), 169 deletions(-) create mode 100644 src/polyfills/custom-elements.extern.js create mode 100644 src/polyfills/promise.extern.js create mode 100644 src/polyfills/shame.extern.js diff --git a/build-system/tasks/check-types.js b/build-system/tasks/check-types.js index 33894a32d1078..4a86ab41ef58e 100644 --- a/build-system/tasks/check-types.js +++ b/build-system/tasks/check-types.js @@ -70,7 +70,15 @@ const PRIDE_FILES_GLOBS = [ 'node_modules/promise-pjs/promise.mjs', ]; -const CORE_EXTERNS_GLOB = 'src/core{,/**}/*.extern.js'; +// We provide glob lists for core src/externs since any other targets are +// allowed to depend on core. +const CORE_SRCS_GLOBS = [ + 'src/core/**/*.js', + + // Needed for CSS escape polyfill + 'third_party/css-escape/css-escape.js', +]; +const CORE_EXTERNS_GLOBS = ['src/core/**/*.extern.js']; /** * Generates a list of source file paths for extensions to type-check @@ -98,55 +106,56 @@ const TYPE_CHECK_TARGETS = { // To test a target locally: // `amp check-types --target=src-foo-bar --warning_level=verbose` 'src-amp-story-player': { - srcGlobs: ['src/amp-story-player{,/**}/*.js'], + srcGlobs: ['src/amp-story-player/**/*.js'], warningLevel: 'QUIET', }, 'src-context': { - srcGlobs: ['src/context{,/**}/*.js'], + srcGlobs: ['src/context/**/*.js'], warningLevel: 'QUIET', }, 'src-core': { - srcGlobs: [ - 'src/core{,/**}/*.js', - // Needed for CSS escape polyfill - 'third_party/css-escape/css-escape.js', - ], - externGlobs: [CORE_EXTERNS_GLOB], + srcGlobs: CORE_SRCS_GLOBS, + externGlobs: CORE_EXTERNS_GLOBS, }, 'src-examiner': { - srcGlobs: ['src/examiner{,/**}/*.js'], + srcGlobs: ['src/examiner/**/*.js'], warningLevel: 'QUIET', }, 'src-experiments': { - srcGlobs: ['src/experiments{,/**}/*.js'], + srcGlobs: ['src/experiments/**/*.js'], warningLevel: 'QUIET', }, 'src-inabox': { - srcGlobs: ['src/inabox{,/**}/*.js'], + srcGlobs: ['src/inabox/**/*.js'], warningLevel: 'QUIET', }, 'src-polyfills': { - srcGlobs: ['src/polyfills{,/**}/*.js'], - warningLevel: 'QUIET', + srcGlobs: [ + 'src/polyfills/**/*.js', + // Exclude fetch its dependencies are cleaned up/extracted to core. + '!src/polyfills/fetch.js', + ...CORE_SRCS_GLOBS, + ], + externGlobs: ['src/polyfills/**/*.extern.js', ...CORE_EXTERNS_GLOBS], }, 'src-preact': { - srcGlobs: ['src/preact{,/**}/*.js'], + srcGlobs: ['src/preact/**/*.js'], warningLevel: 'QUIET', }, 'src-purifier': { - srcGlobs: ['src/purifier{,/**}/*.js'], + srcGlobs: ['src/purifier/**/*.js'], warningLevel: 'QUIET', }, 'src-service': { - srcGlobs: ['src/service{,/**}/*.js'], + srcGlobs: ['src/service/**/*.js'], warningLevel: 'QUIET', }, 'src-utils': { - srcGlobs: ['src/utils{,/**}/*.js'], + srcGlobs: ['src/utils/**/*.js'], warningLevel: 'QUIET', }, 'src-web-worker': { - srcGlobs: ['src/web-worker{,/**}/*.js'], + srcGlobs: ['src/web-worker/**/*.js'], warningLevel: 'QUIET', }, @@ -157,7 +166,7 @@ const TYPE_CHECK_TARGETS = { // bug for cherry-pick. 'pride': { srcGlobs: PRIDE_FILES_GLOBS, - externGlobs: [CORE_EXTERNS_GLOB, 'build-system/externs/*.extern.js'], + externGlobs: ['build-system/externs/*.extern.js', ...CORE_EXTERNS_GLOBS], }, /* diff --git a/src/.eslintrc.js b/src/.eslintrc.js index e9e92d11580d8..065a75ce16350 100644 --- a/src/.eslintrc.js +++ b/src/.eslintrc.js @@ -68,7 +68,10 @@ module.exports = { 'rules': {'import/no-restricted-paths': isCiBuild() ? 0 : 1}, }, { - 'files': ['./core/window.extern.js'], + 'files': [ + './core/window.extern.js', + './polyfills/custom-elements.extern.js', + ], 'rules': {'local/no-global': 0}, }, ], diff --git a/src/polyfills/abort-controller.js b/src/polyfills/abort-controller.js index 6127f8a6516bf..4f0e6f54278f5 100644 --- a/src/polyfills/abort-controller.js +++ b/src/polyfills/abort-controller.js @@ -14,14 +14,15 @@ * limitations under the License. */ +/** Polyfill for the public AbortController. */ class AbortController { - /** */ + /** Constructor. */ constructor() { /** @const {!AbortSignal} */ this.signal_ = new AbortSignal(); } - /** */ + /** Triggers an abort signal. */ abort() { if (this.signal_.isAborted_) { // Already aborted. @@ -29,25 +30,24 @@ class AbortController { } this.signal_.isAborted_ = true; if (this.signal_.onabort_) { - const event = { + const event = /** @type {!Event} */ ({ 'type': 'abort', 'bubbles': false, 'cancelable': false, 'target': this.signal_, 'currentTarget': this.signal_, - }; + }); this.signal_.onabort_(event); } } - /** - * @return {!AbortSignal} - */ + /** @return {!AbortSignal} */ get signal() { return this.signal_; } } +/** Polyfill for the public AbortSignal. */ class AbortSignal { /** */ constructor() { @@ -57,29 +57,24 @@ class AbortSignal { this.onabort_ = null; } - /** - * @return {boolean} - */ + /** @return {boolean} */ get aborted() { return this.isAborted_; } - /** - * @return {?function(!Event)} - */ + /** @return {?function(!Event)} */ get onabort() { return this.onabort_; } - /** - * @param {?function(!Event)} value - */ + /** @param {?function(!Event)} value */ set onabort(value) { this.onabort_ = value; } } /** + * Sets the AbortController and AbortSignal polyfills if not defined. * @param {!Window} win */ export function install(win) { diff --git a/src/polyfills/custom-elements.extern.js b/src/polyfills/custom-elements.extern.js new file mode 100644 index 0000000000000..d05302e616fbf --- /dev/null +++ b/src/polyfills/custom-elements.extern.js @@ -0,0 +1,20 @@ +/** + * Copyright 2021 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. + */ + +// This is needed only by custom elements, so it doesn't need to be in the core +// window externs file. +/** @type {!typeof HTMLElement} */ +window.HTMLElementOrig; diff --git a/src/polyfills/custom-elements.js b/src/polyfills/custom-elements.js index bd656c61c0564..bc1389709fc2a 100644 --- a/src/polyfills/custom-elements.js +++ b/src/polyfills/custom-elements.js @@ -14,17 +14,15 @@ * limitations under the License. */ -/** - * @typedef {{ - * promise: !Promise, - * resolve: function(), - * }} - */ -let DeferredDef; +import {Deferred} from '../core/data-structures/promise'; /** - * @typedef {!typeof HTMLElement} + * For type anotations where Element is a local variable. + * @typedef {!Element} */ +let ElementOrigDef; + +/** @typedef {!typeof HTMLElement} */ let CustomElementConstructorDef; /** @@ -102,6 +100,7 @@ function isPatched(win) { /** * Throws the error outside the current event loop. + * TODO(rcebulko): Condense with core/error#rethrowAsync * * @param {!Error} error */ @@ -121,21 +120,13 @@ class CustomElementRegistry { * @param {!Registry} registry */ constructor(win, registry) { - /** - * @const @private - */ + /** @const @private */ this.win_ = win; - /** - * @const @private - */ + /** @const @private */ this.registry_ = registry; - /** - * @type {!Object} - * @private - * @const - */ + /** @private @const @type {!Object} */ this.pendingDefines_ = Object.create(null); } @@ -188,19 +179,13 @@ class CustomElementRegistry { } const pending = this.pendingDefines_; - const deferred = pending[name]; - if (deferred) { - return deferred.promise; + let deferred = pending[name]; + if (!deferred) { + deferred = new Deferred(); + pending[name] = deferred; } - let resolve; - const promise = new /*OK*/ Promise((res) => (resolve = res)); - pending[name] = { - promise, - resolve, - }; - - return promise; + return deferred.promise; } /** @@ -223,16 +208,10 @@ class Registry { * @param {!Window} win */ constructor(win) { - /** - * @private @const - */ + /** @private @const */ this.win_ = win; - /** - * @type {!Object} - * @private - * @const - */ + /** @private @const @type {!Object} */ this.definitions_ = Object.create(null); /** @@ -243,7 +222,7 @@ class Registry { /** * The currently upgrading element. - * @private {Element} + * @private {?Element} */ this.current_ = null; @@ -251,7 +230,7 @@ class Registry { * Once started (after the first Custom Element definition), this tracks * DOM append and removals. * - * @private {MutationObserver} + * @private {?MutationObserver} */ this.mutationObserver_ = null; @@ -273,7 +252,7 @@ class Registry { * constructor while returning this current node in the HTMLElement * class constructor (the base class of all custom elements). * - * @return {Element} + * @return {?Element} */ current() { const current = this.current_; @@ -285,7 +264,7 @@ class Registry { * Finds the custom element definition by name. * * @param {string} name - * @return {CustomElementDef|undefined} + * @return {!CustomElementDef|undefined} */ getByName(name) { const definition = this.definitions_[name]; @@ -297,8 +276,8 @@ class Registry { /** * Finds the custom element definition by constructor instance. * - * @param {CustomElementConstructorDef} ctor - * @return {CustomElementDef|undefined} + * @param {!CustomElementConstructorDef} ctor + * @return {!CustomElementDef|undefined} */ getByConstructor(ctor) { const definitions = this.definitions_; @@ -445,7 +424,8 @@ class Registry { if (!def) { return; } - this.upgradeSelf_(/** @type {!Element} */ (node), def); + node = /** @type {!HTMLElement} */ (node); + this.upgradeSelf_(node, def); // TODO(jridgewell): It may be appropriate to adoptCallback, if the node // used to be in another doc. // TODO(jridgewell): I should be calling the definitions connectedCallback @@ -467,6 +447,7 @@ class Registry { disconnectedCallback_(node) { // TODO(jridgewell): I should be calling the definitions connectedCallback // with node as the context. + node = /** @type {!HTMLElement} */ (node); if (node.disconnectedCallback) { try { node.disconnectedCallback(); @@ -725,7 +706,7 @@ function polyfill(win) { const {attachShadow, createShadowRoot} = elProto; if (attachShadow) { /** - * @param {!{mode: string}} unused + * @param {{mode: string}} unused * @return {!ShadowRoot} */ elProto.attachShadow = function (unused) { @@ -739,9 +720,7 @@ function polyfill(win) { }; } if (createShadowRoot) { - /** - * @return {!ShadowRoot} - */ + /** @return {!ShadowRoot} */ elProto.createShadowRoot = function () { const shadow = createShadowRoot.apply(this, arguments); registry.observe(shadow); @@ -757,7 +736,7 @@ function polyfill(win) { * You can't use the real HTMLElement constructor, because you can't subclass * it without using native classes. So, mock its approximation using * createElement. - * @return {*} TODO(#23582): Specify return type + * @return {!ElementOrigDef} */ function HTMLElementPolyfill() { const {constructor} = this; @@ -825,9 +804,7 @@ function polyfill(win) { */ function wrapHTMLElement(win) { const {HTMLElement, Reflect} = win; - /** - * @return {!Element} - */ + /** @return {!Element} */ function HTMLElementWrapper() { const ctor = /** @type {function(...?):?|undefined} */ (this.constructor); @@ -846,8 +823,8 @@ function wrapHTMLElement(win) { /** * Setups up prototype inheritance * - * @param {!typeof SUPER} superClass - * @param {!typeof SUB} subClass + * @param {!SUPER} superClass + * @param {!SUB} subClass * @template SUPER * @template SUB */ @@ -882,6 +859,7 @@ function supportsUnderProto() { * old IE. * @param {!Object} obj * @param {!Object} prototype + * @suppress {suspiciousCode} due to IS_ESM inlining */ function setPrototypeOf(obj, prototype) { if (IS_ESM || Object.setPrototypeOf) { diff --git a/src/polyfills/document-contains.js b/src/polyfills/document-contains.js index 546c0df9875ae..5d5d2781b41cf 100644 --- a/src/polyfills/document-contains.js +++ b/src/polyfills/document-contains.js @@ -20,7 +20,7 @@ * See https://developer.mozilla.org/en-US/docs/Web/API/Node/contains * @param {?Node} node * @return {boolean} - * @this {Node} + * @this {Document} */ function documentContainsPolyfill(node) { // Per spec, "contains" method is inclusionary diff --git a/src/polyfills/get-bounding-client-rect.js b/src/polyfills/get-bounding-client-rect.js index e261150247520..cc6d160b257a5 100644 --- a/src/polyfills/get-bounding-client-rect.js +++ b/src/polyfills/get-bounding-client-rect.js @@ -34,6 +34,7 @@ let nativeClientRect; * Polyfill for Node.getBoundingClientRect API. * @this {!Element} * @return {!ClientRect|!LayoutRectDef} + * @suppress {suspiciousCode} due to IS_ESM inlining */ function getBoundingClientRect() { // eslint-disable-next-line local/no-invalid-this @@ -48,6 +49,7 @@ function getBoundingClientRect() { * Determines if this polyfill should be installed. * @param {!Window} win * @return {boolean} + * @suppress {uselessCode} due to IS_ESM inlining */ function shouldInstall(win) { if (IS_ESM) { diff --git a/src/polyfills/promise.extern.js b/src/polyfills/promise.extern.js new file mode 100644 index 0000000000000..82d7de4b6a176 --- /dev/null +++ b/src/polyfills/promise.extern.js @@ -0,0 +1,23 @@ +/** + * Copyright 2021 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 Provide extern for promise-pjs polyfill. + * @externs + */ + +/** type {!Promise} */ +let default$$module$promise_pjs; diff --git a/src/polyfills/promise.js b/src/polyfills/promise.js index 8cb9e918db48d..37ed4f1ab6a12 100644 --- a/src/polyfills/promise.js +++ b/src/polyfills/promise.js @@ -19,11 +19,10 @@ import Promise from 'promise-pjs'; /** * Sets the Promise polyfill if it does not exist. * @param {!Window} win - * @suppress {checkTypes} */ export function install(win) { if (!win.Promise) { - win.Promise = /** @type {?} */ (Promise); + win.Promise = Promise; // In babel the * export is an Object with a default property. // In closure compiler it is the Promise function itself. if (Promise.default) { diff --git a/src/polyfills/shame.extern.js b/src/polyfills/shame.extern.js new file mode 100644 index 0000000000000..b5c0f21e1eaeb --- /dev/null +++ b/src/polyfills/shame.extern.js @@ -0,0 +1,59 @@ +/** + * Copyright 2021 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 The junk-drawer of externs that haven't yet been sorted well. + * Shame! Shame! Shame! Avoid adding to this. + * + * It's okay for some things to start off here, since moving them doesn't + * require any other file changes (unlike real code, which requires updating) + * imports throughout the repo). + * + * @externs + */ + +// This definition is exported by the fetch polyfill implementation, but +// currently has dependencies on non-core modules that aren't yet type-checked. +// +// Planned destination: this should be removed when fetch is re-included in the +// polyfills type-check target. +/** @type {function(!Window)} */ +let install$$module$src$polyfills$fetch; + +// These definitions live in non-core files but are consumed by +// get-bounding-client-rect. They should be removed as dom.js and layout-rect.js +// are moved to core. +/** + * The structure that combines position and size for an element. The exact + * interpretation of position and size depends on the use case. Replicated from + * layout-rect.js + * + * @typedef {{ + * top: number, + * bottom: number, + * left: number, + * right: number, + * width: number, + * height: number, + * x: number, + * y: number + * }} + */ +let LayoutRectDef$$module$src$layout_rect; +/** @type {function(number, number, number, number):LayoutRectDef$$module$src$layout_rect} */ +let layoutRectLtwh$$module$src$layout_rect; +/** @type {function(!Element):boolean} */ +let isConnectedNode$$module$src$dom; diff --git a/src/polyfills/stubs/intersection-observer-stub.js b/src/polyfills/stubs/intersection-observer-stub.js index baca8160e620c..5513c28c2fc88 100644 --- a/src/polyfills/stubs/intersection-observer-stub.js +++ b/src/polyfills/stubs/intersection-observer-stub.js @@ -23,6 +23,9 @@ * amp-intersection-observer-polyfill extension. */ +/** @typedef {function(!typeof IntersectionObserver)} */ +let InObUpgraderDef; + const UPGRADERS = '_upgraders'; const NATIVE = '_native'; const STUB = '_stub'; @@ -52,23 +55,30 @@ export function shouldLoadPolyfill(win) { * @return {boolean} */ function isWebkit(win) { - // navigator.vendor is always "Apple Computer, Inc." for all iOS browsers and Mac OS Safari. + // navigator.vendor is always "Apple Computer, Inc." for all iOS browsers and + // Mac OS Safari. return /apple/i.test(win.navigator.vendor); } /** - * @param {typeof IntersectionObserver} Native - * @param {typeof IntersectionObserver} Polyfill - * @return {typeof IntersectionObserver} + * @param {!typeof IntersectionObserver} Native + * @param {!typeof IntersectionObserver} Polyfill + * @return {!typeof IntersectionObserver} */ function getIntersectionObserverDispatcher(Native, Polyfill) { - return function (ioCallback, opts) { - if (opts && opts.root && opts.root.nodeType === 9) { + /** + * @param {!IntersectionObserverCallback} ioCallback + * @param {IntersectionObserverInit=} opts + * @return {!IntersectionObserver} + */ + function Ctor(ioCallback, opts) { + if (opts?.root?.nodeType === /* Node.DOCUMENT_NODE */ 9) { return new Polyfill(ioCallback, opts); } else { return new Native(ioCallback, opts); } - }; + } + return Ctor; } /** @@ -101,7 +111,11 @@ export function installStub(win) { */ export function supportsDocumentRoot(win) { try { - new win.IntersectionObserver(() => {}, {root: win.document}); + new win.IntersectionObserver(() => {}, { + // TODO(rcebulko): Update when CC updates their externs + // See https://github.com/google/closure-compiler/pull/3804 + root: /** @type {?} */ (win.document), + }); return true; } catch { return false; @@ -115,8 +129,7 @@ export function supportsDocumentRoot(win) { export function upgradePolyfill(win, installer) { // Can't use the IntersectionObserverStub here directly since it's a separate // instance deployed in v0.js vs the polyfill extension. - const Stub = /** @type {typeof IntersectionObserverStub} */ (win - .IntersectionObserver[STUB]); + const Stub = win.IntersectionObserver[STUB]; if (Stub) { const Native = win.IntersectionObserver[NATIVE]; delete win.IntersectionObserver; @@ -130,19 +143,14 @@ export function upgradePolyfill(win, installer) { ); } + /** @type {!Array} */ const upgraders = Stub[UPGRADERS].slice(0); const microtask = Promise.resolve(); const upgrade = (upgrader) => { microtask.then(() => upgrader(Polyfill)); }; - if (upgraders.length > 0) { - /** @type {!Array} */ (upgraders).forEach(upgrade); - } - Stub[ - UPGRADERS - ] = /** @type {!Array} */ ({ - 'push': upgrade, - }); + upgraders.forEach(upgrade); + Stub[UPGRADERS] = {'push': upgrade}; } else { // Even if this is not the stub, we still may need to polyfill // `isIntersecting`. See `shouldLoadPolyfill` for more info. @@ -154,6 +162,11 @@ export function upgradePolyfill(win, installer) { * The stub for `IntersectionObserver`. Implements the same interface, but * keeps the tracked elements in memory until the actual polyfill arives. * This stub is necessary because the polyfill itself is significantly bigger. + * + * It doesn't technically extend IntersectionObserver, but this allows the stub + * to be seen as equivalent when typechecking calls expecting an + * IntersectionObserver. + * @extends IntersectionObserver */ export class IntersectionObserverStub { /** @@ -181,9 +194,7 @@ export class IntersectionObserverStub { IntersectionObserverStub[UPGRADERS].push(this.upgrade_.bind(this)); } - /** - * @return {?Element} - */ + /** @return {?Element} */ get root() { if (this.inst_) { return this.inst_.root; @@ -191,19 +202,18 @@ export class IntersectionObserverStub { return this.options_.root || null; } - /** - * @return {*} - */ + /** @return {string} */ get rootMargin() { if (this.inst_) { return this.inst_.rootMargin; } - return this.options_.rootMargin; + // The CC-provided IntersectionObserverInit type allows for rootMargin to be + // undefined, but we provide a default, so it's guaranteed to be a string + // here. + return /** @type {string} */ (this.options_.rootMargin); } - /** - * @return {*} - */ + /** @return {!Array} */ get thresholds() { if (this.inst_) { return this.inst_.thresholds; @@ -211,9 +221,7 @@ export class IntersectionObserverStub { return [].concat(this.options_.threshold || 0); } - /** - * @return {undefined} - */ + /** @return {undefined} */ disconnect() { if (this.inst_) { this.inst_.disconnect(); @@ -222,9 +230,7 @@ export class IntersectionObserverStub { } } - /** - * @return {!Array} - */ + /** @return {!Array} */ takeRecords() { if (this.inst_) { return this.inst_.takeRecords(); @@ -232,9 +238,7 @@ export class IntersectionObserverStub { return []; } - /** - * @param {!Element} target - */ + /** @param {!Element} target */ observe(target) { if (this.inst_) { this.inst_.observe(target); @@ -245,9 +249,7 @@ export class IntersectionObserverStub { } } - /** - * @param {!Element} target - */ + /** @param {!Element} target */ unobserve(target) { if (this.inst_) { this.inst_.unobserve(target); @@ -260,20 +262,18 @@ export class IntersectionObserverStub { } /** - * @param {typeof IntersectionObserver} constr + * @param {!typeof IntersectionObserver} Ctor * @private */ - upgrade_(constr) { - const inst = new constr(this.callback_, this.options_); + upgrade_(Ctor) { + const inst = new Ctor(this.callback_, this.options_); this.inst_ = inst; this.elements_.forEach((e) => inst.observe(e)); this.elements_ = null; } } -/** - * @type {!Array} - */ +/** @type {!Array} */ IntersectionObserverStub[UPGRADERS] = []; /** @visibleForTesting */ diff --git a/src/polyfills/stubs/resize-observer-stub.js b/src/polyfills/stubs/resize-observer-stub.js index d7631ad091d3a..bd7321910bee4 100644 --- a/src/polyfills/stubs/resize-observer-stub.js +++ b/src/polyfills/stubs/resize-observer-stub.js @@ -23,6 +23,9 @@ * amp-resize-observer-polyfill extension. */ +/** @typedef {function(!typeof ResizeObserver)} */ +let ResObsUpgraderDef; + const UPGRADERS = '_upgraders'; const STUB = '_stub'; @@ -56,26 +59,21 @@ export function installStub(win) { export function upgradePolyfill(win, installer) { // Can't use the ResizeObserverStub here directly since it's a separate // instance deployed in v0.js vs the polyfill extension. - const Stub = /** @type {typeof ResizeObserverStub} */ (win.ResizeObserver[ - STUB - ]); + const Stub = win.ResizeObserver[STUB]; if (Stub) { delete win.ResizeObserver; delete win.ResizeObserverEntry; installer(); const Polyfill = win.ResizeObserver; + /** @type {!Array} */ const upgraders = Stub[UPGRADERS].slice(0); const microtask = Promise.resolve(); const upgrade = (upgrader) => { microtask.then(() => upgrader(Polyfill)); }; - if (upgraders.length > 0) { - /** @type {!Array} */ (upgraders).forEach(upgrade); - } - Stub[UPGRADERS] = /** @type {!Array} */ ({ - 'push': upgrade, - }); + upgraders.forEach(upgrade); + Stub[UPGRADERS] = {'push': upgrade}; } else { // Even if this is not the stub, we still may need to install the polyfill. // See `shouldLoadPolyfill` for more info. @@ -87,13 +85,14 @@ export function upgradePolyfill(win, installer) { * The stub for `ResizeObserver`. Implements the same interface, but * keeps the tracked elements in memory until the actual polyfill arives. * This stub is necessary because the polyfill itself is significantly bigger. + * It doesn't technically extend ResizeObserver, but this allows the stub + * to be seen as equivalent when typechecking calls expecting a ResizeObserver. + * @extends ResizeObserver */ export class ResizeObserverStub { - /** - * @param {!ResizeObserverCallback} callback - */ + /** @param {!ResizeObserverCallback} callback */ constructor(callback) { - /** @private @const */ + /** @private @const {!ResizeObserverCallback} */ this.callback_ = callback; /** @private {?Array} */ @@ -106,9 +105,7 @@ export class ResizeObserverStub { ResizeObserverStub[UPGRADERS].push(this.upgrade_.bind(this)); } - /** - * @return {*} - */ + /** @return {undefined} */ disconnect() { if (this.inst_) { this.inst_.disconnect(); @@ -117,9 +114,7 @@ export class ResizeObserverStub { } } - /** - * @param {!Element} target - */ + /** @param {!Element} target */ observe(target) { if (this.inst_) { this.inst_.observe(target); @@ -130,9 +125,7 @@ export class ResizeObserverStub { } } - /** - * @param {!Element} target - */ + /** @param {!Element} target */ unobserve(target) { if (this.inst_) { this.inst_.unobserve(target); @@ -145,20 +138,18 @@ export class ResizeObserverStub { } /** - * @param {typeof ResizeObserver} constr + * @param {!typeof ResizeObserver} Ctor * @private */ - upgrade_(constr) { - const inst = new constr(this.callback_, this.options_); + upgrade_(Ctor) { + const inst = new Ctor(this.callback_); this.inst_ = inst; this.elements_.forEach((e) => inst.observe(e)); this.elements_ = null; } } -/** - * @type {!Array} - */ +/** @type {!Array} */ ResizeObserverStub[UPGRADERS] = []; /** @visibleForTesting */